xref: /aosp_15_r20/external/toolchain-utils/rust_tools/rust_uprev_test.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li#!/usr/bin/env python3
2*760c253cSXin Li# Copyright 2020 The ChromiumOS Authors
3*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be
4*760c253cSXin Li# found in the LICENSE file.
5*760c253cSXin Li
6*760c253cSXin Li"""Tests for rust_uprev.py"""
7*760c253cSXin Li
8*760c253cSXin Liimport os
9*760c253cSXin Lifrom pathlib import Path
10*760c253cSXin Liimport shutil
11*760c253cSXin Liimport subprocess
12*760c253cSXin Liimport tempfile
13*760c253cSXin Liimport unittest
14*760c253cSXin Lifrom unittest import mock
15*760c253cSXin Li
16*760c253cSXin Lifrom llvm_tools import git
17*760c253cSXin Li
18*760c253cSXin Li
19*760c253cSXin Li# rust_uprev sets SOURCE_ROOT to the output of `repo --show-toplevel`.
20*760c253cSXin Li# The mock below makes us not actually run repo but use a fake value
21*760c253cSXin Li# instead.
22*760c253cSXin Liwith mock.patch("subprocess.check_output", return_value="/fake/chromiumos"):
23*760c253cSXin Li    import rust_uprev
24*760c253cSXin Li
25*760c253cSXin Li
26*760c253cSXin Lidef _fail_command(cmd, *_args, **_kwargs):
27*760c253cSXin Li    err = subprocess.CalledProcessError(returncode=1, cmd=cmd)
28*760c253cSXin Li    err.stderr = b"mock failure"
29*760c253cSXin Li    raise err
30*760c253cSXin Li
31*760c253cSXin Li
32*760c253cSXin Lidef start_mock(obj, *args, **kwargs):
33*760c253cSXin Li    """Creates a patcher, starts it, and registers a cleanup to stop it.
34*760c253cSXin Li
35*760c253cSXin Li    Args:
36*760c253cSXin Li        obj:
37*760c253cSXin Li            the object to attach the cleanup to
38*760c253cSXin Li        *args:
39*760c253cSXin Li            passed to mock.patch()
40*760c253cSXin Li        **kwargs:
41*760c253cSXin Li            passsed to mock.patch()
42*760c253cSXin Li    """
43*760c253cSXin Li    patcher = mock.patch(*args, **kwargs)
44*760c253cSXin Li    val = patcher.start()
45*760c253cSXin Li    obj.addCleanup(patcher.stop)
46*760c253cSXin Li    return val
47*760c253cSXin Li
48*760c253cSXin Li
49*760c253cSXin Liclass FetchDistfileTest(unittest.TestCase):
50*760c253cSXin Li    """Tests rust_uprev.fetch_distfile_from_mirror()"""
51*760c253cSXin Li
52*760c253cSXin Li    @mock.patch.object(
53*760c253cSXin Li        rust_uprev, "get_distdir", return_value=Path("/fake/distfiles")
54*760c253cSXin Li    )
55*760c253cSXin Li    @mock.patch.object(subprocess, "call", side_effect=_fail_command)
56*760c253cSXin Li    def test_fetch_difstfile_fail(self, *_args) -> None:
57*760c253cSXin Li        with self.assertRaises(subprocess.CalledProcessError):
58*760c253cSXin Li            rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz")
59*760c253cSXin Li
60*760c253cSXin Li    @mock.patch.object(
61*760c253cSXin Li        rust_uprev,
62*760c253cSXin Li        "get_command_output_unchecked",
63*760c253cSXin Li        return_value="AccessDeniedException: Access denied.",
64*760c253cSXin Li    )
65*760c253cSXin Li    @mock.patch.object(
66*760c253cSXin Li        rust_uprev, "get_distdir", return_value=Path("/fake/distfiles")
67*760c253cSXin Li    )
68*760c253cSXin Li    @mock.patch.object(subprocess, "call", return_value=0)
69*760c253cSXin Li    def test_fetch_distfile_acl_access_denied(self, *_args) -> None:
70*760c253cSXin Li        rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz")
71*760c253cSXin Li
72*760c253cSXin Li    @mock.patch.object(
73*760c253cSXin Li        rust_uprev,
74*760c253cSXin Li        "get_command_output_unchecked",
75*760c253cSXin Li        return_value='[ { "entity": "allUsers", "role": "READER" } ]',
76*760c253cSXin Li    )
77*760c253cSXin Li    @mock.patch.object(
78*760c253cSXin Li        rust_uprev, "get_distdir", return_value=Path("/fake/distfiles")
79*760c253cSXin Li    )
80*760c253cSXin Li    @mock.patch.object(subprocess, "call", return_value=0)
81*760c253cSXin Li    def test_fetch_distfile_acl_ok(self, *_args) -> None:
82*760c253cSXin Li        rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz")
83*760c253cSXin Li
84*760c253cSXin Li    @mock.patch.object(
85*760c253cSXin Li        rust_uprev,
86*760c253cSXin Li        "get_command_output_unchecked",
87*760c253cSXin Li        return_value='[ { "entity": "[email protected]", "role": "OWNER" } ]',
88*760c253cSXin Li    )
89*760c253cSXin Li    @mock.patch.object(
90*760c253cSXin Li        rust_uprev, "get_distdir", return_value=Path("/fake/distfiles")
91*760c253cSXin Li    )
92*760c253cSXin Li    @mock.patch.object(subprocess, "call", return_value=0)
93*760c253cSXin Li    def test_fetch_distfile_acl_wrong(self, *_args) -> None:
94*760c253cSXin Li        with self.assertRaisesRegex(Exception, "allUsers.*READER"):
95*760c253cSXin Li            with self.assertLogs(level="ERROR") as log:
96*760c253cSXin Li                rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz")
97*760c253cSXin Li                self.assertIn(
98*760c253cSXin Li                    '[ { "entity": "[email protected]", "role": "OWNER" } ]',
99*760c253cSXin Li                    "\n".join(log.output),
100*760c253cSXin Li                )
101*760c253cSXin Li
102*760c253cSXin Li
103*760c253cSXin Liclass FetchRustSrcFromUpstreamTest(unittest.TestCase):
104*760c253cSXin Li    """Tests for rust_uprev.fetch_rust_src_from_upstream."""
105*760c253cSXin Li
106*760c253cSXin Li    def setUp(self) -> None:
107*760c253cSXin Li        self._mock_get_distdir = start_mock(
108*760c253cSXin Li            self,
109*760c253cSXin Li            "rust_uprev.get_distdir",
110*760c253cSXin Li            return_value=Path("/fake/distfiles"),
111*760c253cSXin Li        )
112*760c253cSXin Li
113*760c253cSXin Li        self._mock_gpg = start_mock(
114*760c253cSXin Li            self,
115*760c253cSXin Li            "subprocess.run",
116*760c253cSXin Li            side_effect=self.fake_gpg,
117*760c253cSXin Li        )
118*760c253cSXin Li
119*760c253cSXin Li        self._mock_urlretrieve = start_mock(
120*760c253cSXin Li            self,
121*760c253cSXin Li            "urllib.request.urlretrieve",
122*760c253cSXin Li            side_effect=self.fake_urlretrieve,
123*760c253cSXin Li        )
124*760c253cSXin Li
125*760c253cSXin Li        self._mock_rust_signing_key = start_mock(
126*760c253cSXin Li            self,
127*760c253cSXin Li            "rust_uprev.RUST_SIGNING_KEY",
128*760c253cSXin Li            "1234567",
129*760c253cSXin Li        )
130*760c253cSXin Li
131*760c253cSXin Li    @staticmethod
132*760c253cSXin Li    def fake_urlretrieve(src: str, dest: Path) -> None:
133*760c253cSXin Li        pass
134*760c253cSXin Li
135*760c253cSXin Li    @staticmethod
136*760c253cSXin Li    def fake_gpg(cmd, **_kwargs):
137*760c253cSXin Li        val = mock.Mock()
138*760c253cSXin Li        val.returncode = 0
139*760c253cSXin Li        val.stdout = ""
140*760c253cSXin Li        if "--verify" in cmd:
141*760c253cSXin Li            val.stdout = "GOODSIG 1234567"
142*760c253cSXin Li        return val
143*760c253cSXin Li
144*760c253cSXin Li    def test_success(self):
145*760c253cSXin Li        with mock.patch("rust_uprev.GPG", "gnupg"):
146*760c253cSXin Li            rust_uprev.fetch_rust_src_from_upstream(
147*760c253cSXin Li                "fakehttps://rustc-1.60.3-src.tar.gz",
148*760c253cSXin Li                Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"),
149*760c253cSXin Li            )
150*760c253cSXin Li            self._mock_urlretrieve.has_calls(
151*760c253cSXin Li                [
152*760c253cSXin Li                    (
153*760c253cSXin Li                        "fakehttps://rustc-1.60.3-src.tar.gz",
154*760c253cSXin Li                        Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"),
155*760c253cSXin Li                    ),
156*760c253cSXin Li                    (
157*760c253cSXin Li                        "fakehttps://rustc-1.60.3-src.tar.gz.asc",
158*760c253cSXin Li                        Path("/fake/distfiles/rustc-1.60.3-src.tar.gz.asc"),
159*760c253cSXin Li                    ),
160*760c253cSXin Li                ]
161*760c253cSXin Li            )
162*760c253cSXin Li            self._mock_gpg.has_calls(
163*760c253cSXin Li                [
164*760c253cSXin Li                    (["gnupg", "--refresh-keys", "1234567"], {"check": True}),
165*760c253cSXin Li                ]
166*760c253cSXin Li            )
167*760c253cSXin Li
168*760c253cSXin Li    def test_no_signature_file(self):
169*760c253cSXin Li        def _urlretrieve(src, dest):
170*760c253cSXin Li            if src.endswith(".asc"):
171*760c253cSXin Li                raise Exception("404 not found")
172*760c253cSXin Li            return self.fake_urlretrieve(src, dest)
173*760c253cSXin Li
174*760c253cSXin Li        self._mock_urlretrieve.side_effect = _urlretrieve
175*760c253cSXin Li
176*760c253cSXin Li        with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx:
177*760c253cSXin Li            rust_uprev.fetch_rust_src_from_upstream(
178*760c253cSXin Li                "fakehttps://rustc-1.60.3-src.tar.gz",
179*760c253cSXin Li                Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"),
180*760c253cSXin Li            )
181*760c253cSXin Li        self.assertIn("error fetching signature file", ctx.exception.message)
182*760c253cSXin Li
183*760c253cSXin Li    def test_key_expired(self):
184*760c253cSXin Li        def _gpg_verify(cmd, *args, **kwargs):
185*760c253cSXin Li            val = self.fake_gpg(cmd, *args, **kwargs)
186*760c253cSXin Li            if "--verify" in cmd:
187*760c253cSXin Li                val.stdout = "EXPKEYSIG 1234567"
188*760c253cSXin Li            return val
189*760c253cSXin Li
190*760c253cSXin Li        self._mock_gpg.side_effect = _gpg_verify
191*760c253cSXin Li
192*760c253cSXin Li        with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx:
193*760c253cSXin Li            rust_uprev.fetch_rust_src_from_upstream(
194*760c253cSXin Li                "fakehttps://rustc-1.60.3-src.tar.gz",
195*760c253cSXin Li                Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"),
196*760c253cSXin Li            )
197*760c253cSXin Li        self.assertIn("key has expired", ctx.exception.message)
198*760c253cSXin Li
199*760c253cSXin Li    def test_key_revoked(self):
200*760c253cSXin Li        def _gpg_verify(cmd, *args, **kwargs):
201*760c253cSXin Li            val = self.fake_gpg(cmd, *args, **kwargs)
202*760c253cSXin Li            if "--verify" in cmd:
203*760c253cSXin Li                val.stdout = "REVKEYSIG 1234567"
204*760c253cSXin Li            return val
205*760c253cSXin Li
206*760c253cSXin Li        self._mock_gpg.side_effect = _gpg_verify
207*760c253cSXin Li
208*760c253cSXin Li        with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx:
209*760c253cSXin Li            rust_uprev.fetch_rust_src_from_upstream(
210*760c253cSXin Li                "fakehttps://rustc-1.60.3-src.tar.gz",
211*760c253cSXin Li                Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"),
212*760c253cSXin Li            )
213*760c253cSXin Li        self.assertIn("key has been revoked", ctx.exception.message)
214*760c253cSXin Li
215*760c253cSXin Li    def test_signature_expired(self):
216*760c253cSXin Li        def _gpg_verify(cmd, *args, **kwargs):
217*760c253cSXin Li            val = self.fake_gpg(cmd, *args, **kwargs)
218*760c253cSXin Li            if "--verify" in cmd:
219*760c253cSXin Li                val.stdout = "EXPSIG 1234567"
220*760c253cSXin Li            return val
221*760c253cSXin Li
222*760c253cSXin Li        self._mock_gpg.side_effect = _gpg_verify
223*760c253cSXin Li
224*760c253cSXin Li        with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx:
225*760c253cSXin Li            rust_uprev.fetch_rust_src_from_upstream(
226*760c253cSXin Li                "fakehttps://rustc-1.60.3-src.tar.gz",
227*760c253cSXin Li                Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"),
228*760c253cSXin Li            )
229*760c253cSXin Li        self.assertIn("signature has expired", ctx.exception.message)
230*760c253cSXin Li
231*760c253cSXin Li    def test_wrong_key(self):
232*760c253cSXin Li        def _gpg_verify(cmd, *args, **kwargs):
233*760c253cSXin Li            val = self.fake_gpg(cmd, *args, **kwargs)
234*760c253cSXin Li            if "--verify" in cmd:
235*760c253cSXin Li                val.stdout = "GOODSIG 0000000"
236*760c253cSXin Li            return val
237*760c253cSXin Li
238*760c253cSXin Li        self._mock_gpg.side_effect = _gpg_verify
239*760c253cSXin Li
240*760c253cSXin Li        with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx:
241*760c253cSXin Li            rust_uprev.fetch_rust_src_from_upstream(
242*760c253cSXin Li                "fakehttps://rustc-1.60.3-src.tar.gz",
243*760c253cSXin Li                Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"),
244*760c253cSXin Li            )
245*760c253cSXin Li        self.assertIn("1234567 not found", ctx.exception.message)
246*760c253cSXin Li
247*760c253cSXin Li
248*760c253cSXin Liclass FindEbuildPathTest(unittest.TestCase):
249*760c253cSXin Li    """Tests for rust_uprev.find_ebuild_path()"""
250*760c253cSXin Li
251*760c253cSXin Li    def test_exact_version(self):
252*760c253cSXin Li        with tempfile.TemporaryDirectory() as t:
253*760c253cSXin Li            tmpdir = Path(t)
254*760c253cSXin Li            ebuild = tmpdir / "test-1.3.4.ebuild"
255*760c253cSXin Li            ebuild.touch()
256*760c253cSXin Li            (tmpdir / "test-1.2.3.ebuild").touch()
257*760c253cSXin Li            result = rust_uprev.find_ebuild_path(
258*760c253cSXin Li                tmpdir, "test", rust_uprev.RustVersion(1, 3, 4)
259*760c253cSXin Li            )
260*760c253cSXin Li            self.assertEqual(result, ebuild)
261*760c253cSXin Li
262*760c253cSXin Li    def test_no_version(self):
263*760c253cSXin Li        with tempfile.TemporaryDirectory() as t:
264*760c253cSXin Li            tmpdir = Path(t)
265*760c253cSXin Li            ebuild = tmpdir / "test-1.2.3.ebuild"
266*760c253cSXin Li            ebuild.touch()
267*760c253cSXin Li            result = rust_uprev.find_ebuild_path(tmpdir, "test")
268*760c253cSXin Li            self.assertEqual(result, ebuild)
269*760c253cSXin Li
270*760c253cSXin Li    def test_patch_version(self):
271*760c253cSXin Li        with tempfile.TemporaryDirectory() as t:
272*760c253cSXin Li            tmpdir = Path(t)
273*760c253cSXin Li            ebuild = tmpdir / "test-1.3.4-r3.ebuild"
274*760c253cSXin Li            ebuild.touch()
275*760c253cSXin Li            (tmpdir / "test-1.2.3.ebuild").touch()
276*760c253cSXin Li            result = rust_uprev.find_ebuild_path(
277*760c253cSXin Li                tmpdir, "test", rust_uprev.RustVersion(1, 3, 4)
278*760c253cSXin Li            )
279*760c253cSXin Li            self.assertEqual(result, ebuild)
280*760c253cSXin Li
281*760c253cSXin Li    def test_multiple_versions(self):
282*760c253cSXin Li        with tempfile.TemporaryDirectory() as t:
283*760c253cSXin Li            tmpdir = Path(t)
284*760c253cSXin Li            (tmpdir / "test-1.3.4-r3.ebuild").touch()
285*760c253cSXin Li            (tmpdir / "test-1.3.5.ebuild").touch()
286*760c253cSXin Li            with self.assertRaises(AssertionError):
287*760c253cSXin Li                rust_uprev.find_ebuild_path(tmpdir, "test")
288*760c253cSXin Li
289*760c253cSXin Li    def test_selected_version(self):
290*760c253cSXin Li        with tempfile.TemporaryDirectory() as t:
291*760c253cSXin Li            tmpdir = Path(t)
292*760c253cSXin Li            ebuild = tmpdir / "test-1.3.4-r3.ebuild"
293*760c253cSXin Li            ebuild.touch()
294*760c253cSXin Li            (tmpdir / "test-1.3.5.ebuild").touch()
295*760c253cSXin Li            result = rust_uprev.find_ebuild_path(
296*760c253cSXin Li                tmpdir, "test", rust_uprev.RustVersion(1, 3, 4)
297*760c253cSXin Li            )
298*760c253cSXin Li            self.assertEqual(result, ebuild)
299*760c253cSXin Li
300*760c253cSXin Li    def test_symlink(self):
301*760c253cSXin Li        # Symlinks to ebuilds in the same directory are allowed, and the return
302*760c253cSXin Li        # value is the regular file.
303*760c253cSXin Li        with tempfile.TemporaryDirectory() as t:
304*760c253cSXin Li            tmpdir = Path(t)
305*760c253cSXin Li            ebuild = tmpdir / "test-1.3.4.ebuild"
306*760c253cSXin Li            ebuild.touch()
307*760c253cSXin Li            (tmpdir / "test-1.3.4-r1.ebuild").symlink_to("test-1.3.4.ebuild")
308*760c253cSXin Li            result = rust_uprev.find_ebuild_path(tmpdir, "test")
309*760c253cSXin Li            self.assertEqual(result, ebuild)
310*760c253cSXin Li
311*760c253cSXin Li
312*760c253cSXin Liclass FindRustVersionsTest(unittest.TestCase):
313*760c253cSXin Li    """Tests for rust_uprev.find_rust_versions."""
314*760c253cSXin Li
315*760c253cSXin Li    def test_with_symlinks(self):
316*760c253cSXin Li        with tempfile.TemporaryDirectory() as t:
317*760c253cSXin Li            tmpdir = Path(t)
318*760c253cSXin Li            rust_1_49_1_ebuild = tmpdir / "rust-1.49.1.ebuild"
319*760c253cSXin Li            rust_1_50_0_ebuild = tmpdir / "rust-1.50.0.ebuild"
320*760c253cSXin Li            rust_1_50_0_r1_ebuild = tmpdir / "rust-1.50.0-r1.ebuild"
321*760c253cSXin Li            rust_1_49_1_ebuild.touch()
322*760c253cSXin Li            rust_1_50_0_ebuild.touch()
323*760c253cSXin Li            rust_1_50_0_r1_ebuild.symlink_to(rust_1_50_0_ebuild)
324*760c253cSXin Li            with mock.patch("rust_uprev.RUST_PATH", tmpdir):
325*760c253cSXin Li                actual = rust_uprev.find_rust_versions()
326*760c253cSXin Li                expected = [
327*760c253cSXin Li                    (rust_uprev.RustVersion(1, 49, 1), rust_1_49_1_ebuild),
328*760c253cSXin Li                    (rust_uprev.RustVersion(1, 50, 0), rust_1_50_0_ebuild),
329*760c253cSXin Li                ]
330*760c253cSXin Li                self.assertEqual(actual, expected)
331*760c253cSXin Li
332*760c253cSXin Li
333*760c253cSXin Liclass MirrorHasFileTest(unittest.TestCase):
334*760c253cSXin Li    """Tests for rust_uprev.mirror_has_file."""
335*760c253cSXin Li
336*760c253cSXin Li    @mock.patch.object(subprocess, "run")
337*760c253cSXin Li    def test_no(self, mock_run):
338*760c253cSXin Li        mock_run.return_value = mock.Mock(
339*760c253cSXin Li            returncode=1,
340*760c253cSXin Li            stdout="CommandException: One or more URLs matched no objects.",
341*760c253cSXin Li        )
342*760c253cSXin Li        self.assertFalse(rust_uprev.mirror_has_file("rustc-1.69.0-src.tar.gz"))
343*760c253cSXin Li
344*760c253cSXin Li    @mock.patch.object(subprocess, "run")
345*760c253cSXin Li    def test_yes(self, mock_run):
346*760c253cSXin Li        mock_run.return_value = mock.Mock(
347*760c253cSXin Li            returncode=0,
348*760c253cSXin Li            # pylint: disable=line-too-long
349*760c253cSXin Li            stdout="gs://chromeos-localmirror/distfiles/rustc-1.69.0-src.tar.gz",
350*760c253cSXin Li        )
351*760c253cSXin Li        self.assertTrue(rust_uprev.mirror_has_file("rustc-1.69.0-src.tar.gz"))
352*760c253cSXin Li
353*760c253cSXin Li
354*760c253cSXin Liclass MirrorRustSourceTest(unittest.TestCase):
355*760c253cSXin Li    """Tests for rust_uprev.mirror_rust_source."""
356*760c253cSXin Li
357*760c253cSXin Li    def setUp(self) -> None:
358*760c253cSXin Li        start_mock(self, "rust_uprev.GSUTIL", "gsutil")
359*760c253cSXin Li        start_mock(self, "rust_uprev.MIRROR_PATH", "fakegs://fakemirror/")
360*760c253cSXin Li        start_mock(
361*760c253cSXin Li            self, "rust_uprev.get_distdir", return_value=Path("/fake/distfiles")
362*760c253cSXin Li        )
363*760c253cSXin Li        self.mock_mirror_has_file = start_mock(
364*760c253cSXin Li            self,
365*760c253cSXin Li            "rust_uprev.mirror_has_file",
366*760c253cSXin Li        )
367*760c253cSXin Li        self.mock_fetch_rust_src_from_upstream = start_mock(
368*760c253cSXin Li            self,
369*760c253cSXin Li            "rust_uprev.fetch_rust_src_from_upstream",
370*760c253cSXin Li        )
371*760c253cSXin Li        self.mock_subprocess_run = start_mock(
372*760c253cSXin Li            self,
373*760c253cSXin Li            "subprocess.run",
374*760c253cSXin Li        )
375*760c253cSXin Li
376*760c253cSXin Li    def test_already_present(self):
377*760c253cSXin Li        self.mock_mirror_has_file.return_value = True
378*760c253cSXin Li        rust_uprev.mirror_rust_source(
379*760c253cSXin Li            rust_uprev.RustVersion.parse("1.67.3"),
380*760c253cSXin Li        )
381*760c253cSXin Li        self.mock_fetch_rust_src_from_upstream.assert_not_called()
382*760c253cSXin Li        self.mock_subprocess_run.assert_not_called()
383*760c253cSXin Li
384*760c253cSXin Li    def test_fetch_and_upload(self):
385*760c253cSXin Li        self.mock_mirror_has_file.return_value = False
386*760c253cSXin Li        rust_uprev.mirror_rust_source(
387*760c253cSXin Li            rust_uprev.RustVersion.parse("1.67.3"),
388*760c253cSXin Li        )
389*760c253cSXin Li        self.mock_fetch_rust_src_from_upstream.called_once()
390*760c253cSXin Li        self.mock_subprocess_run.has_calls(
391*760c253cSXin Li            [
392*760c253cSXin Li                (
393*760c253cSXin Li                    [
394*760c253cSXin Li                        "gsutil",
395*760c253cSXin Li                        "cp",
396*760c253cSXin Li                        "-a",
397*760c253cSXin Li                        "public-read",
398*760c253cSXin Li                        "/fake/distdir/rustc-1.67.3-src.tar.gz",
399*760c253cSXin Li                        "fakegs://fakemirror/rustc-1.67.3-src.tar.gz",
400*760c253cSXin Li                    ]
401*760c253cSXin Li                ),
402*760c253cSXin Li            ]
403*760c253cSXin Li        )
404*760c253cSXin Li
405*760c253cSXin Li
406*760c253cSXin Liclass RemoveEbuildVersionTest(unittest.TestCase):
407*760c253cSXin Li    """Tests for rust_uprev.remove_ebuild_version()"""
408*760c253cSXin Li
409*760c253cSXin Li    @mock.patch.object(subprocess, "check_call")
410*760c253cSXin Li    def test_single(self, check_call):
411*760c253cSXin Li        with tempfile.TemporaryDirectory() as tmpdir:
412*760c253cSXin Li            ebuild_dir = Path(tmpdir, "test-ebuilds")
413*760c253cSXin Li            ebuild_dir.mkdir()
414*760c253cSXin Li            ebuild = Path(ebuild_dir, "test-3.1.4.ebuild")
415*760c253cSXin Li            ebuild.touch()
416*760c253cSXin Li            Path(ebuild_dir, "unrelated-1.0.0.ebuild").touch()
417*760c253cSXin Li            rust_uprev.remove_ebuild_version(
418*760c253cSXin Li                ebuild_dir, "test", rust_uprev.RustVersion(3, 1, 4)
419*760c253cSXin Li            )
420*760c253cSXin Li            check_call.assert_called_once_with(
421*760c253cSXin Li                ["git", "rm", "test-3.1.4.ebuild"], cwd=ebuild_dir
422*760c253cSXin Li            )
423*760c253cSXin Li
424*760c253cSXin Li    @mock.patch.object(subprocess, "check_call")
425*760c253cSXin Li    def test_symlink(self, check_call):
426*760c253cSXin Li        with tempfile.TemporaryDirectory() as tmpdir:
427*760c253cSXin Li            ebuild_dir = Path(tmpdir, "test-ebuilds")
428*760c253cSXin Li            ebuild_dir.mkdir()
429*760c253cSXin Li            ebuild = Path(ebuild_dir, "test-3.1.4.ebuild")
430*760c253cSXin Li            ebuild.touch()
431*760c253cSXin Li            symlink = Path(ebuild_dir, "test-3.1.4-r5.ebuild")
432*760c253cSXin Li            symlink.symlink_to(ebuild.name)
433*760c253cSXin Li            Path(ebuild_dir, "unrelated-1.0.0.ebuild").touch()
434*760c253cSXin Li            rust_uprev.remove_ebuild_version(
435*760c253cSXin Li                ebuild_dir, "test", rust_uprev.RustVersion(3, 1, 4)
436*760c253cSXin Li            )
437*760c253cSXin Li            check_call.assert_has_calls(
438*760c253cSXin Li                [
439*760c253cSXin Li                    mock.call(
440*760c253cSXin Li                        ["git", "rm", "test-3.1.4.ebuild"], cwd=ebuild_dir
441*760c253cSXin Li                    ),
442*760c253cSXin Li                    mock.call(
443*760c253cSXin Li                        ["git", "rm", "test-3.1.4-r5.ebuild"], cwd=ebuild_dir
444*760c253cSXin Li                    ),
445*760c253cSXin Li                ],
446*760c253cSXin Li                any_order=True,
447*760c253cSXin Li            )
448*760c253cSXin Li
449*760c253cSXin Li
450*760c253cSXin Liclass RustVersionTest(unittest.TestCase):
451*760c253cSXin Li    """Tests for RustVersion class"""
452*760c253cSXin Li
453*760c253cSXin Li    def test_str(self):
454*760c253cSXin Li        obj = rust_uprev.RustVersion(major=1, minor=2, patch=3)
455*760c253cSXin Li        self.assertEqual(str(obj), "1.2.3")
456*760c253cSXin Li
457*760c253cSXin Li    def test_parse_version_only(self):
458*760c253cSXin Li        expected = rust_uprev.RustVersion(major=1, minor=2, patch=3)
459*760c253cSXin Li        actual = rust_uprev.RustVersion.parse("1.2.3")
460*760c253cSXin Li        self.assertEqual(expected, actual)
461*760c253cSXin Li
462*760c253cSXin Li    def test_parse_ebuild_name(self):
463*760c253cSXin Li        expected = rust_uprev.RustVersion(major=1, minor=2, patch=3)
464*760c253cSXin Li        actual = rust_uprev.RustVersion.parse_from_ebuild("rust-1.2.3.ebuild")
465*760c253cSXin Li        self.assertEqual(expected, actual)
466*760c253cSXin Li
467*760c253cSXin Li        actual = rust_uprev.RustVersion.parse_from_ebuild(
468*760c253cSXin Li            "rust-1.2.3-r1.ebuild"
469*760c253cSXin Li        )
470*760c253cSXin Li        self.assertEqual(expected, actual)
471*760c253cSXin Li
472*760c253cSXin Li    def test_parse_fail(self):
473*760c253cSXin Li        with self.assertRaises(AssertionError) as context:
474*760c253cSXin Li            rust_uprev.RustVersion.parse("invalid-rust-1.2.3")
475*760c253cSXin Li        self.assertEqual(
476*760c253cSXin Li            "failed to parse 'invalid-rust-1.2.3'", str(context.exception)
477*760c253cSXin Li        )
478*760c253cSXin Li
479*760c253cSXin Li
480*760c253cSXin Liclass PrepareUprevTest(unittest.TestCase):
481*760c253cSXin Li    """Tests for prepare_uprev step in rust_uprev"""
482*760c253cSXin Li
483*760c253cSXin Li    def setUp(self):
484*760c253cSXin Li        self.version_old = rust_uprev.RustVersion(1, 2, 3)
485*760c253cSXin Li        self.version_new = rust_uprev.RustVersion(1, 3, 5)
486*760c253cSXin Li
487*760c253cSXin Li    @mock.patch.object(
488*760c253cSXin Li        rust_uprev,
489*760c253cSXin Li        "find_ebuild_for_rust_version",
490*760c253cSXin Li        return_value=Path("/path/to/ebuild"),
491*760c253cSXin Li    )
492*760c253cSXin Li    @mock.patch.object(rust_uprev, "get_command_output")
493*760c253cSXin Li    def test_success_with_template(self, mock_command, _ebuild_for_version):
494*760c253cSXin Li        expected = rust_uprev.PreparedUprev(self.version_old)
495*760c253cSXin Li        actual = rust_uprev.prepare_uprev(
496*760c253cSXin Li            rust_version=self.version_new, template=self.version_old
497*760c253cSXin Li        )
498*760c253cSXin Li        self.assertEqual(expected, actual)
499*760c253cSXin Li        mock_command.assert_not_called()
500*760c253cSXin Li
501*760c253cSXin Li    @mock.patch.object(
502*760c253cSXin Li        rust_uprev,
503*760c253cSXin Li        "find_ebuild_for_rust_version",
504*760c253cSXin Li        return_value="/path/to/ebuild",
505*760c253cSXin Li    )
506*760c253cSXin Li    @mock.patch.object(rust_uprev, "get_command_output")
507*760c253cSXin Li    def test_return_none_with_template_larger_than_input(
508*760c253cSXin Li        self, mock_command, *_args
509*760c253cSXin Li    ):
510*760c253cSXin Li        ret = rust_uprev.prepare_uprev(
511*760c253cSXin Li            rust_version=self.version_old, template=self.version_new
512*760c253cSXin Li        )
513*760c253cSXin Li        self.assertIsNone(ret)
514*760c253cSXin Li        mock_command.assert_not_called()
515*760c253cSXin Li
516*760c253cSXin Li    def test_prepare_uprev_from_json(self):
517*760c253cSXin Li        json_result = (list(self.version_new),)
518*760c253cSXin Li        expected = rust_uprev.PreparedUprev(
519*760c253cSXin Li            self.version_new,
520*760c253cSXin Li        )
521*760c253cSXin Li        actual = rust_uprev.prepare_uprev_from_json(json_result)
522*760c253cSXin Li        self.assertEqual(expected, actual)
523*760c253cSXin Li
524*760c253cSXin Li
525*760c253cSXin Liclass ToggleProfileData(unittest.TestCase):
526*760c253cSXin Li    """Tests functionality to include or exclude profile data from SRC_URI."""
527*760c253cSXin Li
528*760c253cSXin Li    ebuild_with_profdata = """
529*760c253cSXin Li# Some text here.
530*760c253cSXin LiINCLUDE_PROFDATA_IN_SRC_URI=yes
531*760c253cSXin Lisome code here
532*760c253cSXin Li"""
533*760c253cSXin Li
534*760c253cSXin Li    ebuild_without_profdata = """
535*760c253cSXin Li# Some text here.
536*760c253cSXin LiINCLUDE_PROFDATA_IN_SRC_URI=
537*760c253cSXin Lisome code here
538*760c253cSXin Li"""
539*760c253cSXin Li
540*760c253cSXin Li    ebuild_unexpected_content = """
541*760c253cSXin Li# Does not contain OMIT_PROFDATA_FROM_SRC_URI assignment
542*760c253cSXin Li"""
543*760c253cSXin Li
544*760c253cSXin Li    def setUp(self):
545*760c253cSXin Li        self.mock_read_text = start_mock(self, "pathlib.Path.read_text")
546*760c253cSXin Li
547*760c253cSXin Li    def test_turn_off_profdata(self):
548*760c253cSXin Li        # Test that a file with profdata on is rewritten to a file with
549*760c253cSXin Li        # profdata off.
550*760c253cSXin Li        self.mock_read_text.return_value = self.ebuild_with_profdata
551*760c253cSXin Li        ebuild_file = "/path/to/eclass/cros-rustc.eclass"
552*760c253cSXin Li        with mock.patch("pathlib.Path.write_text") as mock_write_text:
553*760c253cSXin Li            rust_uprev.set_include_profdata_src(ebuild_file, include=False)
554*760c253cSXin Li            mock_write_text.assert_called_once_with(
555*760c253cSXin Li                self.ebuild_without_profdata, encoding="utf-8"
556*760c253cSXin Li            )
557*760c253cSXin Li
558*760c253cSXin Li    def test_turn_on_profdata(self):
559*760c253cSXin Li        # Test that a file with profdata off is rewritten to a file with
560*760c253cSXin Li        # profdata on.
561*760c253cSXin Li        self.mock_read_text.return_value = self.ebuild_without_profdata
562*760c253cSXin Li        ebuild_file = "/path/to/eclass/cros-rustc.eclass"
563*760c253cSXin Li        with mock.patch("pathlib.Path.write_text") as mock_write_text:
564*760c253cSXin Li            rust_uprev.set_include_profdata_src(ebuild_file, include=True)
565*760c253cSXin Li            mock_write_text.assert_called_once_with(
566*760c253cSXin Li                self.ebuild_with_profdata, encoding="utf-8"
567*760c253cSXin Li            )
568*760c253cSXin Li
569*760c253cSXin Li    def test_turn_on_profdata_fails_if_no_assignment(self):
570*760c253cSXin Li        # Test that if the string the code expects to find is not found,
571*760c253cSXin Li        # this causes an exception and the file is not overwritten.
572*760c253cSXin Li        self.mock_read_text.return_value = self.ebuild_unexpected_content
573*760c253cSXin Li        ebuild_file = "/path/to/eclass/cros-rustc.eclass"
574*760c253cSXin Li        with mock.patch("pathlib.Path.write_text") as mock_write_text:
575*760c253cSXin Li            with self.assertRaises(Exception):
576*760c253cSXin Li                rust_uprev.set_include_profdata_src(ebuild_file, include=False)
577*760c253cSXin Li            mock_write_text.assert_not_called()
578*760c253cSXin Li
579*760c253cSXin Li
580*760c253cSXin Liclass UpdateBootstrapVersionTest(unittest.TestCase):
581*760c253cSXin Li    """Tests for update_bootstrap_version step in rust_uprev"""
582*760c253cSXin Li
583*760c253cSXin Li    ebuild_file_before = """
584*760c253cSXin LiBOOTSTRAP_VERSION="1.2.0"
585*760c253cSXin Li    """
586*760c253cSXin Li    ebuild_file_after = """
587*760c253cSXin LiBOOTSTRAP_VERSION="1.3.6"
588*760c253cSXin Li    """
589*760c253cSXin Li
590*760c253cSXin Li    def setUp(self):
591*760c253cSXin Li        self.mock_read_text = start_mock(self, "pathlib.Path.read_text")
592*760c253cSXin Li
593*760c253cSXin Li    def test_success(self):
594*760c253cSXin Li        self.mock_read_text.return_value = self.ebuild_file_before
595*760c253cSXin Li        # ebuild_file and new bootstrap version are deliberately different
596*760c253cSXin Li        ebuild_file = "/path/to/rust/cros-rustc.eclass"
597*760c253cSXin Li        with mock.patch("pathlib.Path.write_text") as mock_write_text:
598*760c253cSXin Li            rust_uprev.update_bootstrap_version(
599*760c253cSXin Li                ebuild_file, rust_uprev.RustVersion.parse("1.3.6")
600*760c253cSXin Li            )
601*760c253cSXin Li            mock_write_text.assert_called_once_with(
602*760c253cSXin Li                self.ebuild_file_after, encoding="utf-8"
603*760c253cSXin Li            )
604*760c253cSXin Li
605*760c253cSXin Li    def test_fail_when_ebuild_misses_a_variable(self):
606*760c253cSXin Li        self.mock_read_text.return_value = ""
607*760c253cSXin Li        ebuild_file = "/path/to/rust/rust-1.3.5.ebuild"
608*760c253cSXin Li        with self.assertRaises(RuntimeError) as context:
609*760c253cSXin Li            rust_uprev.update_bootstrap_version(
610*760c253cSXin Li                ebuild_file, rust_uprev.RustVersion.parse("1.2.0")
611*760c253cSXin Li            )
612*760c253cSXin Li        self.assertEqual(
613*760c253cSXin Li            "BOOTSTRAP_VERSION not found in /path/to/rust/rust-1.3.5.ebuild",
614*760c253cSXin Li            str(context.exception),
615*760c253cSXin Li        )
616*760c253cSXin Li
617*760c253cSXin Li
618*760c253cSXin Liclass UpdateRustPackagesTests(unittest.TestCase):
619*760c253cSXin Li    """Tests for update_rust_packages step."""
620*760c253cSXin Li
621*760c253cSXin Li    def setUp(self):
622*760c253cSXin Li        self.old_version = rust_uprev.RustVersion(1, 1, 0)
623*760c253cSXin Li        self.current_version = rust_uprev.RustVersion(1, 2, 3)
624*760c253cSXin Li        self.new_version = rust_uprev.RustVersion(1, 3, 5)
625*760c253cSXin Li        self.ebuild_file = os.path.join(
626*760c253cSXin Li            rust_uprev.RUST_PATH, "rust-{self.new_version}.ebuild"
627*760c253cSXin Li        )
628*760c253cSXin Li
629*760c253cSXin Li    def test_add_new_rust_packages(self):
630*760c253cSXin Li        package_before = (
631*760c253cSXin Li            f"dev-lang/rust-{self.old_version}\n"
632*760c253cSXin Li            f"dev-lang/rust-{self.current_version}"
633*760c253cSXin Li        )
634*760c253cSXin Li        package_after = (
635*760c253cSXin Li            f"dev-lang/rust-{self.old_version}\n"
636*760c253cSXin Li            f"dev-lang/rust-{self.current_version}\n"
637*760c253cSXin Li            f"dev-lang/rust-{self.new_version}"
638*760c253cSXin Li        )
639*760c253cSXin Li        mock_open = mock.mock_open(read_data=package_before)
640*760c253cSXin Li        with mock.patch("builtins.open", mock_open):
641*760c253cSXin Li            rust_uprev.update_rust_packages(
642*760c253cSXin Li                "dev-lang/rust", self.new_version, add=True
643*760c253cSXin Li            )
644*760c253cSXin Li        mock_open.return_value.__enter__().write.assert_called_once_with(
645*760c253cSXin Li            package_after
646*760c253cSXin Li        )
647*760c253cSXin Li
648*760c253cSXin Li    def test_remove_old_rust_packages(self):
649*760c253cSXin Li        package_before = (
650*760c253cSXin Li            f"dev-lang/rust-{self.old_version}\n"
651*760c253cSXin Li            f"dev-lang/rust-{self.current_version}\n"
652*760c253cSXin Li            f"dev-lang/rust-{self.new_version}"
653*760c253cSXin Li        )
654*760c253cSXin Li        package_after = (
655*760c253cSXin Li            f"dev-lang/rust-{self.current_version}\n"
656*760c253cSXin Li            f"dev-lang/rust-{self.new_version}"
657*760c253cSXin Li        )
658*760c253cSXin Li        mock_open = mock.mock_open(read_data=package_before)
659*760c253cSXin Li        with mock.patch("builtins.open", mock_open):
660*760c253cSXin Li            rust_uprev.update_rust_packages(
661*760c253cSXin Li                "dev-lang/rust", self.old_version, add=False
662*760c253cSXin Li            )
663*760c253cSXin Li        mock_open.return_value.__enter__().write.assert_called_once_with(
664*760c253cSXin Li            package_after
665*760c253cSXin Li        )
666*760c253cSXin Li
667*760c253cSXin Li
668*760c253cSXin Liclass RustUprevOtherStagesTests(unittest.TestCase):
669*760c253cSXin Li    """Tests for other steps in rust_uprev"""
670*760c253cSXin Li
671*760c253cSXin Li    def setUp(self):
672*760c253cSXin Li        self.old_version = rust_uprev.RustVersion(1, 1, 0)
673*760c253cSXin Li        self.current_version = rust_uprev.RustVersion(1, 2, 3)
674*760c253cSXin Li        self.new_version = rust_uprev.RustVersion(1, 3, 5)
675*760c253cSXin Li        self.ebuild_file = os.path.join(
676*760c253cSXin Li            rust_uprev.RUST_PATH, "rust-{self.new_version}.ebuild"
677*760c253cSXin Li        )
678*760c253cSXin Li
679*760c253cSXin Li    @mock.patch.object(shutil, "copyfile")
680*760c253cSXin Li    @mock.patch.object(subprocess, "check_call")
681*760c253cSXin Li    def test_create_rust_ebuild(self, mock_call, mock_copy):
682*760c253cSXin Li        template_ebuild = (
683*760c253cSXin Li            rust_uprev.EBUILD_PREFIX
684*760c253cSXin Li            / f"dev-lang/rust/rust-{self.current_version}.ebuild"
685*760c253cSXin Li        )
686*760c253cSXin Li        new_ebuild = (
687*760c253cSXin Li            rust_uprev.EBUILD_PREFIX
688*760c253cSXin Li            / f"dev-lang/rust/rust-{self.new_version}.ebuild"
689*760c253cSXin Li        )
690*760c253cSXin Li        rust_uprev.create_ebuild(
691*760c253cSXin Li            "dev-lang", "rust", self.current_version, self.new_version
692*760c253cSXin Li        )
693*760c253cSXin Li        mock_copy.assert_called_once_with(
694*760c253cSXin Li            template_ebuild,
695*760c253cSXin Li            new_ebuild,
696*760c253cSXin Li        )
697*760c253cSXin Li        mock_call.assert_called_once_with(
698*760c253cSXin Li            ["git", "add", f"rust-{self.new_version}.ebuild"],
699*760c253cSXin Li            cwd=new_ebuild.parent,
700*760c253cSXin Li        )
701*760c253cSXin Li
702*760c253cSXin Li    @mock.patch.object(shutil, "copyfile")
703*760c253cSXin Li    @mock.patch.object(subprocess, "check_call")
704*760c253cSXin Li    def test_create_rust_host_ebuild(self, mock_call, mock_copy):
705*760c253cSXin Li        template_ebuild = (
706*760c253cSXin Li            rust_uprev.EBUILD_PREFIX
707*760c253cSXin Li            / f"dev-lang/rust-host/rust-host-{self.current_version}.ebuild"
708*760c253cSXin Li        )
709*760c253cSXin Li        new_ebuild = (
710*760c253cSXin Li            rust_uprev.EBUILD_PREFIX
711*760c253cSXin Li            / f"dev-lang/rust-host/rust-host-{self.new_version}.ebuild"
712*760c253cSXin Li        )
713*760c253cSXin Li        rust_uprev.create_ebuild(
714*760c253cSXin Li            "dev-lang", "rust-host", self.current_version, self.new_version
715*760c253cSXin Li        )
716*760c253cSXin Li        mock_copy.assert_called_once_with(
717*760c253cSXin Li            template_ebuild,
718*760c253cSXin Li            new_ebuild,
719*760c253cSXin Li        )
720*760c253cSXin Li        mock_call.assert_called_once_with(
721*760c253cSXin Li            ["git", "add", f"rust-host-{self.new_version}.ebuild"],
722*760c253cSXin Li            cwd=new_ebuild.parent,
723*760c253cSXin Li        )
724*760c253cSXin Li
725*760c253cSXin Li    @mock.patch.object(subprocess, "check_call")
726*760c253cSXin Li    def test_remove_virtual_rust(self, mock_call):
727*760c253cSXin Li        with tempfile.TemporaryDirectory() as tmpdir:
728*760c253cSXin Li            ebuild_path = Path(
729*760c253cSXin Li                tmpdir, f"virtual/rust/rust-{self.old_version}.ebuild"
730*760c253cSXin Li            )
731*760c253cSXin Li            os.makedirs(ebuild_path.parent)
732*760c253cSXin Li            ebuild_path.touch()
733*760c253cSXin Li            with mock.patch("rust_uprev.EBUILD_PREFIX", Path(tmpdir)):
734*760c253cSXin Li                rust_uprev.remove_virtual_rust(self.old_version)
735*760c253cSXin Li                mock_call.assert_called_once_with(
736*760c253cSXin Li                    ["git", "rm", str(ebuild_path.name)], cwd=ebuild_path.parent
737*760c253cSXin Li                )
738*760c253cSXin Li
739*760c253cSXin Li    @mock.patch.object(subprocess, "check_call")
740*760c253cSXin Li    def test_remove_virtual_rust_with_symlink(self, mock_call):
741*760c253cSXin Li        with tempfile.TemporaryDirectory() as tmpdir:
742*760c253cSXin Li            ebuild_path = Path(
743*760c253cSXin Li                tmpdir, f"virtual/rust/rust-{self.old_version}.ebuild"
744*760c253cSXin Li            )
745*760c253cSXin Li            symlink_path = Path(
746*760c253cSXin Li                tmpdir, f"virtual/rust/rust-{self.old_version}-r14.ebuild"
747*760c253cSXin Li            )
748*760c253cSXin Li            os.makedirs(ebuild_path.parent)
749*760c253cSXin Li            ebuild_path.touch()
750*760c253cSXin Li            symlink_path.symlink_to(ebuild_path.name)
751*760c253cSXin Li            with mock.patch("rust_uprev.EBUILD_PREFIX", Path(tmpdir)):
752*760c253cSXin Li                rust_uprev.remove_virtual_rust(self.old_version)
753*760c253cSXin Li                mock_call.assert_has_calls(
754*760c253cSXin Li                    [
755*760c253cSXin Li                        mock.call(
756*760c253cSXin Li                            ["git", "rm", ebuild_path.name],
757*760c253cSXin Li                            cwd=ebuild_path.parent,
758*760c253cSXin Li                        ),
759*760c253cSXin Li                        mock.call(
760*760c253cSXin Li                            ["git", "rm", symlink_path.name],
761*760c253cSXin Li                            cwd=ebuild_path.parent,
762*760c253cSXin Li                        ),
763*760c253cSXin Li                    ],
764*760c253cSXin Li                    any_order=True,
765*760c253cSXin Li                )
766*760c253cSXin Li
767*760c253cSXin Li    @mock.patch.object(rust_uprev, "find_ebuild_path")
768*760c253cSXin Li    @mock.patch.object(shutil, "copyfile")
769*760c253cSXin Li    @mock.patch.object(subprocess, "check_call")
770*760c253cSXin Li    def test_update_virtual_rust(self, mock_call, mock_copy, mock_find_ebuild):
771*760c253cSXin Li        ebuild_path = Path(
772*760c253cSXin Li            f"/some/dir/virtual/rust/rust-{self.current_version}.ebuild"
773*760c253cSXin Li        )
774*760c253cSXin Li        mock_find_ebuild.return_value = Path(ebuild_path)
775*760c253cSXin Li        rust_uprev.update_virtual_rust(self.current_version, self.new_version)
776*760c253cSXin Li        mock_call.assert_called_once_with(
777*760c253cSXin Li            ["git", "add", f"rust-{self.new_version}.ebuild"],
778*760c253cSXin Li            cwd=ebuild_path.parent,
779*760c253cSXin Li        )
780*760c253cSXin Li        mock_copy.assert_called_once_with(
781*760c253cSXin Li            ebuild_path.parent.joinpath(f"rust-{self.current_version}.ebuild"),
782*760c253cSXin Li            ebuild_path.parent.joinpath(f"rust-{self.new_version}.ebuild"),
783*760c253cSXin Li        )
784*760c253cSXin Li
785*760c253cSXin Li    @mock.patch("rust_uprev.find_rust_versions")
786*760c253cSXin Li    def test_find_oldest_rust_version_pass(self, rust_versions):
787*760c253cSXin Li        oldest_version_name = f"rust-{self.old_version}.ebuild"
788*760c253cSXin Li        rust_versions.return_value = [
789*760c253cSXin Li            (self.old_version, oldest_version_name),
790*760c253cSXin Li            (self.current_version, f"rust-{self.current_version}.ebuild"),
791*760c253cSXin Li            (self.new_version, f"rust-{self.new_version}.ebuild"),
792*760c253cSXin Li        ]
793*760c253cSXin Li        actual = rust_uprev.find_oldest_rust_version()
794*760c253cSXin Li        expected = self.old_version
795*760c253cSXin Li        self.assertEqual(expected, actual)
796*760c253cSXin Li
797*760c253cSXin Li    @mock.patch("rust_uprev.find_rust_versions")
798*760c253cSXin Li    def test_find_oldest_rust_version_fail_with_only_one_ebuild(
799*760c253cSXin Li        self, rust_versions
800*760c253cSXin Li    ):
801*760c253cSXin Li        rust_versions.return_value = [
802*760c253cSXin Li            (self.new_version, f"rust-{self.new_version}.ebuild"),
803*760c253cSXin Li        ]
804*760c253cSXin Li        with self.assertRaises(RuntimeError) as context:
805*760c253cSXin Li            rust_uprev.find_oldest_rust_version()
806*760c253cSXin Li        self.assertEqual(
807*760c253cSXin Li            "Expect to find more than one Rust versions", str(context.exception)
808*760c253cSXin Li        )
809*760c253cSXin Li
810*760c253cSXin Li    @mock.patch.object(rust_uprev, "get_command_output")
811*760c253cSXin Li    @mock.patch.object(git, "CreateBranch")
812*760c253cSXin Li    def test_create_new_repo(self, mock_branch, mock_output):
813*760c253cSXin Li        mock_output.return_value = ""
814*760c253cSXin Li        rust_uprev.create_new_repo(self.new_version)
815*760c253cSXin Li        mock_branch.assert_called_once_with(
816*760c253cSXin Li            rust_uprev.EBUILD_PREFIX, f"rust-to-{self.new_version}"
817*760c253cSXin Li        )
818*760c253cSXin Li
819*760c253cSXin Li    @mock.patch.object(rust_uprev, "run_in_chroot")
820*760c253cSXin Li    def test_build_cross_compiler(self, mock_run_in_chroot):
821*760c253cSXin Li        cros_targets = [
822*760c253cSXin Li            "x86_64-cros-linux-gnu",
823*760c253cSXin Li            "armv7a-cros-linux-gnueabihf",
824*760c253cSXin Li            "aarch64-cros-linux-gnu",
825*760c253cSXin Li        ]
826*760c253cSXin Li        all_triples = ["x86_64-pc-linux-gnu"] + cros_targets
827*760c253cSXin Li        rust_ebuild = "RUSTC_TARGET_TRIPLES=(" + "\n\t".join(all_triples) + ")"
828*760c253cSXin Li        with mock.patch("rust_uprev.find_ebuild_path") as mock_find_ebuild_path:
829*760c253cSXin Li            mock_path = mock.Mock()
830*760c253cSXin Li            mock_path.read_text.return_value = rust_ebuild
831*760c253cSXin Li            mock_find_ebuild_path.return_value = mock_path
832*760c253cSXin Li            rust_uprev.build_cross_compiler(rust_uprev.RustVersion(7, 3, 31))
833*760c253cSXin Li
834*760c253cSXin Li        mock_run_in_chroot.assert_called_once_with(
835*760c253cSXin Li            ["sudo", "emerge", "-j", "-G"]
836*760c253cSXin Li            + [f"cross-{x}/gcc" for x in cros_targets + ["arm-none-eabi"]]
837*760c253cSXin Li        )
838*760c253cSXin Li
839*760c253cSXin Li
840*760c253cSXin Liif __name__ == "__main__":
841*760c253cSXin Li    unittest.main()
842