| #!/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. | 
 | """Unittests for test_result_util.py.""" | 
 |  | 
 | import collections | 
 | import copy | 
 | import mock | 
 | import unittest | 
 |  | 
 | import test_result_util | 
 | from test_result_util import TestResult, TestStatus, ResultCollection | 
 | import test_runner_test | 
 |  | 
 | FAKE_TEST_LOC = {'repo': 'https://test', 'fileName': '//test.cc'} | 
 | PASSED_RESULT = TestResult( | 
 |     'passed/test', TestStatus.PASS, duration=1233, test_log='Logs') | 
 | PASSED_RESULT_WITH_LOC = TestResult( | 
 |     'passed/test', | 
 |     TestStatus.PASS, | 
 |     duration=1233, | 
 |     test_log='Logs', | 
 |     test_loc=FAKE_TEST_LOC) | 
 | FAILED_RESULT = TestResult( | 
 |     'failed/test', TestStatus.FAIL, duration=1233, test_log='line1\nline2') | 
 | FAILED_RESULT_DUPLICATE = TestResult( | 
 |     'failed/test', TestStatus.FAIL, test_log='line3\nline4') | 
 | DISABLED_RESULT = TestResult( | 
 |     'disabled/test', | 
 |     TestStatus.SKIP, | 
 |     expected_status=TestStatus.SKIP, | 
 |     attachments={'name': '/path/to/name'}) | 
 | UNEXPECTED_SKIPPED_RESULT = TestResult('unexpected/skipped_test', | 
 |                                        TestStatus.SKIP) | 
 | CRASHED_RESULT = TestResult('crashed/test', TestStatus.CRASH) | 
 | FLAKY_PASS_RESULT = TestResult('flaky/test', TestStatus.PASS) | 
 | FLAKY_FAIL_RESULT = TestResult( | 
 |     'flaky/test', TestStatus.FAIL, test_log='line1\nline2') | 
 | ABORTED_RESULT = TestResult('aborted/test', TestStatus.ABORT) | 
 |  | 
 |  | 
 | class UtilTest(test_runner_test.TestCase): | 
 |   """Tests util methods in test_result_util module.""" | 
 |  | 
 |   def test_validate_kwargs(self): | 
 |     """Tests _validate_kwargs.""" | 
 |     with self.assertRaises(AssertionError) as context: | 
 |       TestResult('name', TestStatus.PASS, unknown='foo') | 
 |     expected_message = ("Invalid keyword argument(s) in") | 
 |     self.assertTrue(expected_message in str(context.exception)) | 
 |     with self.assertRaises(AssertionError) as context: | 
 |       ResultCollection(test_log='foo') | 
 |     expected_message = ("Invalid keyword argument(s) in") | 
 |     self.assertTrue(expected_message in str(context.exception)) | 
 |  | 
 |   def test_validate_test_status(self): | 
 |     """Tests exception raised from validation.""" | 
 |     with self.assertRaises(TypeError) as context: | 
 |       test_result_util._validate_test_status('TIMEOUT') | 
 |     expected_message = ('Invalid test status: TIMEOUT. Should be one of') | 
 |     self.assertTrue(expected_message in str(context.exception)) | 
 |  | 
 |   def test_to_standard_json_literal(self): | 
 |     """Tests _to_standard_json_literal.""" | 
 |     status = test_result_util._to_standard_json_literal(TestStatus.FAIL) | 
 |     self.assertEqual(status, 'FAIL') | 
 |     status = test_result_util._to_standard_json_literal(TestStatus.ABORT) | 
 |     self.assertEqual(status, 'TIMEOUT') | 
 |  | 
 |  | 
 | class TestResultTest(test_runner_test.TestCase): | 
 |   """Tests TestResult class APIs.""" | 
 |  | 
 |   def test_init(self): | 
 |     """Tests class initialization.""" | 
 |     test_result = PASSED_RESULT | 
 |     self.assertEqual(test_result.name, 'passed/test') | 
 |     self.assertEqual(test_result.status, TestStatus.PASS) | 
 |     self.assertEqual(test_result.expected_status, TestStatus.PASS) | 
 |     self.assertEqual(test_result.test_log, 'Logs') | 
 |  | 
 |   def test_compose_result_sink_tags(self): | 
 |     """Tests _compose_result_sink_tags.""" | 
 |     disabled_test_tags = [('test_name', 'disabled/test'), | 
 |                           ('disabled_test', 'true')] | 
 |     unexpected_skip_test_tags = [('test_name', 'unexpected/skipped_test'), | 
 |                                  ('disabled_test', 'false')] | 
 |     not_skip_test_tags = [('test_name', 'passed/test')] | 
 |  | 
 |     not_skip_test_result = PASSED_RESULT | 
 |     self.assertEqual(not_skip_test_tags, | 
 |                      not_skip_test_result._compose_result_sink_tags()) | 
 |  | 
 |     disabled_test_result = DISABLED_RESULT | 
 |     self.assertEqual(disabled_test_tags, | 
 |                      disabled_test_result._compose_result_sink_tags()) | 
 |  | 
 |     unexpected_skip_test_result = UNEXPECTED_SKIPPED_RESULT | 
 |     self.assertEqual(unexpected_skip_test_tags, | 
 |                      unexpected_skip_test_result._compose_result_sink_tags()) | 
 |  | 
 |   @mock.patch('result_sink_util.ResultSinkClient.post') | 
 |   def test_report_to_result_sink(self, mock_post): | 
 |     disabled_test_result = DISABLED_RESULT | 
 |     client = mock.MagicMock() | 
 |     disabled_test_result.report_to_result_sink(client) | 
 |     client.post.assert_called_with( | 
 |         'disabled/test', | 
 |         'SKIP', | 
 |         True, | 
 |         duration=None, | 
 |         test_log='', | 
 |         tags=[('test_name', 'disabled/test'), ('disabled_test', 'true')], | 
 |         test_loc=None, | 
 |         file_artifacts={'name': '/path/to/name'}) | 
 |     # Duplicate calls will only report once. | 
 |     disabled_test_result.report_to_result_sink(client) | 
 |     self.assertEqual(client.post.call_count, 1) | 
 |     disabled_test_result.report_to_result_sink(client) | 
 |     self.assertEqual(client.post.call_count, 1) | 
 |  | 
 |     faileded_result = FAILED_RESULT | 
 |     client = mock.MagicMock() | 
 |     faileded_result.report_to_result_sink(client) | 
 |     client.post.assert_called_with( | 
 |         'failed/test', | 
 |         'FAIL', | 
 |         False, | 
 |         duration=1233, | 
 |         file_artifacts={}, | 
 |         tags=[('test_name', 'failed/test')], | 
 |         test_loc=None, | 
 |         test_log='line1\nline2') | 
 |  | 
 |     passed_result = PASSED_RESULT_WITH_LOC | 
 |     client = mock.MagicMock() | 
 |     passed_result.report_to_result_sink(client) | 
 |     client.post.assert_called_with( | 
 |         'passed/test', | 
 |         'PASS', | 
 |         True, | 
 |         duration=1233, | 
 |         file_artifacts={}, | 
 |         tags=[('test_name', 'passed/test')], | 
 |         test_loc=FAKE_TEST_LOC, | 
 |         test_log='Logs') | 
 |  | 
 |  | 
 | class ResultCollectionTest(test_runner_test.TestCase): | 
 |   """Tests ResultCollection class APIs.""" | 
 |  | 
 |   def setUp(self): | 
 |     super(ResultCollectionTest, self).setUp() | 
 |     self.full_collection = ResultCollection(test_results=[ | 
 |         PASSED_RESULT, FAILED_RESULT, FAILED_RESULT_DUPLICATE, DISABLED_RESULT, | 
 |         UNEXPECTED_SKIPPED_RESULT, CRASHED_RESULT, FLAKY_PASS_RESULT, | 
 |         FLAKY_FAIL_RESULT, ABORTED_RESULT | 
 |     ]) | 
 |  | 
 |   def test_init(self): | 
 |     """Tests class initialization.""" | 
 |     collection = ResultCollection( | 
 |         test_results=[ | 
 |             PASSED_RESULT, DISABLED_RESULT, UNEXPECTED_SKIPPED_RESULT | 
 |         ], | 
 |         crashed=True) | 
 |     self.assertTrue(collection.crashed) | 
 |     self.assertEqual(collection.crash_message, '') | 
 |     self.assertEqual( | 
 |         collection.test_results, | 
 |         [PASSED_RESULT, DISABLED_RESULT, UNEXPECTED_SKIPPED_RESULT]) | 
 |  | 
 |   def test_add_result(self): | 
 |     """Tests add_test_result.""" | 
 |     collection = ResultCollection(test_results=[FAILED_RESULT]) | 
 |     collection.add_test_result(DISABLED_RESULT) | 
 |     self.assertEqual(collection.test_results, [FAILED_RESULT, DISABLED_RESULT]) | 
 |  | 
 |   def test_add_result_collection_default(self): | 
 |     """Tests add_result_collection default (merge crash info).""" | 
 |     collection = ResultCollection(test_results=[FAILED_RESULT]) | 
 |     self.assertFalse(collection.crashed) | 
 |     collection.append_crash_message('Crash1') | 
 |  | 
 |     crashed_collection = ResultCollection( | 
 |         test_results=[PASSED_RESULT], crashed=True) | 
 |     crashed_collection.append_crash_message('Crash2') | 
 |  | 
 |     collection.add_result_collection(crashed_collection) | 
 |     self.assertTrue(collection.crashed) | 
 |     self.assertEqual(collection.crash_message, 'Crash1\nCrash2') | 
 |     self.assertEqual(collection.test_results, [FAILED_RESULT, PASSED_RESULT]) | 
 |  | 
 |   def test_add_result_collection_overwrite(self): | 
 |     """Tests add_result_collection overwrite.""" | 
 |     collection = ResultCollection(test_results=[FAILED_RESULT], crashed=True) | 
 |     self.assertTrue(collection.crashed) | 
 |     collection.append_crash_message('Crash1') | 
 |  | 
 |     crashed_collection = ResultCollection(test_results=[PASSED_RESULT]) | 
 |  | 
 |     collection.add_result_collection(crashed_collection, overwrite_crash=True) | 
 |     self.assertFalse(collection.crashed) | 
 |     self.assertEqual(collection.crash_message, '') | 
 |     self.assertEqual(collection.test_results, [FAILED_RESULT, PASSED_RESULT]) | 
 |  | 
 |   def test_add_result_collection_ignore(self): | 
 |     """Tests add_result_collection overwrite.""" | 
 |     collection = ResultCollection(test_results=[FAILED_RESULT]) | 
 |     self.assertFalse(collection.crashed) | 
 |  | 
 |     crashed_collection = ResultCollection( | 
 |         test_results=[PASSED_RESULT], crashed=True) | 
 |     crashed_collection.append_crash_message('Crash2') | 
 |  | 
 |     collection.add_result_collection(crashed_collection, ignore_crash=True) | 
 |     self.assertFalse(collection.crashed) | 
 |     self.assertEqual(collection.crash_message, '') | 
 |     self.assertEqual(collection.test_results, [FAILED_RESULT, PASSED_RESULT]) | 
 |  | 
 |   def test_add_results(self): | 
 |     """Tests add_results.""" | 
 |     collection = ResultCollection(test_results=[PASSED_RESULT]) | 
 |     collection.add_results([FAILED_RESULT, DISABLED_RESULT]) | 
 |     self.assertEqual(collection.test_results, | 
 |                      [PASSED_RESULT, FAILED_RESULT, DISABLED_RESULT]) | 
 |  | 
 |   def test_add_name_prefix_to_tests(self): | 
 |     """Tests add_name_prefix_to_tests.""" | 
 |     passed = copy.copy(PASSED_RESULT) | 
 |     disabeld = copy.copy(DISABLED_RESULT) | 
 |     collection = ResultCollection(test_results=[passed, disabeld]) | 
 |     some_prefix = 'Some/prefix' | 
 |     collection.add_name_prefix_to_tests(some_prefix) | 
 |     for test_result in collection.test_results: | 
 |       self.assertTrue(test_result.name.startswith(some_prefix)) | 
 |  | 
 |   def test_add_test_names_status(self): | 
 |     """Tests add_test_names_status.""" | 
 |     test_names = ['test1', 'test2', 'test3'] | 
 |     collection = ResultCollection(test_results=[PASSED_RESULT]) | 
 |     collection.add_test_names_status(test_names, TestStatus.SKIP) | 
 |     disabled_test_names = ['test4', 'test5', 'test6'] | 
 |     collection.add_test_names_status( | 
 |         disabled_test_names, TestStatus.SKIP, expected_status=TestStatus.SKIP) | 
 |     self.assertEqual(collection.test_results[0], PASSED_RESULT) | 
 |     unexpected_skipped = collection.tests_by_expression( | 
 |         lambda t: not t.expected() and t.status == TestStatus.SKIP) | 
 |     self.assertEqual(unexpected_skipped, set(['test1', 'test2', 'test3'])) | 
 |     self.assertEqual(collection.disabled_tests(), | 
 |                      set(['test4', 'test5', 'test6'])) | 
 |  | 
 |   @mock.patch('test_result_util.TestResult.report_to_result_sink') | 
 |   @mock.patch('result_sink_util.ResultSinkClient.close') | 
 |   @mock.patch('result_sink_util.ResultSinkClient.__init__', return_value=None) | 
 |   def test_add_and_report_test_names_status(self, mock_sink_init, | 
 |                                             mock_sink_close, mock_report): | 
 |     """Tests add_test_names_status.""" | 
 |     test_names = ['test1', 'test2', 'test3'] | 
 |     collection = ResultCollection(test_results=[PASSED_RESULT]) | 
 |     collection.add_and_report_test_names_status(test_names, TestStatus.SKIP) | 
 |     self.assertEqual(collection.test_results[0], PASSED_RESULT) | 
 |     unexpected_skipped = collection.tests_by_expression( | 
 |         lambda t: not t.expected() and t.status == TestStatus.SKIP) | 
 |     self.assertEqual(unexpected_skipped, set(['test1', 'test2', 'test3'])) | 
 |     self.assertEqual(1, len(mock_sink_init.mock_calls)) | 
 |     self.assertEqual(3, len(mock_report.mock_calls)) | 
 |     self.assertEqual(1, len(mock_sink_close.mock_calls)) | 
 |  | 
 |   def testappend_crash_message(self): | 
 |     """Tests append_crash_message.""" | 
 |     collection = ResultCollection(test_results=[PASSED_RESULT]) | 
 |     collection.append_crash_message('Crash message 1.') | 
 |     self.assertEqual(collection.crash_message, 'Crash message 1.') | 
 |     collection.append_crash_message('Crash message 2.') | 
 |     self.assertEqual(collection.crash_message, | 
 |                      'Crash message 1.\nCrash message 2.') | 
 |  | 
 |   def test_tests_by_expression(self): | 
 |     """Tests tests_by_expression.""" | 
 |     collection = self.full_collection | 
 |     exp = lambda result: result.status == TestStatus.SKIP | 
 |     skipped_tests = collection.tests_by_expression(exp) | 
 |     self.assertEqual(skipped_tests, | 
 |                      set(['unexpected/skipped_test', 'disabled/test'])) | 
 |  | 
 |   def test_get_spcific_tests(self): | 
 |     """Tests getting sets of tests of specific status.""" | 
 |     collection = self.full_collection | 
 |     self.assertEqual( | 
 |         collection.all_test_names(), | 
 |         set([ | 
 |             'passed/test', 'disabled/test', 'failed/test', | 
 |             'unexpected/skipped_test', 'crashed/test', 'flaky/test', | 
 |             'aborted/test' | 
 |         ])) | 
 |     self.assertEqual(collection.crashed_tests(), set(['crashed/test'])) | 
 |     self.assertEqual(collection.disabled_tests(), set(['disabled/test'])) | 
 |     self.assertEqual(collection.expected_tests(), | 
 |                      set(['passed/test', 'disabled/test', 'flaky/test'])) | 
 |     self.assertEqual( | 
 |         collection.unexpected_tests(), | 
 |         set([ | 
 |             'failed/test', 'unexpected/skipped_test', 'crashed/test', | 
 |             'flaky/test', 'aborted/test' | 
 |         ])) | 
 |     self.assertEqual(collection.passed_tests(), | 
 |                      set(['passed/test', 'flaky/test'])) | 
 |     self.assertEqual(collection.failed_tests(), | 
 |                      set(['failed/test', 'flaky/test'])) | 
 |     self.assertEqual(collection.flaky_tests(), set(['flaky/test'])) | 
 |     self.assertEqual( | 
 |         collection.never_expected_tests(), | 
 |         set([ | 
 |             'failed/test', 'unexpected/skipped_test', 'crashed/test', | 
 |             'aborted/test' | 
 |         ])) | 
 |     self.assertEqual(collection.pure_expected_tests(), | 
 |                      set(['passed/test', 'disabled/test'])) | 
 |  | 
 |   def test_add_and_report_crash(self): | 
 |     """Tests add_and_report_crash.""" | 
 |     collection = copy.copy(self.full_collection) | 
 |  | 
 |     collection.set_crashed_with_prefix('Prefix Line') | 
 |     self.assertEqual(collection.crash_message, 'Prefix Line\n') | 
 |     self.assertTrue(collection.crashed) | 
 |  | 
 |   @mock.patch('test_result_util.TestResult.report_to_result_sink') | 
 |   @mock.patch('result_sink_util.ResultSinkClient.close') | 
 |   @mock.patch('result_sink_util.ResultSinkClient.__init__', return_value=None) | 
 |   def test_report_to_result_sink(self, mock_sink_init, mock_sink_close, | 
 |                                  mock_report): | 
 |     """Tests report_to_result_sink.""" | 
 |     collection = copy.copy(self.full_collection) | 
 |     collection.report_to_result_sink() | 
 |  | 
 |     mock_sink_init.assert_called_once() | 
 |     self.assertEqual(len(collection.test_results), len(mock_report.mock_calls)) | 
 |     mock_sink_close.assert_called() | 
 |  | 
 |   @mock.patch('shard_util.gtest_shard_index', return_value=0) | 
 |   @mock.patch('time.time', return_value=10000) | 
 |   def test_standard_json_output(self, *args): | 
 |     """Tests standard_json_output.""" | 
 |     passed_test_value = { | 
 |         'expected': 'PASS', | 
 |         'actual': 'PASS', | 
 |         'shard': 0, | 
 |         'is_unexpected': False | 
 |     } | 
 |     failed_test_value = { | 
 |         'expected': 'PASS', | 
 |         'actual': 'FAIL FAIL', | 
 |         'shard': 0, | 
 |         'is_unexpected': True | 
 |     } | 
 |     disabled_test_value = { | 
 |         'expected': 'SKIP', | 
 |         'actual': 'SKIP', | 
 |         'shard': 0, | 
 |         'is_unexpected': False | 
 |     } | 
 |     unexpected_skip_test_value = { | 
 |         'expected': 'PASS', | 
 |         'actual': 'SKIP', | 
 |         'shard': 0, | 
 |         'is_unexpected': True | 
 |     } | 
 |     crashed_test_value = { | 
 |         'expected': 'PASS', | 
 |         'actual': 'CRASH', | 
 |         'shard': 0, | 
 |         'is_unexpected': True | 
 |     } | 
 |     flaky_test_value = { | 
 |         'expected': 'PASS', | 
 |         'actual': 'PASS FAIL', | 
 |         'shard': 0, | 
 |         'is_unexpected': False, | 
 |         'is_flaky': True | 
 |     } | 
 |     aborted_test_value = { | 
 |         'expected': 'PASS', | 
 |         'actual': 'TIMEOUT', | 
 |         'shard': 0, | 
 |         'is_unexpected': True | 
 |     } | 
 |     expected_tests = collections.OrderedDict() | 
 |     expected_tests['passed/test'] = passed_test_value | 
 |     expected_tests['failed/test'] = failed_test_value | 
 |     expected_tests['disabled/test'] = disabled_test_value | 
 |     expected_tests['unexpected/skipped_test'] = unexpected_skip_test_value | 
 |     expected_tests['crashed/test'] = crashed_test_value | 
 |     expected_tests['flaky/test'] = flaky_test_value | 
 |     expected_tests['aborted/test'] = aborted_test_value | 
 |     expected_num_failures_by_type = { | 
 |         'PASS': 2, | 
 |         'FAIL': 1, | 
 |         'CRASH': 1, | 
 |         'SKIP': 2, | 
 |         'TIMEOUT': 1 | 
 |     } | 
 |     expected_json = { | 
 |         'version': 3, | 
 |         'path_delimiter': '/', | 
 |         'seconds_since_epoch': 10000, | 
 |         'interrupted': False, | 
 |         'num_failures_by_type': expected_num_failures_by_type, | 
 |         'tests': expected_tests | 
 |     } | 
 |     self.assertEqual( | 
 |         self.full_collection.standard_json_output(path_delimiter='/'), | 
 |         expected_json) | 
 |  | 
 |   def test_test_runner_logs(self): | 
 |     """Test test_runner_logs.""" | 
 |     expected_logs = collections.OrderedDict() | 
 |     expected_logs['passed tests'] = ['passed/test'] | 
 |     expected_logs['disabled tests'] = ['disabled/test'] | 
 |     flaky_logs = ['Failure log of attempt 1:', 'line1', 'line2'] | 
 |     failed_logs = [ | 
 |         'Failure log of attempt 1:', 'line1', 'line2', | 
 |         'Failure log of attempt 2:', 'line3', 'line4' | 
 |     ] | 
 |     no_logs = ['Failure log of attempt 1:', ''] | 
 |     expected_logs['flaked tests'] = {'flaky/test': flaky_logs} | 
 |     expected_logs['failed tests'] = { | 
 |         'failed/test': failed_logs, | 
 |         'crashed/test': no_logs, | 
 |         'unexpected/skipped_test': no_logs, | 
 |         'aborted/test': no_logs | 
 |     } | 
 |     expected_logs['failed/test'] = failed_logs | 
 |     expected_logs['unexpected/skipped_test'] = no_logs | 
 |     expected_logs['flaky/test'] = flaky_logs | 
 |     expected_logs['crashed/test'] = no_logs | 
 |     expected_logs['aborted/test'] = no_logs | 
 |     generated_logs = self.full_collection.test_runner_logs() | 
 |     keys = [ | 
 |         'passed tests', 'disabled tests', 'flaked tests', 'failed tests', | 
 |         'failed/test', 'unexpected/skipped_test', 'flaky/test', 'crashed/test', | 
 |         'aborted/test' | 
 |     ] | 
 |     for key in keys: | 
 |       self.assertEqual(generated_logs[key], expected_logs[key]) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |   unittest.main() |