| #!/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() |