xref: /aosp_15_r20/external/toolchain-utils/llvm_tools/llvm_bisection_unittest.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li#!/usr/bin/env python3
2*760c253cSXin Li# Copyright 2019 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# pylint: disable=protected-access
7*760c253cSXin Li
8*760c253cSXin Li"""Tests for LLVM bisection."""
9*760c253cSXin Li
10*760c253cSXin Liimport json
11*760c253cSXin Liimport os
12*760c253cSXin Liimport subprocess
13*760c253cSXin Liimport unittest
14*760c253cSXin Lifrom unittest import mock
15*760c253cSXin Li
16*760c253cSXin Liimport chroot
17*760c253cSXin Liimport get_llvm_hash
18*760c253cSXin Liimport git_llvm_rev
19*760c253cSXin Liimport llvm_bisection
20*760c253cSXin Liimport modify_a_tryjob
21*760c253cSXin Liimport test_helpers
22*760c253cSXin Li
23*760c253cSXin Li
24*760c253cSXin Liclass LLVMBisectionTest(unittest.TestCase):
25*760c253cSXin Li    """Unittests for LLVM bisection."""
26*760c253cSXin Li
27*760c253cSXin Li    def testGetRemainingRangePassed(self):
28*760c253cSXin Li        start = 100
29*760c253cSXin Li        end = 150
30*760c253cSXin Li
31*760c253cSXin Li        test_tryjobs = [
32*760c253cSXin Li            {
33*760c253cSXin Li                "rev": 110,
34*760c253cSXin Li                "status": "good",
35*760c253cSXin Li                "link": "https://some_tryjob_1_url.com",
36*760c253cSXin Li            },
37*760c253cSXin Li            {
38*760c253cSXin Li                "rev": 120,
39*760c253cSXin Li                "status": "good",
40*760c253cSXin Li                "link": "https://some_tryjob_2_url.com",
41*760c253cSXin Li            },
42*760c253cSXin Li            {
43*760c253cSXin Li                "rev": 130,
44*760c253cSXin Li                "status": "pending",
45*760c253cSXin Li                "link": "https://some_tryjob_3_url.com",
46*760c253cSXin Li            },
47*760c253cSXin Li            {
48*760c253cSXin Li                "rev": 135,
49*760c253cSXin Li                "status": "skip",
50*760c253cSXin Li                "link": "https://some_tryjob_4_url.com",
51*760c253cSXin Li            },
52*760c253cSXin Li            {
53*760c253cSXin Li                "rev": 140,
54*760c253cSXin Li                "status": "bad",
55*760c253cSXin Li                "link": "https://some_tryjob_5_url.com",
56*760c253cSXin Li            },
57*760c253cSXin Li        ]
58*760c253cSXin Li
59*760c253cSXin Li        # Tuple consists of the new good revision, the new bad revision, a set
60*760c253cSXin Li        # of 'pending' revisions, and a set of 'skip' revisions.
61*760c253cSXin Li        expected_revisions_tuple = 120, 140, {130}, {135}
62*760c253cSXin Li
63*760c253cSXin Li        self.assertEqual(
64*760c253cSXin Li            llvm_bisection.GetRemainingRange(start, end, test_tryjobs),
65*760c253cSXin Li            expected_revisions_tuple,
66*760c253cSXin Li        )
67*760c253cSXin Li
68*760c253cSXin Li    def testGetRemainingRangeFailedWithMissingStatus(self):
69*760c253cSXin Li        start = 100
70*760c253cSXin Li        end = 150
71*760c253cSXin Li
72*760c253cSXin Li        test_tryjobs = [
73*760c253cSXin Li            {
74*760c253cSXin Li                "rev": 105,
75*760c253cSXin Li                "status": "good",
76*760c253cSXin Li                "link": "https://some_tryjob_1_url.com",
77*760c253cSXin Li            },
78*760c253cSXin Li            {
79*760c253cSXin Li                "rev": 120,
80*760c253cSXin Li                "status": None,
81*760c253cSXin Li                "link": "https://some_tryjob_2_url.com",
82*760c253cSXin Li            },
83*760c253cSXin Li            {
84*760c253cSXin Li                "rev": 140,
85*760c253cSXin Li                "status": "bad",
86*760c253cSXin Li                "link": "https://some_tryjob_3_url.com",
87*760c253cSXin Li            },
88*760c253cSXin Li        ]
89*760c253cSXin Li
90*760c253cSXin Li        with self.assertRaises(ValueError) as err:
91*760c253cSXin Li            llvm_bisection.GetRemainingRange(start, end, test_tryjobs)
92*760c253cSXin Li
93*760c253cSXin Li        error_message = (
94*760c253cSXin Li            '"status" is missing or has no value, please '
95*760c253cSXin Li            "go to %s and update it" % test_tryjobs[1]["link"]
96*760c253cSXin Li        )
97*760c253cSXin Li        self.assertEqual(str(err.exception), error_message)
98*760c253cSXin Li
99*760c253cSXin Li    def testGetRemainingRangeFailedWithInvalidRange(self):
100*760c253cSXin Li        start = 100
101*760c253cSXin Li        end = 150
102*760c253cSXin Li
103*760c253cSXin Li        test_tryjobs = [
104*760c253cSXin Li            {
105*760c253cSXin Li                "rev": 110,
106*760c253cSXin Li                "status": "bad",
107*760c253cSXin Li                "link": "https://some_tryjob_1_url.com",
108*760c253cSXin Li            },
109*760c253cSXin Li            {
110*760c253cSXin Li                "rev": 125,
111*760c253cSXin Li                "status": "skip",
112*760c253cSXin Li                "link": "https://some_tryjob_2_url.com",
113*760c253cSXin Li            },
114*760c253cSXin Li            {
115*760c253cSXin Li                "rev": 140,
116*760c253cSXin Li                "status": "good",
117*760c253cSXin Li                "link": "https://some_tryjob_3_url.com",
118*760c253cSXin Li            },
119*760c253cSXin Li        ]
120*760c253cSXin Li
121*760c253cSXin Li        with self.assertRaises(AssertionError) as err:
122*760c253cSXin Li            llvm_bisection.GetRemainingRange(start, end, test_tryjobs)
123*760c253cSXin Li
124*760c253cSXin Li        expected_error_message = (
125*760c253cSXin Li            "Bisection is broken because %d (good) is >= "
126*760c253cSXin Li            "%d (bad)" % (test_tryjobs[2]["rev"], test_tryjobs[0]["rev"])
127*760c253cSXin Li        )
128*760c253cSXin Li
129*760c253cSXin Li        self.assertEqual(str(err.exception), expected_error_message)
130*760c253cSXin Li
131*760c253cSXin Li    @mock.patch.object(get_llvm_hash, "GetGitHashFrom")
132*760c253cSXin Li    def testGetCommitsBetweenPassed(self, mock_get_git_hash):
133*760c253cSXin Li        start = git_llvm_rev.base_llvm_revision
134*760c253cSXin Li        end = start + 10
135*760c253cSXin Li        test_pending_revisions = {start + 7}
136*760c253cSXin Li        test_skip_revisions = {
137*760c253cSXin Li            start + 1,
138*760c253cSXin Li            start + 2,
139*760c253cSXin Li            start + 4,
140*760c253cSXin Li            start + 8,
141*760c253cSXin Li            start + 9,
142*760c253cSXin Li        }
143*760c253cSXin Li        parallel = 3
144*760c253cSXin Li        abs_path_to_src = "/abs/path/to/src"
145*760c253cSXin Li
146*760c253cSXin Li        revs = ["a123testhash3", "a123testhash5"]
147*760c253cSXin Li        mock_get_git_hash.side_effect = revs
148*760c253cSXin Li
149*760c253cSXin Li        git_hashes = [
150*760c253cSXin Li            git_llvm_rev.base_llvm_revision + 3,
151*760c253cSXin Li            git_llvm_rev.base_llvm_revision + 5,
152*760c253cSXin Li        ]
153*760c253cSXin Li
154*760c253cSXin Li        self.assertEqual(
155*760c253cSXin Li            llvm_bisection.GetCommitsBetween(
156*760c253cSXin Li                start,
157*760c253cSXin Li                end,
158*760c253cSXin Li                parallel,
159*760c253cSXin Li                abs_path_to_src,
160*760c253cSXin Li                test_pending_revisions,
161*760c253cSXin Li                test_skip_revisions,
162*760c253cSXin Li            ),
163*760c253cSXin Li            (git_hashes, revs),
164*760c253cSXin Li        )
165*760c253cSXin Li
166*760c253cSXin Li    def testLoadStatusFilePassedWithExistingFile(self):
167*760c253cSXin Li        start = 100
168*760c253cSXin Li        end = 150
169*760c253cSXin Li
170*760c253cSXin Li        test_bisect_state = {"start": start, "end": end, "jobs": []}
171*760c253cSXin Li
172*760c253cSXin Li        # Simulate that the status file exists.
173*760c253cSXin Li        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
174*760c253cSXin Li            with open(temp_json_file, "w", encoding="utf-8") as f:
175*760c253cSXin Li                test_helpers.WritePrettyJsonFile(test_bisect_state, f)
176*760c253cSXin Li
177*760c253cSXin Li            self.assertEqual(
178*760c253cSXin Li                llvm_bisection.LoadStatusFile(temp_json_file, start, end),
179*760c253cSXin Li                test_bisect_state,
180*760c253cSXin Li            )
181*760c253cSXin Li
182*760c253cSXin Li    def testLoadStatusFilePassedWithoutExistingFile(self):
183*760c253cSXin Li        start = 200
184*760c253cSXin Li        end = 250
185*760c253cSXin Li
186*760c253cSXin Li        expected_bisect_state = {"start": start, "end": end, "jobs": []}
187*760c253cSXin Li
188*760c253cSXin Li        last_tested = "/abs/path/to/file_that_does_not_exist.json"
189*760c253cSXin Li
190*760c253cSXin Li        self.assertEqual(
191*760c253cSXin Li            llvm_bisection.LoadStatusFile(last_tested, start, end),
192*760c253cSXin Li            expected_bisect_state,
193*760c253cSXin Li        )
194*760c253cSXin Li
195*760c253cSXin Li    @mock.patch.object(modify_a_tryjob, "AddTryjob")
196*760c253cSXin Li    def testBisectPassed(self, mock_add_tryjob):
197*760c253cSXin Li        git_hash_list = ["a123testhash1", "a123testhash2", "a123testhash3"]
198*760c253cSXin Li        revisions_list = [102, 104, 106]
199*760c253cSXin Li
200*760c253cSXin Li        # Simulate behavior of `AddTryjob()` when successfully launched a
201*760c253cSXin Li        # tryjob for the updated packages.
202*760c253cSXin Li        @test_helpers.CallCountsToMockFunctions
203*760c253cSXin Li        def MockAddTryjob(
204*760c253cSXin Li            call_count,
205*760c253cSXin Li            _packages,
206*760c253cSXin Li            _git_hash,
207*760c253cSXin Li            _revision,
208*760c253cSXin Li            _chroot_path,
209*760c253cSXin Li            _extra_cls,
210*760c253cSXin Li            _options,
211*760c253cSXin Li            _builder,
212*760c253cSXin Li            _svn_revision,
213*760c253cSXin Li        ):
214*760c253cSXin Li            if call_count < 2:
215*760c253cSXin Li                return {"rev": revisions_list[call_count], "status": "pending"}
216*760c253cSXin Li
217*760c253cSXin Li            # Simulate an exception happened along the way when updating the
218*760c253cSXin Li            # packages' `LLVM_NEXT_HASH`.
219*760c253cSXin Li            if call_count == 2:
220*760c253cSXin Li                raise ValueError("Unable to launch tryjob")
221*760c253cSXin Li
222*760c253cSXin Li            assert False, "Called `AddTryjob()` more than expected."
223*760c253cSXin Li
224*760c253cSXin Li        # Use the test function to simulate `AddTryjob()`.
225*760c253cSXin Li        mock_add_tryjob.side_effect = MockAddTryjob
226*760c253cSXin Li
227*760c253cSXin Li        start = 100
228*760c253cSXin Li        end = 110
229*760c253cSXin Li
230*760c253cSXin Li        bisection_contents = {"start": start, "end": end, "jobs": []}
231*760c253cSXin Li
232*760c253cSXin Li        args_output = test_helpers.ArgsOutputTest()
233*760c253cSXin Li
234*760c253cSXin Li        packages = ["sys-devel/llvm"]
235*760c253cSXin Li
236*760c253cSXin Li        # Create a temporary .JSON file to simulate a status file for bisection.
237*760c253cSXin Li        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
238*760c253cSXin Li            with open(temp_json_file, "w", encoding="utf-8") as f:
239*760c253cSXin Li                test_helpers.WritePrettyJsonFile(bisection_contents, f)
240*760c253cSXin Li
241*760c253cSXin Li            # Verify that the status file is updated when an exception happened
242*760c253cSXin Li            # when attempting to launch a revision (i.e. progress is not lost).
243*760c253cSXin Li            with self.assertRaises(ValueError) as err:
244*760c253cSXin Li                llvm_bisection.Bisect(
245*760c253cSXin Li                    revisions_list,
246*760c253cSXin Li                    git_hash_list,
247*760c253cSXin Li                    bisection_contents,
248*760c253cSXin Li                    temp_json_file,
249*760c253cSXin Li                    packages,
250*760c253cSXin Li                    args_output.chromeos_path,
251*760c253cSXin Li                    args_output.extra_change_lists,
252*760c253cSXin Li                    args_output.options,
253*760c253cSXin Li                    args_output.builders,
254*760c253cSXin Li                )
255*760c253cSXin Li
256*760c253cSXin Li            expected_bisection_contents = {
257*760c253cSXin Li                "start": start,
258*760c253cSXin Li                "end": end,
259*760c253cSXin Li                "jobs": [
260*760c253cSXin Li                    {"rev": revisions_list[0], "status": "pending"},
261*760c253cSXin Li                    {"rev": revisions_list[1], "status": "pending"},
262*760c253cSXin Li                ],
263*760c253cSXin Li            }
264*760c253cSXin Li
265*760c253cSXin Li            # Verify that the launched tryjobs were added to the status file
266*760c253cSXin Li            # when an exception happened.
267*760c253cSXin Li            with open(temp_json_file, encoding="utf-8") as f:
268*760c253cSXin Li                json_contents = json.load(f)
269*760c253cSXin Li
270*760c253cSXin Li                self.assertEqual(json_contents, expected_bisection_contents)
271*760c253cSXin Li
272*760c253cSXin Li        self.assertEqual(str(err.exception), "Unable to launch tryjob")
273*760c253cSXin Li
274*760c253cSXin Li        self.assertEqual(mock_add_tryjob.call_count, 3)
275*760c253cSXin Li
276*760c253cSXin Li    @mock.patch.object(subprocess, "check_output", return_value=None)
277*760c253cSXin Li    @mock.patch.object(
278*760c253cSXin Li        get_llvm_hash.LLVMHash, "GetLLVMHash", return_value="a123testhash4"
279*760c253cSXin Li    )
280*760c253cSXin Li    @mock.patch.object(llvm_bisection, "GetCommitsBetween")
281*760c253cSXin Li    @mock.patch.object(llvm_bisection, "GetRemainingRange")
282*760c253cSXin Li    @mock.patch.object(llvm_bisection, "LoadStatusFile")
283*760c253cSXin Li    @mock.patch.object(chroot, "VerifyChromeOSRoot")
284*760c253cSXin Li    @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True)
285*760c253cSXin Li    def testMainPassed(
286*760c253cSXin Li        self,
287*760c253cSXin Li        mock_outside_chroot,
288*760c253cSXin Li        mock_chromeos_root,
289*760c253cSXin Li        mock_load_status_file,
290*760c253cSXin Li        mock_get_range,
291*760c253cSXin Li        mock_get_revision_and_hash_list,
292*760c253cSXin Li        _mock_get_bad_llvm_hash,
293*760c253cSXin Li        mock_abandon_cl,
294*760c253cSXin Li    ):
295*760c253cSXin Li        start = 500
296*760c253cSXin Li        end = 502
297*760c253cSXin Li        cl = 1
298*760c253cSXin Li
299*760c253cSXin Li        bisect_state = {
300*760c253cSXin Li            "start": start,
301*760c253cSXin Li            "end": end,
302*760c253cSXin Li            "jobs": [{"rev": 501, "status": "bad", "cl": cl}],
303*760c253cSXin Li        }
304*760c253cSXin Li
305*760c253cSXin Li        skip_revisions = {501}
306*760c253cSXin Li        pending_revisions = {}
307*760c253cSXin Li
308*760c253cSXin Li        mock_load_status_file.return_value = bisect_state
309*760c253cSXin Li
310*760c253cSXin Li        mock_get_range.return_value = (
311*760c253cSXin Li            start,
312*760c253cSXin Li            end,
313*760c253cSXin Li            pending_revisions,
314*760c253cSXin Li            skip_revisions,
315*760c253cSXin Li        )
316*760c253cSXin Li
317*760c253cSXin Li        mock_get_revision_and_hash_list.return_value = [], []
318*760c253cSXin Li
319*760c253cSXin Li        args_output = test_helpers.ArgsOutputTest()
320*760c253cSXin Li        args_output.start_rev = start
321*760c253cSXin Li        args_output.end_rev = end
322*760c253cSXin Li        args_output.parallel = 3
323*760c253cSXin Li        args_output.src_path = None
324*760c253cSXin Li        args_output.chromeos_path = "somepath"
325*760c253cSXin Li        args_output.cleanup = True
326*760c253cSXin Li
327*760c253cSXin Li        self.assertEqual(
328*760c253cSXin Li            llvm_bisection.main(args_output),
329*760c253cSXin Li            llvm_bisection.BisectionExitStatus.BISECTION_COMPLETE.value,
330*760c253cSXin Li        )
331*760c253cSXin Li
332*760c253cSXin Li        mock_chromeos_root.assert_called_once()
333*760c253cSXin Li
334*760c253cSXin Li        mock_outside_chroot.assert_called_once()
335*760c253cSXin Li
336*760c253cSXin Li        mock_load_status_file.assert_called_once()
337*760c253cSXin Li
338*760c253cSXin Li        mock_get_range.assert_called_once()
339*760c253cSXin Li
340*760c253cSXin Li        mock_get_revision_and_hash_list.assert_called_once()
341*760c253cSXin Li
342*760c253cSXin Li        mock_abandon_cl.assert_called_once()
343*760c253cSXin Li        self.assertEqual(
344*760c253cSXin Li            mock_abandon_cl.call_args,
345*760c253cSXin Li            mock.call(
346*760c253cSXin Li                [
347*760c253cSXin Li                    os.path.join(
348*760c253cSXin Li                        args_output.chromeos_path, "chromite/bin/gerrit"
349*760c253cSXin Li                    ),
350*760c253cSXin Li                    "abandon",
351*760c253cSXin Li                    str(cl),
352*760c253cSXin Li                ],
353*760c253cSXin Li                stderr=subprocess.STDOUT,
354*760c253cSXin Li                encoding="utf-8",
355*760c253cSXin Li            ),
356*760c253cSXin Li        )
357*760c253cSXin Li
358*760c253cSXin Li    @mock.patch.object(llvm_bisection, "LoadStatusFile")
359*760c253cSXin Li    @mock.patch.object(chroot, "VerifyChromeOSRoot")
360*760c253cSXin Li    @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True)
361*760c253cSXin Li    def testMainFailedWithInvalidRange(
362*760c253cSXin Li        self, mock_chromeos_root, mock_outside_chroot, mock_load_status_file
363*760c253cSXin Li    ):
364*760c253cSXin Li        start = 500
365*760c253cSXin Li        end = 502
366*760c253cSXin Li
367*760c253cSXin Li        bisect_state = {
368*760c253cSXin Li            "start": start - 1,
369*760c253cSXin Li            "end": end,
370*760c253cSXin Li        }
371*760c253cSXin Li
372*760c253cSXin Li        mock_load_status_file.return_value = bisect_state
373*760c253cSXin Li
374*760c253cSXin Li        args_output = test_helpers.ArgsOutputTest()
375*760c253cSXin Li        args_output.start_rev = start
376*760c253cSXin Li        args_output.end_rev = end
377*760c253cSXin Li        args_output.parallel = 3
378*760c253cSXin Li        args_output.src_path = None
379*760c253cSXin Li
380*760c253cSXin Li        with self.assertRaises(ValueError) as err:
381*760c253cSXin Li            llvm_bisection.main(args_output)
382*760c253cSXin Li
383*760c253cSXin Li        error_message = (
384*760c253cSXin Li            f"The start {start} or the end {end} version provided is "
385*760c253cSXin Li            f'different than "start" {bisect_state["start"]} or "end" '
386*760c253cSXin Li            f'{bisect_state["end"]} in the .JSON file'
387*760c253cSXin Li        )
388*760c253cSXin Li
389*760c253cSXin Li        self.assertEqual(str(err.exception), error_message)
390*760c253cSXin Li
391*760c253cSXin Li        mock_chromeos_root.assert_called_once()
392*760c253cSXin Li
393*760c253cSXin Li        mock_outside_chroot.assert_called_once()
394*760c253cSXin Li
395*760c253cSXin Li        mock_load_status_file.assert_called_once()
396*760c253cSXin Li
397*760c253cSXin Li    @mock.patch.object(llvm_bisection, "GetCommitsBetween")
398*760c253cSXin Li    @mock.patch.object(llvm_bisection, "GetRemainingRange")
399*760c253cSXin Li    @mock.patch.object(llvm_bisection, "LoadStatusFile")
400*760c253cSXin Li    @mock.patch.object(chroot, "VerifyChromeOSRoot")
401*760c253cSXin Li    @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True)
402*760c253cSXin Li    def testMainFailedWithPendingBuilds(
403*760c253cSXin Li        self,
404*760c253cSXin Li        mock_chromeos_root,
405*760c253cSXin Li        mock_outside_chroot,
406*760c253cSXin Li        mock_load_status_file,
407*760c253cSXin Li        mock_get_range,
408*760c253cSXin Li        mock_get_revision_and_hash_list,
409*760c253cSXin Li    ):
410*760c253cSXin Li        start = 500
411*760c253cSXin Li        end = 502
412*760c253cSXin Li        rev = 501
413*760c253cSXin Li
414*760c253cSXin Li        bisect_state = {
415*760c253cSXin Li            "start": start,
416*760c253cSXin Li            "end": end,
417*760c253cSXin Li            "jobs": [{"rev": rev, "status": "pending"}],
418*760c253cSXin Li        }
419*760c253cSXin Li
420*760c253cSXin Li        skip_revisions = {}
421*760c253cSXin Li        pending_revisions = {rev}
422*760c253cSXin Li
423*760c253cSXin Li        mock_load_status_file.return_value = bisect_state
424*760c253cSXin Li
425*760c253cSXin Li        mock_get_range.return_value = (
426*760c253cSXin Li            start,
427*760c253cSXin Li            end,
428*760c253cSXin Li            pending_revisions,
429*760c253cSXin Li            skip_revisions,
430*760c253cSXin Li        )
431*760c253cSXin Li
432*760c253cSXin Li        mock_get_revision_and_hash_list.return_value = [], []
433*760c253cSXin Li
434*760c253cSXin Li        args_output = test_helpers.ArgsOutputTest()
435*760c253cSXin Li        args_output.start_rev = start
436*760c253cSXin Li        args_output.end_rev = end
437*760c253cSXin Li        args_output.parallel = 3
438*760c253cSXin Li        args_output.src_path = None
439*760c253cSXin Li
440*760c253cSXin Li        with self.assertRaises(ValueError) as err:
441*760c253cSXin Li            llvm_bisection.main(args_output)
442*760c253cSXin Li
443*760c253cSXin Li        error_message = (
444*760c253cSXin Li            f"No revisions between start {start} and end {end} to "
445*760c253cSXin Li            "create tryjobs\nThe following tryjobs are pending:\n"
446*760c253cSXin Li            f"{rev}\n"
447*760c253cSXin Li        )
448*760c253cSXin Li
449*760c253cSXin Li        self.assertEqual(str(err.exception), error_message)
450*760c253cSXin Li
451*760c253cSXin Li        mock_chromeos_root.assert_called_once()
452*760c253cSXin Li
453*760c253cSXin Li        mock_outside_chroot.assert_called_once()
454*760c253cSXin Li
455*760c253cSXin Li        mock_load_status_file.assert_called_once()
456*760c253cSXin Li
457*760c253cSXin Li        mock_get_range.assert_called_once()
458*760c253cSXin Li
459*760c253cSXin Li        mock_get_revision_and_hash_list.assert_called_once()
460*760c253cSXin Li
461*760c253cSXin Li    @mock.patch.object(llvm_bisection, "GetCommitsBetween")
462*760c253cSXin Li    @mock.patch.object(llvm_bisection, "GetRemainingRange")
463*760c253cSXin Li    @mock.patch.object(llvm_bisection, "LoadStatusFile")
464*760c253cSXin Li    @mock.patch.object(chroot, "VerifyChromeOSRoot")
465*760c253cSXin Li    @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True)
466*760c253cSXin Li    def testMainFailedWithDuplicateBuilds(
467*760c253cSXin Li        self,
468*760c253cSXin Li        mock_outside_chroot,
469*760c253cSXin Li        mock_chromeos_root,
470*760c253cSXin Li        mock_load_status_file,
471*760c253cSXin Li        mock_get_range,
472*760c253cSXin Li        mock_get_revision_and_hash_list,
473*760c253cSXin Li    ):
474*760c253cSXin Li        start = 500
475*760c253cSXin Li        end = 502
476*760c253cSXin Li        rev = 501
477*760c253cSXin Li        git_hash = "a123testhash1"
478*760c253cSXin Li
479*760c253cSXin Li        bisect_state = {
480*760c253cSXin Li            "start": start,
481*760c253cSXin Li            "end": end,
482*760c253cSXin Li            "jobs": [{"rev": rev, "status": "pending"}],
483*760c253cSXin Li        }
484*760c253cSXin Li
485*760c253cSXin Li        skip_revisions = {}
486*760c253cSXin Li        pending_revisions = {rev}
487*760c253cSXin Li
488*760c253cSXin Li        mock_load_status_file.return_value = bisect_state
489*760c253cSXin Li
490*760c253cSXin Li        mock_get_range.return_value = (
491*760c253cSXin Li            start,
492*760c253cSXin Li            end,
493*760c253cSXin Li            pending_revisions,
494*760c253cSXin Li            skip_revisions,
495*760c253cSXin Li        )
496*760c253cSXin Li
497*760c253cSXin Li        mock_get_revision_and_hash_list.return_value = [rev], [git_hash]
498*760c253cSXin Li
499*760c253cSXin Li        args_output = test_helpers.ArgsOutputTest()
500*760c253cSXin Li        args_output.start_rev = start
501*760c253cSXin Li        args_output.end_rev = end
502*760c253cSXin Li        args_output.parallel = 3
503*760c253cSXin Li        args_output.src_path = None
504*760c253cSXin Li
505*760c253cSXin Li        with self.assertRaises(ValueError) as err:
506*760c253cSXin Li            llvm_bisection.main(args_output)
507*760c253cSXin Li
508*760c253cSXin Li        error_message = 'Revision %d exists already in "jobs"' % rev
509*760c253cSXin Li        self.assertEqual(str(err.exception), error_message)
510*760c253cSXin Li
511*760c253cSXin Li        mock_chromeos_root.assert_called_once()
512*760c253cSXin Li
513*760c253cSXin Li        mock_outside_chroot.assert_called_once()
514*760c253cSXin Li
515*760c253cSXin Li        mock_load_status_file.assert_called_once()
516*760c253cSXin Li
517*760c253cSXin Li        mock_get_range.assert_called_once()
518*760c253cSXin Li
519*760c253cSXin Li        mock_get_revision_and_hash_list.assert_called_once()
520*760c253cSXin Li
521*760c253cSXin Li    @mock.patch.object(subprocess, "check_output", return_value=None)
522*760c253cSXin Li    @mock.patch.object(
523*760c253cSXin Li        get_llvm_hash.LLVMHash, "GetLLVMHash", return_value="a123testhash4"
524*760c253cSXin Li    )
525*760c253cSXin Li    @mock.patch.object(llvm_bisection, "GetCommitsBetween")
526*760c253cSXin Li    @mock.patch.object(llvm_bisection, "GetRemainingRange")
527*760c253cSXin Li    @mock.patch.object(llvm_bisection, "LoadStatusFile")
528*760c253cSXin Li    @mock.patch.object(chroot, "VerifyChromeOSRoot")
529*760c253cSXin Li    @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True)
530*760c253cSXin Li    def testMainFailedToAbandonCL(
531*760c253cSXin Li        self,
532*760c253cSXin Li        mock_outside_chroot,
533*760c253cSXin Li        mock_chromeos_root,
534*760c253cSXin Li        mock_load_status_file,
535*760c253cSXin Li        mock_get_range,
536*760c253cSXin Li        mock_get_revision_and_hash_list,
537*760c253cSXin Li        _mock_get_bad_llvm_hash,
538*760c253cSXin Li        mock_abandon_cl,
539*760c253cSXin Li    ):
540*760c253cSXin Li        start = 500
541*760c253cSXin Li        end = 502
542*760c253cSXin Li
543*760c253cSXin Li        bisect_state = {
544*760c253cSXin Li            "start": start,
545*760c253cSXin Li            "end": end,
546*760c253cSXin Li            "jobs": [{"rev": 501, "status": "bad", "cl": 0}],
547*760c253cSXin Li        }
548*760c253cSXin Li
549*760c253cSXin Li        skip_revisions = {501}
550*760c253cSXin Li        pending_revisions = {}
551*760c253cSXin Li
552*760c253cSXin Li        mock_load_status_file.return_value = bisect_state
553*760c253cSXin Li
554*760c253cSXin Li        mock_get_range.return_value = (
555*760c253cSXin Li            start,
556*760c253cSXin Li            end,
557*760c253cSXin Li            pending_revisions,
558*760c253cSXin Li            skip_revisions,
559*760c253cSXin Li        )
560*760c253cSXin Li
561*760c253cSXin Li        mock_get_revision_and_hash_list.return_value = ([], [])
562*760c253cSXin Li
563*760c253cSXin Li        error_message = "Error message."
564*760c253cSXin Li        mock_abandon_cl.side_effect = subprocess.CalledProcessError(
565*760c253cSXin Li            returncode=1, cmd=[], output=error_message
566*760c253cSXin Li        )
567*760c253cSXin Li
568*760c253cSXin Li        args_output = test_helpers.ArgsOutputTest()
569*760c253cSXin Li        args_output.start_rev = start
570*760c253cSXin Li        args_output.end_rev = end
571*760c253cSXin Li        args_output.parallel = 3
572*760c253cSXin Li        args_output.src_path = None
573*760c253cSXin Li        args_output.cleanup = True
574*760c253cSXin Li
575*760c253cSXin Li        with self.assertRaises(subprocess.CalledProcessError) as err:
576*760c253cSXin Li            llvm_bisection.main(args_output)
577*760c253cSXin Li
578*760c253cSXin Li        self.assertEqual(err.exception.output, error_message)
579*760c253cSXin Li
580*760c253cSXin Li        mock_chromeos_root.assert_called_once()
581*760c253cSXin Li
582*760c253cSXin Li        mock_outside_chroot.assert_called_once()
583*760c253cSXin Li
584*760c253cSXin Li        mock_load_status_file.assert_called_once()
585*760c253cSXin Li
586*760c253cSXin Li        mock_get_range.assert_called_once()
587*760c253cSXin Li
588*760c253cSXin Li
589*760c253cSXin Liif __name__ == "__main__":
590*760c253cSXin Li    unittest.main()
591