|
17 | 17 | """ |
18 | 18 |
|
19 | 19 | import xml.etree.ElementTree as ET |
| 20 | +from collections.abc import Callable |
20 | 21 | from pathlib import Path |
21 | 22 | from typing import Any |
22 | 23 |
|
|
30 | 31 |
|
31 | 32 |
|
32 | 33 | # Unsure if I should make these last a session or not |
| 34 | +def _write_test_xml( |
| 35 | + path: Path, |
| 36 | + name: str, |
| 37 | + result: str = "", |
| 38 | + props: dict[str, str] | None = None, |
| 39 | + file: str = "", |
| 40 | + line: int = 0, |
| 41 | +): |
| 42 | + """Helper to create the XML structure for a test case.""" |
| 43 | + ts = ET.Element("testsuites") |
| 44 | + suite = ET.SubElement(ts, "testsuite") |
| 45 | + |
| 46 | + # Create testcase with attributes |
| 47 | + tc_attrs = {"name": name} |
| 48 | + if file: |
| 49 | + tc_attrs["file"] = file |
| 50 | + if line: |
| 51 | + tc_attrs["line"] = str(line) |
| 52 | + tc = ET.SubElement(suite, "testcase", tc_attrs) |
| 53 | + |
| 54 | + # Add failure/skipped status |
| 55 | + if result == "failed": |
| 56 | + ET.SubElement(tc, "failure", {"message": "failmsg"}) |
| 57 | + elif result == "skipped": |
| 58 | + ET.SubElement(tc, "skipped", {"message": "skipmsg"}) |
| 59 | + |
| 60 | + # Add properties if provided |
| 61 | + if props: |
| 62 | + props_el = ET.SubElement(tc, "properties") |
| 63 | + for k, v in props.items(): |
| 64 | + ET.SubElement(props_el, "property", {"name": k, "value": v}) |
| 65 | + |
| 66 | + # Save to file |
| 67 | + ET.ElementTree(ts).write(path, encoding="utf-8", xml_declaration=True) |
| 68 | + |
| 69 | + |
33 | 70 | @pytest.fixture |
34 | | -def tmp_xml_dirs(tmp_path: Path) -> tuple[Path, Path, Path]: |
35 | | - root: Path = tmp_path / "bazel-testlogs" |
36 | | - dir1: Path = root / "with_props" |
37 | | - dir2: Path = root / "no_props" |
38 | | - dir1.mkdir(parents=True) |
39 | | - dir2.mkdir(parents=True) |
40 | | - |
41 | | - def write(file_path: Path, testcases: list[ET.Element]): |
42 | | - ts = ET.Element("testsuites") |
43 | | - suite = ET.SubElement(ts, "testsuite") |
44 | | - for tc in testcases: |
45 | | - suite.append(tc) |
46 | | - tree = ET.ElementTree(ts) |
47 | | - tree.write(file_path, encoding="utf-8", xml_declaration=True) |
48 | | - |
49 | | - def make_tc( |
50 | | - name: str, |
51 | | - result: str = "", |
52 | | - props: dict[str, str] | None = None, |
53 | | - file: str = "", |
54 | | - line: int = 0, |
55 | | - ): |
56 | | - tc = ET.Element("testcase", {"name": name}) |
57 | | - if file: |
58 | | - tc.set("file", file) |
59 | | - if line: |
60 | | - tc.set("line", str(line)) |
61 | | - if result == "failed": |
62 | | - ET.SubElement(tc, "failure", {"message": "failmsg"}) |
63 | | - elif result == "skipped": |
64 | | - ET.SubElement(tc, "skipped", {"message": "skipmsg"}) |
65 | | - if props: |
66 | | - props_el = ET.SubElement(tc, "properties") |
67 | | - for k, v in props.items(): |
68 | | - ET.SubElement(props_el, "property", {"name": k, "value": v}) |
69 | | - return tc |
70 | | - |
71 | | - # File with properties |
72 | | - tc1 = make_tc( |
73 | | - "tc_with_props", |
74 | | - result="failed", |
75 | | - props={ |
76 | | - "PartiallyVerifies": "REQ1", |
77 | | - "FullyVerifies": "", |
78 | | - "TestType": "type", |
79 | | - "DerivationTechnique": "tech", |
80 | | - "Description": "desc", |
81 | | - }, |
82 | | - file="path1", |
83 | | - line=10, |
84 | | - ) |
85 | | - write(dir1 / "test.xml", [tc1]) |
86 | | - |
87 | | - # File without properties |
88 | | - # HINT: Once the assertions in xml_parser are back and active, this should allow us |
89 | | - # to catch that the tests Need to be changed too. |
90 | | - tc2 = make_tc("tc_no_props", file="path2", line=20) |
91 | | - write(dir2 / "test.xml", [tc2]) |
92 | | - |
93 | | - return root, dir1, dir2 |
| 71 | +def tmp_xml_dirs(tmp_path: Path) -> Callable[..., tuple[Path, Path, Path]]: |
| 72 | + def _tmp_xml_dirs(test_folder: str = "bazel-testlogs") -> tuple[Path, Path, Path]: |
| 73 | + root = tmp_path / test_folder |
| 74 | + dir1, dir2 = root / "with_props", root / "no_props" |
| 75 | + |
| 76 | + for d in (dir1, dir2): |
| 77 | + d.mkdir(parents=True, exist_ok=True) |
| 78 | + |
| 79 | + # File with properties |
| 80 | + _write_test_xml( |
| 81 | + dir1 / "test.xml", |
| 82 | + name="tc_with_props", |
| 83 | + result="failed", |
| 84 | + file="path1", |
| 85 | + line=10, |
| 86 | + props={ |
| 87 | + "PartiallyVerifies": "REQ1", |
| 88 | + "FullyVerifies": "", |
| 89 | + "TestType": "type", |
| 90 | + "DerivationTechnique": "tech", |
| 91 | + "Description": "desc", |
| 92 | + }, |
| 93 | + ) |
| 94 | + |
| 95 | + # File without properties |
| 96 | + _write_test_xml(dir2 / "test.xml", name="tc_no_props", file="path2", line=20) |
| 97 | + |
| 98 | + return root, dir1, dir2 |
| 99 | + |
| 100 | + return _tmp_xml_dirs |
94 | 101 |
|
95 | 102 |
|
96 | 103 | @add_test_properties( |
97 | 104 | partially_verifies=["tool_req__docs_test_link_testcase"], |
98 | 105 | test_type="requirements-based", |
99 | 106 | derivation_technique="requirements-analysis", |
100 | 107 | ) |
101 | | -def test_find_xml_files(tmp_xml_dirs: tuple[Path, Path, Path]): |
102 | | - """Ensure xml files are found as expected""" |
| 108 | +def test_find_xml_files(tmp_xml_dirs: Callable[..., tuple[Path, Path, Path]]): |
| 109 | + """Ensure xml files are found as expected if bazel-testlogs is used""" |
103 | 110 | root: Path |
104 | 111 | dir1: Path |
105 | 112 | dir2: Path |
106 | | - root, dir1, dir2 = tmp_xml_dirs |
| 113 | + root, dir1, dir2 = tmp_xml_dirs() |
107 | 114 | found = xml_parser.find_xml_files(root) |
108 | 115 | expected: set[Path] = {dir1 / "test.xml", dir2 / "test.xml"} |
109 | 116 | assert set(found) == expected |
110 | 117 |
|
111 | 118 |
|
| 119 | +def test_find_xml_folder(tmp_xml_dirs: Callable[..., tuple[Path, Path, Path]]): |
| 120 | + """Ensure xml files are found as expected if bazel-testlogs is used""" |
| 121 | + root: Path |
| 122 | + root, _, _ = tmp_xml_dirs() |
| 123 | + found = xml_parser.find_test_folder(base_path=root.parent) |
| 124 | + assert found is not None |
| 125 | + assert found == root |
| 126 | + |
| 127 | + |
| 128 | +def test_find_xml_folder_test_reports( |
| 129 | + tmp_xml_dirs: Callable[..., tuple[Path, Path, Path]], |
| 130 | +): |
| 131 | + # root is the 'tests-report' folder inside tmp_path |
| 132 | + root, _, _ = tmp_xml_dirs(test_folder="tests-report") |
| 133 | + # We pass the PARENT of 'tests-report' as the workspace root |
| 134 | + found = xml_parser.find_test_folder(base_path=root.parent) |
| 135 | + assert found is not None |
| 136 | + assert found == root |
| 137 | + |
| 138 | + |
| 139 | +def test_find_xml_files_test_reports( |
| 140 | + tmp_xml_dirs: Callable[..., tuple[Path, Path, Path]], |
| 141 | +): |
| 142 | + """Ensure xml files are found as expected if tests-report is used""" |
| 143 | + root: Path |
| 144 | + dir1: Path |
| 145 | + dir2: Path |
| 146 | + root, dir1, dir2 = tmp_xml_dirs(test_folder="tests-report") |
| 147 | + found = xml_parser.find_xml_files(dir=root) |
| 148 | + assert found is not None |
| 149 | + expected: set[Path] = {root / dir1 / "test.xml", root / dir2 / "test.xml"} |
| 150 | + assert set(found) == expected |
| 151 | + |
| 152 | + |
| 153 | +def test_early_return(tmp_path: Path): |
| 154 | + """ |
| 155 | + Ensure that if tests-report & bazel-testlogs is not found, |
| 156 | + we return None for early return inside extension |
| 157 | + """ |
| 158 | + # Move the test execution context to a 100% empty folder |
| 159 | + |
| 160 | + found = xml_parser.find_test_folder(tmp_path) |
| 161 | + assert found is None |
| 162 | + |
| 163 | + |
112 | 164 | @add_test_properties( |
113 | 165 | partially_verifies=["tool_req__docs_test_link_testcase"], |
114 | 166 | test_type="requirements-based", |
@@ -152,12 +204,12 @@ def test_parse_properties(): |
152 | 204 | test_type="requirements-based", |
153 | 205 | derivation_technique="requirements-analysis", |
154 | 206 | ) |
155 | | -def test_read_test_xml_file(tmp_xml_dirs: tuple[Path, Path, Path]): |
| 207 | +def test_read_test_xml_file(tmp_xml_dirs: Callable[..., tuple[Path, Path, Path]]): |
156 | 208 | """Ensure a whole pre-defined xml file is parsed correctly""" |
157 | 209 | _: Path |
158 | 210 | dir1: Path |
159 | 211 | dir2: Path |
160 | | - _, dir1, dir2 = tmp_xml_dirs |
| 212 | + _, dir1, dir2 = tmp_xml_dirs() |
161 | 213 |
|
162 | 214 | needs1, no_props1 = xml_parser.read_test_xml_file(dir1 / "test.xml") |
163 | 215 | assert isinstance(needs1, list) and len(needs1) == 1 |
|
0 commit comments