| #!/usr/bin/env vpython3 |
| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # pylint: disable=protected-access |
| |
| import datetime |
| import os |
| import sys |
| import tempfile |
| import unittest |
| import unittest.mock as mock |
| |
| from pyfakefs import fake_filesystem_unittest # pylint:disable=import-error |
| |
| from flake_suppressor_common import common_typing as ct |
| from flake_suppressor_common import expectations |
| from flake_suppressor_common import unittest_utils as uu |
| |
| |
| # Note for all tests in this class: We can safely check the contents of the file |
| # at the end despite potentially having multiple added lines because Python 3.7+ |
| # guarantees that dictionaries remember insertion order, so there is no risk of |
| # the order of modification changing. |
| @unittest.skipIf(sys.version_info[0] != 3, 'Python 3-only') |
| class IterateThroughResultsForUserUnittest(fake_filesystem_unittest.TestCase): |
| def setUp(self) -> None: |
| self._new_stdout = open(os.devnull, 'w') |
| self.setUpPyfakefs() |
| self._expectations = uu.UnitTestExpectationProcessor() |
| # Redirect stdout since the tested function prints a lot. |
| self._old_stdout = sys.stdout |
| sys.stdout = self._new_stdout |
| |
| self._input_patcher = mock.patch.object(expectations.ExpectationProcessor, |
| 'PromptUserForExpectationAction') |
| self._input_mock = self._input_patcher.start() |
| self.addCleanup(self._input_patcher.stop) |
| |
| self.result_map = { |
| 'pixel_integration_test': { |
| 'foo_test': { |
| tuple(['win']): ['a'], |
| tuple(['mac']): ['b'], |
| }, |
| 'bar_test': { |
| tuple(['win']): ['c'], |
| }, |
| }, |
| } |
| |
| self.expectation_file = os.path.join(uu.ABSOLUTE_EXPECTATION_FILE_DIRECTORY, |
| 'pixel_expectations.txt') |
| uu.CreateFile(self, self.expectation_file) |
| expectation_file_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| |
| self._expectation_file_patcher = mock.patch.object( |
| uu.UnitTestExpectationProcessor, 'GetExpectationFileForSuite') |
| self._expectation_file_mock = self._expectation_file_patcher.start() |
| self._expectation_file_mock.return_value = self.expectation_file |
| self.addCleanup(self._expectation_file_patcher.stop) |
| |
| def tearDown(self) -> None: |
| sys.stdout = self._old_stdout |
| self._new_stdout.close() |
| |
| def testIterateThroughResultsForUserIgnoreNoGroupByTags(self) -> None: |
| """Tests that everything appears to function with ignore and no group.""" |
| self._input_mock.return_value = (None, None) |
| self._expectations.IterateThroughResultsForUser(self.result_map, False, |
| True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testIterateThroughResultsForUserIgnoreGroupByTags(self) -> None: |
| """Tests that everything appears to function with ignore and grouping.""" |
| self._input_mock.return_value = (None, None) |
| self._expectations.IterateThroughResultsForUser(self.result_map, True, True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testIterateThroughResultsForUserRetryNoGroupByTags(self) -> None: |
| """Tests that everything appears to function with retry and no group.""" |
| self._input_mock.return_value = ('RetryOnFailure', '') |
| self._expectations.IterateThroughResultsForUser(self.result_map, False, |
| True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| [ win ] foo_test [ RetryOnFailure ] |
| [ mac ] foo_test [ RetryOnFailure ] |
| [ win ] bar_test [ RetryOnFailure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testIterateThroughResultsForUserRetryGroupByTags(self) -> None: |
| """Tests that everything appears to function with retry and grouping.""" |
| self._input_mock.return_value = ('RetryOnFailure', 'crbug.com/1') |
| self._expectations.IterateThroughResultsForUser(self.result_map, True, True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| crbug.com/1 [ win ] foo_test [ RetryOnFailure ] |
| crbug.com/1 [ win ] bar_test [ RetryOnFailure ] |
| [ mac ] some_test [ Failure ] |
| crbug.com/1 [ mac ] foo_test [ RetryOnFailure ] |
| [ android ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testIterateThroughResultsForUserFailNoGroupByTags(self) -> None: |
| """Tests that everything appears to function with failure and no group.""" |
| self._input_mock.return_value = ('Failure', 'crbug.com/1') |
| self._expectations.IterateThroughResultsForUser(self.result_map, False, |
| True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| crbug.com/1 [ win ] foo_test [ Failure ] |
| crbug.com/1 [ mac ] foo_test [ Failure ] |
| crbug.com/1 [ win ] bar_test [ Failure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testIterateThroughResultsForUserFailGroupByTags(self) -> None: |
| """Tests that everything appears to function with failure and grouping.""" |
| self._input_mock.return_value = ('Failure', '') |
| self._expectations.IterateThroughResultsForUser(self.result_map, True, True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ win ] foo_test [ Failure ] |
| [ win ] bar_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ mac ] foo_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testIterateThroughResultsForUserNoIncludeAllTags(self) -> None: |
| """Tests that everything appears to function without including all tags""" |
| self.result_map = { |
| 'pixel_integration_test': { |
| 'foo_test': { |
| tuple(['win', 'win10']): ['a'], |
| tuple(['mac']): ['b'], |
| }, |
| 'bar_test': { |
| tuple(['win']): ['c'], |
| }, |
| }, |
| } |
| self._input_mock.return_value = ('RetryOnFailure', '') |
| self._expectations.IterateThroughResultsForUser(self.result_map, False, |
| False) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| [ win10 ] foo_test [ RetryOnFailure ] |
| [ mac ] foo_test [ RetryOnFailure ] |
| [ win ] bar_test [ RetryOnFailure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| |
| @unittest.skipIf(sys.version_info[0] != 3, 'Python 3-only') |
| class IterateThroughResultsWithThresholdsUnittest( |
| fake_filesystem_unittest.TestCase): |
| def setUp(self) -> None: |
| self.setUpPyfakefs() |
| self._expectations = uu.UnitTestExpectationProcessor() |
| self.result_map = { |
| 'pixel_integration_test': { |
| 'foo_test': { |
| tuple(['win']): ['a'], |
| tuple(['mac']): ['b'], |
| }, |
| 'bar_test': { |
| tuple(['win']): ['c'], |
| }, |
| }, |
| } |
| |
| self.expectation_file = os.path.join(uu.ABSOLUTE_EXPECTATION_FILE_DIRECTORY, |
| 'pixel_expectations.txt') |
| uu.CreateFile(self, self.expectation_file) |
| expectation_file_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| |
| self._expectation_file_patcher = mock.patch.object( |
| uu.UnitTestExpectationProcessor, 'GetExpectationFileForSuite') |
| self._expectation_file_mock = self._expectation_file_patcher.start() |
| self._expectation_file_mock.return_value = self.expectation_file |
| self.addCleanup(self._expectation_file_patcher.stop) |
| |
| def testGroupByTags(self) -> None: |
| """Tests that threshold-based expectations work when grouping by tags.""" |
| result_counts = { |
| tuple(['win']): { |
| # We expect this to be ignored since it has a 1% flake rate. |
| 'foo_test': 100, |
| # We expect this to be RetryOnFailure since it has a 25% flake rate. |
| 'bar_test': 4, |
| }, |
| tuple(['mac']): { |
| # We expect this to be Failure since it has a 50% flake rate. |
| 'foo_test': 2 |
| } |
| } |
| self._expectations.IterateThroughResultsWithThresholds( |
| self.result_map, True, result_counts, 0.02, 0.5, True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ win ] bar_test [ RetryOnFailure ] |
| [ mac ] some_test [ Failure ] |
| [ mac ] foo_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testNoGroupByTags(self) -> None: |
| """Tests that threshold-based expectations work when not grouping by tags""" |
| result_counts = { |
| tuple(['win']): { |
| # We expect this to be ignored since it has a 1% flake rate. |
| 'foo_test': 100, |
| # We expect this to be RetryOnFailure since it has a 25% flake rate. |
| 'bar_test': 4, |
| }, |
| tuple(['mac']): { |
| # We expect this to be Failure since it has a 50% flake rate. |
| 'foo_test': 2 |
| } |
| } |
| self._expectations.IterateThroughResultsWithThresholds( |
| self.result_map, False, result_counts, 0.02, 0.5, True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| [ mac ] foo_test [ Failure ] |
| [ win ] bar_test [ RetryOnFailure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testNoIncludeAllTags(self) -> None: |
| """Tests that threshold-based expectations work when filtering tags.""" |
| self.result_map = { |
| 'pixel_integration_test': { |
| 'foo_test': { |
| tuple(['win', 'win10']): ['a'], |
| tuple(['mac']): ['b'], |
| }, |
| 'bar_test': { |
| tuple(['win', 'win10']): ['c'], |
| }, |
| }, |
| } |
| |
| result_counts = { |
| tuple(['win', 'win10']): { |
| # We expect this to be ignored since it has a 1% flake rate. |
| 'foo_test': 100, |
| # We expect this to be RetryOnFailure since it has a 25% flake rate. |
| 'bar_test': 4, |
| }, |
| tuple(['mac']): { |
| # We expect this to be Failure since it has a 50% flake rate. |
| 'foo_test': 2 |
| } |
| } |
| self._expectations.IterateThroughResultsWithThresholds( |
| self.result_map, False, result_counts, 0.02, 0.5, False) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ mac ] some_test [ Failure ] |
| [ android ] some_test [ Failure ] |
| [ mac ] foo_test [ Failure ] |
| [ win10 ] bar_test [ RetryOnFailure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| |
| @unittest.skipIf(sys.version_info[0] != 3, 'Python 3-only') |
| class CreateExpectationsForAllResultsUnittest(fake_filesystem_unittest.TestCase |
| ): |
| def setUp(self) -> None: |
| self.setUpPyfakefs() |
| self._expectations = uu.UnitTestExpectationProcessor() |
| self.result_map = { |
| 'pixel_integration_test': { |
| 'foo_test': { |
| tuple(['win']): [ |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/1111', |
| datetime.date.today() - datetime.timedelta(days=2), |
| False, ['Pass']), |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/2222', |
| datetime.date.today() - datetime.timedelta(days=3), |
| False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/3333', |
| datetime.date.today(), False, ['Pass']), |
| ], |
| tuple(['mac']): [ |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/1111', |
| datetime.date.today() - datetime.timedelta(days=1), |
| False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/3333', |
| datetime.date.today() - datetime.timedelta(days=3), |
| False, ['Pass']), |
| ], |
| }, |
| 'bar_test': { |
| tuple(['win']): [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/4444', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/5555', |
| datetime.date.today() - datetime.timedelta(days=1), |
| False, ['Pass']), |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/6666', |
| datetime.date.today() - datetime.timedelta(days=2), |
| False, ['Pass']), |
| ], |
| }, |
| 'baz_test': { |
| # This test config causes build fail on less than 2 consecutive |
| # days, and thus should not exist in the output. |
| tuple(['win']): [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/7777', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/8888', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/9999', |
| datetime.date.today(), False, ['Pass']), |
| ], |
| tuple(['mac']): [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/7777', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/8888', |
| datetime.date.today(), False, ['Pass']), |
| ], |
| }, |
| 'wpt_test': { |
| # Test for same test in all builders over threshold. |
| tuple(['win']): [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1234', |
| datetime.date.today(), False, ['Pass']), |
| ], |
| tuple(['mac']): [ |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/2345', |
| datetime.date.today() - datetime.timedelta(days=1), |
| False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/3456', |
| datetime.date.today(), False, ['Pass']), |
| ], |
| }, |
| }, |
| } |
| self.build_fail_total_number_threshold = 3 |
| self.build_fail_consecutive_day_threshold = 2 |
| self.build_fail_recent_day_threshold = 1 |
| |
| self.expectation_file = os.path.join(uu.ABSOLUTE_EXPECTATION_FILE_DIRECTORY, |
| 'pixel_expectations.txt') |
| uu.CreateFile(self, self.expectation_file) |
| expectation_file_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure Pass ] |
| [ mac ] some_test [ Failure Pass ] |
| [ android ] some_test [ Failure Pass ] |
| """ |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| |
| self._expectation_file_patcher = mock.patch.object( |
| uu.UnitTestExpectationProcessor, 'GetExpectationFileForSuite') |
| self._expectation_file_mock = self._expectation_file_patcher.start() |
| self._expectation_file_mock.return_value = self.expectation_file |
| self.addCleanup(self._expectation_file_patcher.stop) |
| |
| def testGroupByTags(self) -> None: |
| """Tests that threshold-based expectations work when grouping by tags.""" |
| self._expectations.CreateExpectationsForAllResults( |
| self.result_map, True, True, self.build_fail_total_number_threshold, |
| self.build_fail_consecutive_day_threshold, |
| self.build_fail_recent_day_threshold) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure Pass ] |
| [ win ] foo_test [ Failure Pass ] |
| [ win ] bar_test [ Failure Pass ] |
| [ win ] wpt_test [ Failure Pass ] |
| [ mac ] some_test [ Failure Pass ] |
| [ mac ] foo_test [ Failure Pass ] |
| [ mac ] wpt_test [ Failure Pass ] |
| [ android ] some_test [ Failure Pass ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testNoGroupByTags(self) -> None: |
| """Tests that threshold-based expectations work when not grouping by tags""" |
| self._expectations.CreateExpectationsForAllResults( |
| self.result_map, False, True, self.build_fail_total_number_threshold, |
| self.build_fail_consecutive_day_threshold, |
| self.build_fail_recent_day_threshold) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure Pass ] |
| [ mac ] some_test [ Failure Pass ] |
| [ android ] some_test [ Failure Pass ] |
| [ win ] foo_test [ Failure Pass ] |
| [ mac ] foo_test [ Failure Pass ] |
| [ win ] bar_test [ Failure Pass ] |
| [ win ] wpt_test [ Failure Pass ] |
| [ mac ] wpt_test [ Failure Pass ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testNoIncludeAllTags(self) -> None: |
| """Tests that threshold-based expectations work when filtering tags.""" |
| self.result_map = { |
| 'pixel_integration_test': { |
| 'foo_test': { |
| tuple(['win', 'win10']): [ |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/1111', |
| datetime.date.today() - datetime.timedelta(days=2), |
| False, ['Pass']), |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/2222', |
| datetime.date.today() - datetime.timedelta(days=3), |
| False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/3333', |
| datetime.date.today(), False, ['Pass']), |
| ], |
| tuple(['mac']): [ |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/1111', |
| datetime.date.today() - datetime.timedelta(days=1), |
| False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/3333', |
| datetime.date.today() - datetime.timedelta(days=3), |
| False, ['Pass']), |
| ], |
| }, |
| 'bar_test': { |
| tuple(['win', 'win10']): [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/4444', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/5555', |
| datetime.date.today() - datetime.timedelta(days=1), |
| False, ['Pass']), |
| ct.ResultTupleType( |
| ct.ResultStatus.FAIL, 'http://ci.chromium.org/b/6666', |
| datetime.date.today() - datetime.timedelta(days=2), |
| False, ['Pass']), |
| ], |
| }, |
| 'baz_test': { |
| # This test config causes build fail on less than 2 consecutive |
| # days, and thus should not exist in the output. |
| tuple(['win']): [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/7777', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/8888', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/9999', |
| datetime.date.today(), False, ['Pass']), |
| ], |
| tuple(['mac']): [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/7777', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/8888', |
| datetime.date.today(), False, ['Pass']), |
| ], |
| }, |
| }, |
| } |
| self._expectations.CreateExpectationsForAllResults( |
| self.result_map, False, False, self.build_fail_total_number_threshold, |
| self.build_fail_consecutive_day_threshold, |
| self.build_fail_recent_day_threshold) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure Pass ] |
| [ mac ] some_test [ Failure Pass ] |
| [ android ] some_test [ Failure Pass ] |
| [ win10 ] foo_test [ Failure Pass ] |
| [ mac ] foo_test [ Failure Pass ] |
| [ win10 ] bar_test [ Failure Pass ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| |
| @unittest.skipIf(sys.version_info[0] != 3, 'Python 3-only') |
| class FindFailuresInSameConditionUnittest(unittest.TestCase): |
| def setUp(self) -> None: |
| self._expectations = uu.UnitTestExpectationProcessor() |
| self.result_map = { |
| 'pixel_integration_test': { |
| 'foo_test': { |
| tuple(['win']): ['a'], |
| tuple(['mac']): ['a', 'b'], |
| }, |
| 'bar_test': { |
| tuple(['win']): ['a', 'b', 'c'], |
| tuple(['mac']): ['a', 'b', 'c', 'd'], |
| }, |
| }, |
| 'webgl_conformance_integration_test': { |
| 'foo_test': { |
| tuple(['win']): ['a', 'b', 'c', 'd', 'e'], |
| tuple(['mac']): ['a', 'b', 'c', 'd', 'e', 'f'], |
| }, |
| 'bar_test': { |
| tuple(['win']): ['a', 'b', 'c', 'd', 'e', 'f', 'g'], |
| tuple(['mac']): ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], |
| }, |
| }, |
| } |
| |
| def testFindFailuresInSameTest(self) -> None: |
| other_failures = self._expectations.FindFailuresInSameTest( |
| self.result_map, 'pixel_integration_test', 'foo_test', tuple(['win'])) |
| self.assertEqual(other_failures, [(tuple(['mac']), 2)]) |
| |
| def testFindFailuresInSameConfig(self) -> None: |
| typ_tag_ordered_result_map = self._expectations._ReorderMapByTypTags( |
| self.result_map) |
| other_failures = self._expectations.FindFailuresInSameConfig( |
| typ_tag_ordered_result_map, 'pixel_integration_test', 'foo_test', |
| tuple(['win'])) |
| expected_other_failures = [ |
| ('pixel_integration_test.bar_test', 3), |
| ('webgl_conformance_integration_test.foo_test', 5), |
| ('webgl_conformance_integration_test.bar_test', 7), |
| ] |
| self.assertEqual(len(other_failures), len(expected_other_failures)) |
| self.assertEqual(set(other_failures), set(expected_other_failures)) |
| |
| |
| @unittest.skipIf(sys.version_info[0] != 3, 'Python 3-only') |
| class ModifyFileForResultUnittest(fake_filesystem_unittest.TestCase): |
| def setUp(self) -> None: |
| self.setUpPyfakefs() |
| self._expectations = uu.UnitTestExpectationProcessor() |
| self.expectation_file = os.path.join(uu.ABSOLUTE_EXPECTATION_FILE_DIRECTORY, |
| 'expectation.txt') |
| uu.CreateFile(self, self.expectation_file) |
| self._expectation_file_patcher = mock.patch.object( |
| uu.UnitTestExpectationProcessor, 'GetExpectationFileForSuite') |
| self._expectation_file_mock = self._expectation_file_patcher.start() |
| self.addCleanup(self._expectation_file_patcher.stop) |
| self._expectation_file_mock.return_value = self.expectation_file |
| |
| def testNoGroupByTags(self) -> None: |
| """Tests that not grouping by tags appends to the end.""" |
| expectation_file_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| |
| [ mac ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| self._expectations.ModifyFileForResult('some_file', 'some_test', |
| ('win', 'win10'), '', 'Failure', |
| False, True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| |
| [ mac ] some_test [ Failure ] |
| [ win win10 ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testGroupByTagsNoMatch(self) -> None: |
| """Tests that grouping by tags but finding no match appends to the end.""" |
| expectation_file_contents = uu.TAG_HEADER + """\ |
| [ mac ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| self._expectations.ModifyFileForResult('some_file', 'some_test', |
| ('win', 'win10'), '', 'Failure', |
| True, True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ mac ] some_test [ Failure ] |
| [ win win10 ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| def testGroupByTagsMatch(self) -> None: |
| """Tests that grouping by tags and finding a match adds mid-file.""" |
| expectation_file_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| |
| [ mac ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| self._expectations.ModifyFileForResult('some_file', 'foo_test', |
| ('win', 'win10'), '', 'Failure', |
| True, True) |
| expected_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| [ win ] foo_test [ Failure ] |
| |
| [ mac ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file) as infile: |
| self.assertEqual(infile.read(), expected_contents) |
| |
| |
| @unittest.skipIf(sys.version_info[0] != 3, 'Python 3-only') |
| class FilterToMostSpecificTagTypeUnittest(fake_filesystem_unittest.TestCase): |
| def setUp(self) -> None: |
| self._expectations = uu.UnitTestExpectationProcessor() |
| self.setUpPyfakefs() |
| with tempfile.NamedTemporaryFile(delete=False) as tf: |
| self.expectation_file = tf.name |
| |
| def testBasic(self): |
| """Tests that only the most specific tags are kept.""" |
| expectation_file_contents = """\ |
| # tags: [ tag1_least_specific tag1_middle_specific tag1_most_specific ] |
| # tags: [ tag2_least_specific tag2_middle_specific tag2_most_specific ]""" |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| |
| tags = ('tag1_least_specific', 'tag1_most_specific', 'tag2_middle_specific', |
| 'tag2_least_specific') |
| filtered_tags = self._expectations.FilterToMostSpecificTypTags( |
| tags, self.expectation_file) |
| self.assertEqual(filtered_tags, |
| ('tag1_most_specific', 'tag2_middle_specific')) |
| |
| def testSingleTags(self) -> None: |
| """Tests that functionality works as expected with single tags.""" |
| expectation_file_contents = """\ |
| # tags: [ tag1_most_specific ] |
| # tags: [ tag2_most_specific ]""" |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| |
| tags = ('tag1_most_specific', 'tag2_most_specific') |
| filtered_tags = self._expectations.FilterToMostSpecificTypTags( |
| tags, self.expectation_file) |
| self.assertEqual(filtered_tags, tags) |
| |
| def testUnusedTags(self) -> None: |
| """Tests that functionality works as expected with extra/unused tags.""" |
| expectation_file_contents = """\ |
| # tags: [ tag1_least_specific tag1_middle_specific tag1_most_specific ] |
| # tags: [ tag2_least_specific tag2_middle_specific tag2_most_specific ] |
| # tags: [ some_unused_tag ]""" |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| |
| tags = ('tag1_least_specific', 'tag1_most_specific', 'tag2_middle_specific', |
| 'tag2_least_specific') |
| filtered_tags = self._expectations.FilterToMostSpecificTypTags( |
| tags, self.expectation_file) |
| self.assertEqual(filtered_tags, |
| ('tag1_most_specific', 'tag2_middle_specific')) |
| |
| def testMultiline(self) -> None: |
| """Tests that functionality works when tags cover multiple lines.""" |
| expectation_file_contents = """\ |
| # tags: [ tag1_least_specific |
| # tag1_middle_specific |
| # tag1_most_specific ] |
| # tags: [ tag2_least_specific |
| # tag2_middle_specific tag2_most_specific ]""" |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| |
| tags = ('tag1_least_specific', 'tag1_middle_specific', 'tag1_most_specific', |
| 'tag2_middle_specific', 'tag2_least_specific') |
| filtered_tags = self._expectations.FilterToMostSpecificTypTags( |
| tags, self.expectation_file) |
| self.assertEqual(filtered_tags, |
| ('tag1_most_specific', 'tag2_middle_specific')) |
| |
| def testMissingTags(self) -> None: |
| """Tests that a file not having all tags is an error.""" |
| expectation_file_contents = """\ |
| # tags: [ tag1_least_specific tag1_middle_specific ] |
| # tags: [ tag2_least_specific tag2_middle_specific tag2_most_specific ]""" |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| |
| tags = ('tag1_least_specific', 'tag1_most_specific', 'tag2_middle_specific', |
| 'tag2_least_specific') |
| with self.assertRaises(RuntimeError): |
| self._expectations.FilterToMostSpecificTypTags(tags, |
| self.expectation_file) |
| |
| |
| @unittest.skipIf(sys.version_info[0] != 3, 'Python 3-only') |
| class FindBestInsertionLineForExpectationUnittest( |
| fake_filesystem_unittest.TestCase): |
| def setUp(self) -> None: |
| self.setUpPyfakefs() |
| self._expectations = uu.UnitTestExpectationProcessor() |
| self.expectation_file = os.path.join(uu.ABSOLUTE_EXPECTATION_FILE_DIRECTORY, |
| 'expectation.txt') |
| uu.CreateFile(self, self.expectation_file) |
| expectation_file_contents = uu.TAG_HEADER + """\ |
| [ win ] some_test [ Failure ] |
| |
| [ mac ] some_test [ Failure ] |
| |
| [ win release ] bar_test [ Failure ] |
| [ win ] foo_test [ Failure ] |
| |
| [ chromeos ] some_test [ Failure ] |
| """ |
| with open(self.expectation_file, 'w') as outfile: |
| outfile.write(expectation_file_contents) |
| |
| def testNoMatchingTags(self) -> None: |
| """Tests behavior when there are no expectations with matching tags.""" |
| insertion_line, tags = ( |
| self._expectations.FindBestInsertionLineForExpectation( |
| tuple(['android']), self.expectation_file)) |
| self.assertEqual(insertion_line, -1) |
| self.assertEqual(tags, set()) |
| |
| def testMatchingTagsLastEntryChosen(self) -> None: |
| """Tests that the last matching line is chosen.""" |
| insertion_line, tags = ( |
| self._expectations.FindBestInsertionLineForExpectation( |
| tuple(['win']), self.expectation_file)) |
| # We expect "[ win ] foo_test [ Failure ]" to be chosen |
| expected_line = len(uu.TAG_HEADER.splitlines()) + 6 |
| self.assertEqual(insertion_line, expected_line) |
| self.assertEqual(tags, set(['win'])) |
| |
| def testMatchingTagsClosestMatchChosen(self) -> None: |
| """Tests that the closest tag match is chosen.""" |
| insertion_line, tags = ( |
| self._expectations.FindBestInsertionLineForExpectation( |
| ('win', 'release'), self.expectation_file)) |
| # We expect "[ win release ] bar_test [ Failure ]" to be chosen |
| expected_line = len(uu.TAG_HEADER.splitlines()) + 5 |
| self.assertEqual(insertion_line, expected_line) |
| self.assertEqual(tags, set(['win', 'release'])) |
| |
| |
| class AssertCheckoutIsUpToDateUnittest(unittest.TestCase): |
| def setUp(self) -> None: |
| self._expectations = uu.UnitTestExpectationProcessor() |
| self._origin_patcher = mock.patch( |
| 'flake_suppressor_common.expectations.ExpectationProcessor.' |
| 'GetOriginExpectationFileContents') |
| self._origin_mock = self._origin_patcher.start() |
| self.addCleanup(self._origin_patcher.stop) |
| self._local_patcher = mock.patch( |
| 'flake_suppressor_common.expectations.' + |
| 'ExpectationProcessor.GetLocalCheckoutExpectationFileContents') |
| self._local_mock = self._local_patcher.start() |
| self.addCleanup(self._local_patcher.stop) |
| |
| def testContentsMatch(self) -> None: |
| """Tests the happy path where the contents match.""" |
| self._origin_mock.return_value = { |
| 'foo.txt': 'foo_content', |
| 'bar.txt': 'bar_content', |
| } |
| self._local_mock.return_value = { |
| 'bar.txt': 'bar_content', |
| 'foo.txt': 'foo_content', |
| } |
| self._expectations.AssertCheckoutIsUpToDate() |
| |
| def testContentsDoNotMatch(self) -> None: |
| """Tests that mismatched contents results in a failure.""" |
| self._origin_mock.return_value = { |
| 'foo.txt': 'foo_content', |
| 'bar.txt': 'bar_content', |
| } |
| # Differing keys. |
| self._local_mock.return_value = { |
| 'bar.txt': 'bar_content', |
| 'foo2.txt': 'foo_content', |
| } |
| with self.assertRaises(RuntimeError): |
| self._expectations.AssertCheckoutIsUpToDate() |
| |
| # Differing values. |
| self._local_mock.return_value = { |
| 'bar.txt': 'bar_content', |
| 'foo.txt': 'foo_content2', |
| } |
| with self.assertRaises(RuntimeError): |
| self._expectations.AssertCheckoutIsUpToDate() |
| |
| |
| class OverFailedBuildThresholdUnittest(unittest.TestCase): |
| def setUp(self) -> None: |
| self.build_fail_total_number_threshold = 3 |
| |
| def testOverThreshold(self) -> None: |
| """Tests functionality when |result_tuple_list| passes |
| |build_fail_total_number_threshold|. |
| |
| True is expected output on these inputs. |
| """ |
| result_tuple_list = [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1111', |
| datetime.date(2021, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/3333', |
| datetime.date(2023, 1, 1), False, ['Pass']), |
| ] |
| self.assertTrue( |
| expectations.OverFailedBuildThreshold( |
| result_tuple_list, self.build_fail_total_number_threshold)) |
| |
| def testUnderThreshold(self) -> None: |
| """Tests functionality when |result_tuple_list| cannot pass |
| |build_fail_total_number_threshold|. |
| |
| False is expected output on these inputs. |
| """ |
| result_tuple_list = [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1111', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date(2022, 1, 2), False, ['Pass']), |
| ] |
| self.assertFalse( |
| expectations.OverFailedBuildThreshold( |
| result_tuple_list, self.build_fail_total_number_threshold)) |
| |
| result_tuple_list = [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1111', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date(2022, 1, 2), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date(2022, 1, 3), False, ['Pass']), |
| ] |
| self.assertFalse( |
| expectations.OverFailedBuildThreshold( |
| result_tuple_list, self.build_fail_total_number_threshold)) |
| |
| |
| class OverFailedBuildByConsecutiveDayThresholdUnittest(unittest.TestCase): |
| def setUp(self) -> None: |
| self.build_fail_consecutive_day_threshold = 3 |
| |
| def testOverThreshold(self) -> None: |
| """Tests functionality when |result_tuple_list| passes |
| |build_fail_consecutive_day_threshold|. |
| |
| True is expected output on these inputs. |
| """ |
| result_tuple_list = [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1111', |
| datetime.date(2022, 1, 2), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/3333', |
| datetime.date(2022, 1, 3), False, ['Pass']), |
| ] |
| self.assertTrue( |
| expectations.OverFailedBuildByConsecutiveDayThreshold( |
| result_tuple_list, self.build_fail_consecutive_day_threshold)) |
| |
| def testUnderThreshold(self) -> None: |
| """Tests functionality when |result_tuple_list| cannot pass |
| |build_fail_consecutive_day_threshold|. |
| |
| False is expected output on these inputs. |
| """ |
| result_tuple_list = [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1111', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/3333', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ] |
| self.assertFalse( |
| expectations.OverFailedBuildByConsecutiveDayThreshold( |
| result_tuple_list, self.build_fail_consecutive_day_threshold)) |
| |
| result_tuple_list = [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1111', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date(2022, 1, 2), False, ['Pass']), |
| ] |
| self.assertFalse( |
| expectations.OverFailedBuildByConsecutiveDayThreshold( |
| result_tuple_list, self.build_fail_consecutive_day_threshold)) |
| |
| result_tuple_list = [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1111', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date(2022, 1, 2), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/3333', |
| datetime.date(2022, 1, 4), False, ['Pass']), |
| ] |
| self.assertFalse( |
| expectations.OverFailedBuildByConsecutiveDayThreshold( |
| result_tuple_list, self.build_fail_consecutive_day_threshold)) |
| |
| |
| class FailedBuildWithinRecentDayThresholdUnittest(unittest.TestCase): |
| def setUp(self) -> None: |
| self.build_fail_recent_day_threshold = 3 |
| |
| def testWithinThreshold(self) -> None: |
| """Tests functionality when |result_tuple_list| has build fail within |
| |build_fail_recent_day_threshold|. |
| |
| True is expected output on these inputs. |
| """ |
| result_tuple_list = [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1111', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date.today(), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/3333', |
| datetime.date.today(), False, ['Pass']), |
| ] |
| self.assertTrue( |
| expectations.FailedBuildWithinRecentDayThreshold( |
| result_tuple_list, self.build_fail_recent_day_threshold)) |
| |
| def testBeyondThreshold(self) -> None: |
| """Tests functionality when |result_tuple_list| has no build fail within |
| |build_fail_recent_day_threshold|. |
| |
| False is expected output on these inputs. |
| """ |
| result_tuple_list = [ |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/1111', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/2222', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ct.ResultTupleType(ct.ResultStatus.FAIL, |
| 'http://ci.chromium.org/b/3333', |
| datetime.date(2022, 1, 1), False, ['Pass']), |
| ] |
| self.assertFalse( |
| expectations.FailedBuildWithinRecentDayThreshold( |
| result_tuple_list, self.build_fail_recent_day_threshold)) |
| |
| |
| if __name__ == '__main__': |
| unittest.main(verbosity=2) |