|
1 | 1 | """Tests for auto_mark.py - test result parsing and auto-marking.""" |
2 | 2 |
|
3 | 3 | import ast |
| 4 | +import pathlib |
4 | 5 | import subprocess |
| 6 | +import tempfile |
5 | 7 | import unittest |
| 8 | +from unittest import mock |
6 | 9 |
|
7 | 10 | from update_lib.cmd_auto_mark import ( |
8 | 11 | Test, |
9 | 12 | TestResult, |
| 13 | + TestRunError, |
10 | 14 | _expand_stripped_to_children, |
11 | 15 | _is_super_call_only, |
12 | 16 | apply_test_changes, |
| 17 | + auto_mark_directory, |
| 18 | + auto_mark_file, |
13 | 19 | collect_test_changes, |
14 | 20 | extract_test_methods, |
15 | 21 | parse_results, |
@@ -94,6 +100,34 @@ def test_parse_tests_result(self): |
94 | 100 | result = parse_results(_make_result("== Tests result: FAILURE ==\n")) |
95 | 101 | self.assertEqual(result.tests_result, "FAILURE") |
96 | 102 |
|
| 103 | + def test_parse_crashed_run_no_tests_result(self): |
| 104 | + """Test results are still parsed when the runner crashes (no Tests result line).""" |
| 105 | + stdout = """\ |
| 106 | +Run 1 test sequentially in a single process |
| 107 | +0:00:00 [1/1] test_ast |
| 108 | +test_foo (test.test_ast.test_ast.TestA.test_foo) ... FAIL |
| 109 | +test_bar (test.test_ast.test_ast.TestA.test_bar) ... ok |
| 110 | +test_baz (test.test_ast.test_ast.TestB.test_baz) ... ERROR |
| 111 | +""" |
| 112 | + result = parse_results(_make_result(stdout)) |
| 113 | + self.assertEqual(result.tests_result, "") |
| 114 | + self.assertEqual(len(result.tests), 2) |
| 115 | + names = {t.name for t in result.tests} |
| 116 | + self.assertIn("test_foo", names) |
| 117 | + self.assertIn("test_baz", names) |
| 118 | + |
| 119 | + def test_parse_crashed_run_has_unexpected_success(self): |
| 120 | + """Unexpected successes are parsed even without Tests result line.""" |
| 121 | + stdout = """\ |
| 122 | +Run 1 test sequentially in a single process |
| 123 | +0:00:00 [1/1] test_ast |
| 124 | +test_foo (test.test_ast.test_ast.TestA.test_foo) ... unexpected success |
| 125 | +UNEXPECTED SUCCESS: test_foo (test.test_ast.test_ast.TestA.test_foo) |
| 126 | +""" |
| 127 | + result = parse_results(_make_result(stdout)) |
| 128 | + self.assertEqual(result.tests_result, "") |
| 129 | + self.assertEqual(len(result.unexpected_successes), 1) |
| 130 | + |
97 | 131 | def test_parse_error_messages(self): |
98 | 132 | """Single and multiple error messages are parsed from tracebacks.""" |
99 | 133 | stdout = """\ |
@@ -747,5 +781,152 @@ async def test_one(self): |
747 | 781 | ) |
748 | 782 |
|
749 | 783 |
|
| 784 | +class TestAutoMarkFileWithCrashedRun(unittest.TestCase): |
| 785 | + """auto_mark_file should process partial results when test runner crashes.""" |
| 786 | + |
| 787 | + CRASHED_STDOUT = """\ |
| 788 | +Run 1 test sequentially in a single process |
| 789 | +0:00:00 [1/1] test_example |
| 790 | +test_foo (test.test_example.TestA.test_foo) ... FAIL |
| 791 | +test_bar (test.test_example.TestA.test_bar) ... ok |
| 792 | +====================================================================== |
| 793 | +FAIL: test_foo (test.test_example.TestA.test_foo) |
| 794 | +---------------------------------------------------------------------- |
| 795 | +Traceback (most recent call last): |
| 796 | + File "test.py", line 10, in test_foo |
| 797 | + self.assertEqual(1, 2) |
| 798 | +AssertionError: 1 != 2 |
| 799 | +""" |
| 800 | + |
| 801 | + def test_auto_mark_file_crashed_run(self): |
| 802 | + """auto_mark_file processes results even when tests_result is empty (crash).""" |
| 803 | + test_code = f"""import unittest |
| 804 | +
|
| 805 | +class TestA(unittest.TestCase): |
| 806 | + def test_foo(self): |
| 807 | + pass |
| 808 | +
|
| 809 | + def test_bar(self): |
| 810 | + pass |
| 811 | +""" |
| 812 | + with tempfile.TemporaryDirectory() as tmpdir: |
| 813 | + test_file = pathlib.Path(tmpdir) / "test_example.py" |
| 814 | + test_file.write_text(test_code) |
| 815 | + |
| 816 | + mock_result = TestResult() |
| 817 | + mock_result.tests_result = "" |
| 818 | + mock_result.tests = [ |
| 819 | + Test( |
| 820 | + name="test_foo", |
| 821 | + path="test.test_example.TestA.test_foo", |
| 822 | + result="fail", |
| 823 | + error_message="AssertionError: 1 != 2", |
| 824 | + ), |
| 825 | + ] |
| 826 | + |
| 827 | + with mock.patch( |
| 828 | + "update_lib.cmd_auto_mark.run_test", return_value=mock_result |
| 829 | + ): |
| 830 | + added, removed, regressions = auto_mark_file( |
| 831 | + test_file, mark_failure=True, verbose=False |
| 832 | + ) |
| 833 | + |
| 834 | + self.assertEqual(added, 1) |
| 835 | + contents = test_file.read_text() |
| 836 | + self.assertIn("expectedFailure", contents) |
| 837 | + |
| 838 | + def test_auto_mark_file_no_results_at_all_raises(self): |
| 839 | + """auto_mark_file raises TestRunError when there are zero parsed results.""" |
| 840 | + test_code = """import unittest |
| 841 | +
|
| 842 | +class TestA(unittest.TestCase): |
| 843 | + def test_foo(self): |
| 844 | + pass |
| 845 | +""" |
| 846 | + with tempfile.TemporaryDirectory() as tmpdir: |
| 847 | + test_file = pathlib.Path(tmpdir) / "test_example.py" |
| 848 | + test_file.write_text(test_code) |
| 849 | + |
| 850 | + mock_result = TestResult() |
| 851 | + mock_result.tests_result = "" |
| 852 | + mock_result.tests = [] |
| 853 | + mock_result.stdout = "some crash output" |
| 854 | + |
| 855 | + with mock.patch( |
| 856 | + "update_lib.cmd_auto_mark.run_test", return_value=mock_result |
| 857 | + ): |
| 858 | + with self.assertRaises(TestRunError): |
| 859 | + auto_mark_file(test_file, verbose=False) |
| 860 | + |
| 861 | + |
| 862 | +class TestAutoMarkDirectoryWithCrashedRun(unittest.TestCase): |
| 863 | + """auto_mark_directory should process partial results when test runner crashes.""" |
| 864 | + |
| 865 | + def test_auto_mark_directory_crashed_run(self): |
| 866 | + """auto_mark_directory processes results even when tests_result is empty.""" |
| 867 | + test_code = f"""import unittest |
| 868 | +
|
| 869 | +class TestA(unittest.TestCase): |
| 870 | + def test_foo(self): |
| 871 | + pass |
| 872 | +""" |
| 873 | + with tempfile.TemporaryDirectory() as tmpdir: |
| 874 | + test_dir = pathlib.Path(tmpdir) / "test_example" |
| 875 | + test_dir.mkdir() |
| 876 | + test_file = test_dir / "test_sub.py" |
| 877 | + test_file.write_text(test_code) |
| 878 | + |
| 879 | + mock_result = TestResult() |
| 880 | + mock_result.tests_result = "" |
| 881 | + mock_result.tests = [ |
| 882 | + Test( |
| 883 | + name="test_foo", |
| 884 | + path="test.test_example.test_sub.TestA.test_foo", |
| 885 | + result="fail", |
| 886 | + error_message="AssertionError: oops", |
| 887 | + ), |
| 888 | + ] |
| 889 | + |
| 890 | + with mock.patch( |
| 891 | + "update_lib.cmd_auto_mark.run_test", return_value=mock_result |
| 892 | + ), mock.patch( |
| 893 | + "update_lib.cmd_auto_mark.get_test_module_name", |
| 894 | + side_effect=lambda p: ( |
| 895 | + "test_example" |
| 896 | + if p == test_dir |
| 897 | + else "test_example.test_sub" |
| 898 | + ), |
| 899 | + ): |
| 900 | + added, removed, regressions = auto_mark_directory( |
| 901 | + test_dir, mark_failure=True, verbose=False |
| 902 | + ) |
| 903 | + |
| 904 | + self.assertEqual(added, 1) |
| 905 | + contents = test_file.read_text() |
| 906 | + self.assertIn("expectedFailure", contents) |
| 907 | + |
| 908 | + def test_auto_mark_directory_no_results_raises(self): |
| 909 | + """auto_mark_directory raises TestRunError when zero results.""" |
| 910 | + with tempfile.TemporaryDirectory() as tmpdir: |
| 911 | + test_dir = pathlib.Path(tmpdir) / "test_example" |
| 912 | + test_dir.mkdir() |
| 913 | + test_file = test_dir / "test_sub.py" |
| 914 | + test_file.write_text("import unittest\n") |
| 915 | + |
| 916 | + mock_result = TestResult() |
| 917 | + mock_result.tests_result = "" |
| 918 | + mock_result.tests = [] |
| 919 | + mock_result.stdout = "crash" |
| 920 | + |
| 921 | + with mock.patch( |
| 922 | + "update_lib.cmd_auto_mark.run_test", return_value=mock_result |
| 923 | + ), mock.patch( |
| 924 | + "update_lib.cmd_auto_mark.get_test_module_name", |
| 925 | + return_value="test_example", |
| 926 | + ): |
| 927 | + with self.assertRaises(TestRunError): |
| 928 | + auto_mark_directory(test_dir, verbose=False) |
| 929 | + |
| 930 | + |
750 | 931 | if __name__ == "__main__": |
751 | 932 | unittest.main() |
0 commit comments