xref: /aosp_15_r20/external/toolchain-utils/llvm_tools/modify_a_tryjob_unittest.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1#!/usr/bin/env python3
2# Copyright 2019 The ChromiumOS Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Tests for modifying a tryjob."""
7
8import json
9import unittest
10from unittest import mock
11
12import get_llvm_hash
13import modify_a_tryjob
14import test_helpers
15import update_packages_and_run_tests
16import update_tryjob_status
17
18
19class ModifyATryjobTest(unittest.TestCase):
20    """Unittests for modifying a tryjob."""
21
22    def testNoTryjobsInStatusFile(self):
23        bisect_test_contents = {"start": 369410, "end": 369420, "jobs": []}
24
25        # Create a temporary .JSON file to simulate a .JSON file that has
26        # bisection contents.
27        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
28            with open(temp_json_file, "w", encoding="utf-8") as f:
29                test_helpers.WritePrettyJsonFile(bisect_test_contents, f)
30
31            revision_to_modify = 369411
32
33            args_output = test_helpers.ArgsOutputTest()
34            args_output.builders = None
35            args_output.options = None
36
37            # Verify the exception is raised there are no tryjobs in the status
38            # file and the mode is not to 'add' a tryjob.
39            with self.assertRaises(SystemExit) as err:
40                modify_a_tryjob.PerformTryjobModification(
41                    revision_to_modify,
42                    modify_a_tryjob.ModifyTryjob.REMOVE,
43                    temp_json_file,
44                    args_output.extra_change_lists,
45                    args_output.options,
46                    args_output.builders,
47                    args_output.chromeos_path,
48                )
49
50            self.assertEqual(
51                str(err.exception), "No tryjobs in %s" % temp_json_file
52            )
53
54    # Simulate the behavior of `FindTryjobIndex()` when the index of the tryjob
55    # was not found.
56    @mock.patch.object(
57        update_tryjob_status, "FindTryjobIndex", return_value=None
58    )
59    def testNoTryjobIndexFound(self, mock_find_tryjob_index):
60        bisect_test_contents = {
61            "start": 369410,
62            "end": 369420,
63            "jobs": [
64                {"rev": 369411, "status": "pending", "buildbucket_id": 1200}
65            ],
66        }
67
68        # Create a temporary .JSON file to simulate a .JSON file that has
69        # bisection contents.
70        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
71            with open(temp_json_file, "w", encoding="utf-8") as f:
72                test_helpers.WritePrettyJsonFile(bisect_test_contents, f)
73
74            revision_to_modify = 369412
75
76            args_output = test_helpers.ArgsOutputTest()
77            args_output.builders = None
78            args_output.options = None
79
80            # Verify the exception is raised when the index of the tryjob was
81            # not found in the status file and the mode is not to 'add' a
82            # tryjob.
83            with self.assertRaises(ValueError) as err:
84                modify_a_tryjob.PerformTryjobModification(
85                    revision_to_modify,
86                    modify_a_tryjob.ModifyTryjob.REMOVE,
87                    temp_json_file,
88                    args_output.extra_change_lists,
89                    args_output.options,
90                    args_output.builders,
91                    args_output.chromeos_path,
92                )
93
94            self.assertEqual(
95                str(err.exception),
96                "Unable to find tryjob for %d in %s"
97                % (revision_to_modify, temp_json_file),
98            )
99
100        mock_find_tryjob_index.assert_called_once()
101
102    # Simulate the behavior of `FindTryjobIndex()` when the index of the tryjob
103    # was found.
104    @mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
105    def testSuccessfullyRemovedTryjobInStatusFile(self, mock_find_tryjob_index):
106        bisect_test_contents = {
107            "start": 369410,
108            "end": 369420,
109            "jobs": [
110                {"rev": 369414, "status": "pending", "buildbucket_id": 1200}
111            ],
112        }
113
114        # Create a temporary .JSON file to simulate a .JSON file that has
115        # bisection contents.
116        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
117            with open(temp_json_file, "w", encoding="utf-8") as f:
118                test_helpers.WritePrettyJsonFile(bisect_test_contents, f)
119
120            revision_to_modify = 369414
121
122            args_output = test_helpers.ArgsOutputTest()
123            args_output.builders = None
124            args_output.options = None
125
126            modify_a_tryjob.PerformTryjobModification(
127                revision_to_modify,
128                modify_a_tryjob.ModifyTryjob.REMOVE,
129                temp_json_file,
130                args_output.extra_change_lists,
131                args_output.options,
132                args_output.builders,
133                args_output.chromeos_path,
134            )
135
136            # Verify that the tryjob was removed from the status file.
137            with open(temp_json_file, encoding="utf-8") as status_file:
138                bisect_contents = json.load(status_file)
139
140                expected_file_contents = {
141                    "start": 369410,
142                    "end": 369420,
143                    "jobs": [],
144                }
145
146                self.assertDictEqual(bisect_contents, expected_file_contents)
147
148        mock_find_tryjob_index.assert_called_once()
149
150    # Simulate the behavior of `RunTryJobs()` when successfully submitted a
151    # tryjob.
152    @mock.patch.object(update_packages_and_run_tests, "RunTryJobs")
153    # Simulate the behavior of `FindTryjobIndex()` when the index of the tryjob
154    # was found.
155    @mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
156    def testSuccessfullyRelaunchedTryjob(
157        self, mock_find_tryjob_index, mock_run_tryjob
158    ):
159        bisect_test_contents = {
160            "start": 369410,
161            "end": 369420,
162            "jobs": [
163                {
164                    "rev": 369411,
165                    "status": "bad",
166                    "link": "https://some_tryjob_link.com",
167                    "buildbucket_id": 1200,
168                    "cl": 123,
169                    "extra_cls": None,
170                    "options": None,
171                    "builder": ["some-builder-tryjob"],
172                }
173            ],
174        }
175
176        tryjob_result = [
177            {"link": "https://some_new_tryjob_link.com", "buildbucket_id": 20}
178        ]
179
180        mock_run_tryjob.return_value = tryjob_result
181
182        # Create a temporary .JSON file to simulate a .JSON file that has
183        # bisection contents.
184        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
185            with open(temp_json_file, "w", encoding="utf-8") as f:
186                test_helpers.WritePrettyJsonFile(bisect_test_contents, f)
187
188            revision_to_modify = 369411
189
190            args_output = test_helpers.ArgsOutputTest()
191            args_output.builders = None
192            args_output.options = None
193
194            modify_a_tryjob.PerformTryjobModification(
195                revision_to_modify,
196                modify_a_tryjob.ModifyTryjob.RELAUNCH,
197                temp_json_file,
198                args_output.extra_change_lists,
199                args_output.options,
200                args_output.builders,
201                args_output.chromeos_path,
202            )
203
204            # Verify that the tryjob's information was updated after submtting
205            # the tryjob.
206            with open(temp_json_file, encoding="utf-8") as status_file:
207                bisect_contents = json.load(status_file)
208
209                expected_file_contents = {
210                    "start": 369410,
211                    "end": 369420,
212                    "jobs": [
213                        {
214                            "rev": 369411,
215                            "status": "pending",
216                            "link": "https://some_new_tryjob_link.com",
217                            "buildbucket_id": 20,
218                            "cl": 123,
219                            "extra_cls": None,
220                            "options": None,
221                            "builder": ["some-builder-tryjob"],
222                        }
223                    ],
224                }
225
226                self.assertDictEqual(bisect_contents, expected_file_contents)
227
228        mock_find_tryjob_index.assert_called_once()
229
230        mock_run_tryjob.assert_called_once()
231
232    # Simulate the behavior of `FindTryjobIndex()` when the index of the tryjob
233    # was found.
234    @mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
235    def testAddingTryjobThatAlreadyExists(self, mock_find_tryjob_index):
236        bisect_test_contents = {
237            "start": 369410,
238            "end": 369420,
239            "jobs": [
240                {"rev": 369411, "status": "bad", "builder": ["some-builder"]}
241            ],
242        }
243
244        # Create a temporary .JSON file to simulate a .JSON file that has
245        # bisection contents.
246        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
247            with open(temp_json_file, "w", encoding="utf-8") as f:
248                test_helpers.WritePrettyJsonFile(bisect_test_contents, f)
249
250            revision_to_add = 369411
251
252            # Index of the tryjob in 'jobs' list.
253            tryjob_index = 0
254
255            args_output = test_helpers.ArgsOutputTest()
256            args_output.options = None
257
258            # Verify the exception is raised when the tryjob that is going to
259            # added already exists in the status file (found its index).
260            with self.assertRaises(ValueError) as err:
261                modify_a_tryjob.PerformTryjobModification(
262                    revision_to_add,
263                    modify_a_tryjob.ModifyTryjob.ADD,
264                    temp_json_file,
265                    args_output.extra_change_lists,
266                    args_output.options,
267                    args_output.builders,
268                    args_output.chromeos_path,
269                )
270
271            self.assertEqual(
272                str(err.exception),
273                "Tryjob already exists (index is %d) in %s."
274                % (tryjob_index, temp_json_file),
275            )
276
277        mock_find_tryjob_index.assert_called_once()
278
279    # Simulate the behavior of `FindTryjobIndex()` when the tryjob was not
280    # found.
281    @mock.patch.object(
282        update_tryjob_status, "FindTryjobIndex", return_value=None
283    )
284    def testSuccessfullyDidNotAddTryjobOutsideOfBisectionBounds(
285        self, mock_find_tryjob_index
286    ):
287        bisect_test_contents = {
288            "start": 369410,
289            "end": 369420,
290            "jobs": [{"rev": 369411, "status": "bad"}],
291        }
292
293        # Create a temporary .JSON file to simulate a .JSON file that has
294        # bisection contents.
295        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
296            with open(temp_json_file, "w", encoding="utf-8") as f:
297                test_helpers.WritePrettyJsonFile(bisect_test_contents, f)
298
299            # Add a revision that is outside of 'start' and 'end'.
300            revision_to_add = 369450
301
302            args_output = test_helpers.ArgsOutputTest()
303            args_output.options = None
304
305            # Verify the exception is raised when adding a tryjob that does not
306            # exist and is not within 'start' and 'end'.
307            with self.assertRaises(ValueError) as err:
308                modify_a_tryjob.PerformTryjobModification(
309                    revision_to_add,
310                    modify_a_tryjob.ModifyTryjob.ADD,
311                    temp_json_file,
312                    args_output.extra_change_lists,
313                    args_output.options,
314                    args_output.builders,
315                    args_output.chromeos_path,
316                )
317
318            self.assertEqual(
319                str(err.exception),
320                "Failed to add tryjob to %s" % temp_json_file,
321            )
322
323        mock_find_tryjob_index.assert_called_once()
324
325    # Simulate the behavior of `AddTryjob()` when successfully submitted the
326    # tryjob and constructed the tryjob information (a dictionary).
327    @mock.patch.object(modify_a_tryjob, "AddTryjob")
328    # Simulate the behavior of `GetLLVMHashAndVersionFromSVNOption()` when
329    # successfully retrieved the git hash of the revision to launch a tryjob
330    # for.
331    @mock.patch.object(
332        get_llvm_hash,
333        "GetLLVMHashAndVersionFromSVNOption",
334        return_value=("a123testhash1", 369418),
335    )
336    # Simulate the behavior of `FindTryjobIndex()` when the tryjob was not
337    # found.
338    @mock.patch.object(
339        update_tryjob_status, "FindTryjobIndex", return_value=None
340    )
341    def testSuccessfullyAddedTryjob(
342        self, mock_find_tryjob_index, mock_get_llvm_hash, mock_add_tryjob
343    ):
344        bisect_test_contents = {
345            "start": 369410,
346            "end": 369420,
347            "jobs": [{"rev": 369411, "status": "bad"}],
348        }
349
350        # Create a temporary .JSON file to simulate a .JSON file that has
351        # bisection contents.
352        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
353            with open(temp_json_file, "w", encoding="utf-8") as f:
354                test_helpers.WritePrettyJsonFile(bisect_test_contents, f)
355
356            # Add a revision that is outside of 'start' and 'end'.
357            revision_to_add = 369418
358
359            args_output = test_helpers.ArgsOutputTest()
360            args_output.options = None
361
362            new_tryjob_info = {
363                "rev": revision_to_add,
364                "status": "pending",
365                "options": args_output.options,
366                "extra_cls": args_output.extra_change_lists,
367                "builder": args_output.builders,
368            }
369
370            mock_add_tryjob.return_value = new_tryjob_info
371
372            modify_a_tryjob.PerformTryjobModification(
373                revision_to_add,
374                modify_a_tryjob.ModifyTryjob.ADD,
375                temp_json_file,
376                args_output.extra_change_lists,
377                args_output.options,
378                args_output.builders,
379                args_output.chromeos_path,
380            )
381
382            # Verify that the tryjob was added to the status file.
383            with open(temp_json_file, encoding="utf-8") as status_file:
384                bisect_contents = json.load(status_file)
385
386                expected_file_contents = {
387                    "start": 369410,
388                    "end": 369420,
389                    "jobs": [{"rev": 369411, "status": "bad"}, new_tryjob_info],
390                }
391
392                self.assertDictEqual(bisect_contents, expected_file_contents)
393
394        mock_find_tryjob_index.assert_called_once()
395
396        mock_get_llvm_hash.assert_called_once_with(revision_to_add)
397
398        mock_add_tryjob.assert_called_once()
399
400    # Simulate the behavior of `FindTryjobIndex()` when the tryjob was found.
401    @mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0)
402    def testModifyATryjobOptionDoesNotExist(self, mock_find_tryjob_index):
403        bisect_test_contents = {
404            "start": 369410,
405            "end": 369420,
406            "jobs": [{"rev": 369414, "status": "bad"}],
407        }
408
409        # Create a temporary .JSON file to simulate a .JSON file that has
410        # bisection contents.
411        with test_helpers.CreateTemporaryJsonFile() as temp_json_file:
412            with open(temp_json_file, "w", encoding="utf-8") as f:
413                test_helpers.WritePrettyJsonFile(bisect_test_contents, f)
414
415            # Add a revision that is outside of 'start' and 'end'.
416            revision_to_modify = 369414
417
418            args_output = test_helpers.ArgsOutputTest()
419            args_output.builders = None
420            args_output.options = None
421
422            # Verify the exception is raised when the modify a tryjob option
423            # does not exist.
424            with self.assertRaises(ValueError) as err:
425                modify_a_tryjob.PerformTryjobModification(
426                    revision_to_modify,
427                    "remove_link",
428                    temp_json_file,
429                    args_output.extra_change_lists,
430                    args_output.options,
431                    args_output.builders,
432                    args_output.chromeos_path,
433                )
434
435            self.assertEqual(
436                str(err.exception),
437                'Invalid "modify_tryjob" option provided: remove_link',
438            )
439
440            mock_find_tryjob_index.assert_called_once()
441
442
443if __name__ == "__main__":
444    unittest.main()
445