Skip to content

Commit 878c217

Browse files
committed
Refactor the data model of pytest metadata
1 parent 1112203 commit 878c217

File tree

4 files changed

+92
-60
lines changed

4 files changed

+92
-60
lines changed

launchable/test_runners/pytest.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import pathlib
55
import subprocess
6+
from enum import Enum
67
from typing import Generator, List
78

89
import click
@@ -125,6 +126,52 @@ def _pytest_formatter(test_path):
125126
split_subset = launchable.CommonSplitSubsetImpls(__name__, formatter=_pytest_formatter).split_subset()
126127

127128

129+
class ReportKind(Enum):
130+
JSON = 0
131+
XML = 1
132+
133+
134+
def _parse_markers(props: List, kind: ReportKind) -> List[dict]:
135+
"""
136+
Parse marker properties from either XML properties or JSON user_properties format.
137+
"""
138+
markers = []
139+
marker = {}
140+
141+
for prop in props:
142+
if kind == ReportKind.JSON:
143+
# JSON format: prop is a list like ["name", "value"]
144+
if not isinstance(prop, list) or len(prop) != 2:
145+
continue
146+
prop_name, prop_value = prop[0], prop[1]
147+
else:
148+
# XML format: prop has .name and .value attributes
149+
prop_name, prop_value = prop.name, prop.value
150+
151+
if prop_name == "name":
152+
marker["name"] = prop_value
153+
elif prop_name == "args":
154+
if isinstance(prop_value, str):
155+
if prop_value not in ("()", "{}"):
156+
marker["value"] = prop_value
157+
else:
158+
if prop_value not in ((), {}):
159+
marker["value"] = json.dumps(prop_value)
160+
elif prop_name == "kwargs":
161+
if isinstance(prop_value, str):
162+
if prop_value not in ("()", "{}"):
163+
marker["value"] = prop_value
164+
else:
165+
if prop_value not in ((), {}):
166+
marker["value"] = json.dumps(prop_value)
167+
if "value" not in marker:
168+
marker["value"] = ""
169+
markers.append(marker)
170+
marker = {}
171+
172+
return markers
173+
174+
128175
@click.option('--json', 'json_report', help="use JSON report files produced by pytest-dev/pytest-reportlog",
129176
is_flag=True)
130177
@click.argument('source_roots', required=True, nargs=-1)
@@ -148,7 +195,7 @@ def data_builder(case: TestCase):
148195
</properties>
149196
```
150197
"""
151-
markers = [{"name": prop.name, "value": prop.value} for prop in props]
198+
markers = _parse_markers(props, kind=ReportKind.XML)
152199
result["markers"] = markers if markers else []
153200

154201
metadata = MetadataTestCase.fromelem(case)
@@ -312,15 +359,7 @@ def parse_func(
312359
"""
313360
props = data.get('user_properties')
314361
if isinstance(props, list):
315-
markers = []
316-
for prop in props:
317-
if isinstance(prop, list) and len(prop) == 2:
318-
# prop is like ["name", "value"]
319-
# prop[0] is name, prop[1] is value
320-
if isinstance(prop[1], str):
321-
markers.append({"name": prop[0], "value": prop[1]})
322-
else:
323-
markers.append({"name": prop[0], "value": json.dumps(prop[1])})
362+
markers = _parse_markers(props, kind=ReportKind.JSON)
324363
if len(props) > 0:
325364
props = {'markers': markers}
326365
else:

tests/data/pytest/record_test_result.json

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,7 @@
6464
"stdout": "",
6565
"stderr": "",
6666
"data": {
67-
"markers": [
68-
{ "name": "name", "value": "foo" },
69-
{ "name": "args", "value": "()" },
70-
{ "name": "kwargs", "value": "{}" }
71-
],
67+
"markers": [{ "name": "foo", "value": "" }],
7268
"lineNumber": 3
7369
}
7470
},
@@ -84,11 +80,7 @@
8480
"stdout": "",
8581
"stderr": "@pytest.mark.bar\n def test_func2():\n > assert 1 == False # noqa: E712\n E assert 1 == False\n\n tests/test_funcs1.py:9: AssertionError",
8682
"data": {
87-
"markers": [
88-
{ "name": "name", "value": "bar" },
89-
{ "name": "args", "value": "()" },
90-
{ "name": "kwargs", "value": "{}" }
91-
],
83+
"markers": [{ "name": "bar", "value": "" }],
9284
"lineNumber": 7
9385
}
9486
},
@@ -105,12 +97,8 @@
10597
"stderr": "x = 0, y = 2\n\n @pytest.mark.parametrize(\"x\", [0, 1])\n @pytest.mark.parametrize(\"y\", [2, 3])\n def test_foo(x, y):\n > assert x == 1\n E assert 0 == 1\n\n tests/test_funcs1.py:15: AssertionError",
10698
"data": {
10799
"markers": [
108-
{ "name": "name", "value": "parametrize" },
109-
{ "name": "args", "value": "('y', [2, 3])" },
110-
{ "name": "kwargs", "value": "{}" },
111-
{ "name": "name", "value": "parametrize" },
112-
{ "name": "args", "value": "('x', [0, 1])" },
113-
{ "name": "kwargs", "value": "{}" }
100+
{ "name": "parametrize", "value": "('x', [0, 1])" },
101+
{ "name": "parametrize", "value": "('y', [2, 3])" }
114102
],
115103
"lineNumber": 12
116104
}
@@ -128,12 +116,8 @@
128116
"stderr": "",
129117
"data": {
130118
"markers": [
131-
{ "name": "name", "value": "parametrize" },
132-
{ "name": "args", "value": "('y', [2, 3])" },
133-
{ "name": "kwargs", "value": "{}" },
134-
{ "name": "name", "value": "parametrize" },
135-
{ "name": "args", "value": "('x', [0, 1])" },
136-
{ "name": "kwargs", "value": "{}" }
119+
{ "name": "parametrize", "value": "('x', [0, 1])" },
120+
{ "name": "parametrize", "value": "('y', [2, 3])" }
137121
],
138122
"lineNumber": 12
139123
}
@@ -151,12 +135,8 @@
151135
"stderr": "x = 0, y = 3\n\n @pytest.mark.parametrize(\"x\", [0, 1])\n @pytest.mark.parametrize(\"y\", [2, 3])\n def test_foo(x, y):\n > assert x == 1\n E assert 0 == 1\n\n tests/test_funcs1.py:15: AssertionError",
152136
"data": {
153137
"markers": [
154-
{ "name": "name", "value": "parametrize" },
155-
{ "name": "args", "value": "('y', [2, 3])" },
156-
{ "name": "kwargs", "value": "{}" },
157-
{ "name": "name", "value": "parametrize" },
158-
{ "name": "args", "value": "('x', [0, 1])" },
159-
{ "name": "kwargs", "value": "{}" }
138+
{ "name": "parametrize", "value": "('x', [0, 1])" },
139+
{ "name": "parametrize", "value": "('y', [2, 3])" }
160140
],
161141
"lineNumber": 12
162142
}
@@ -174,12 +154,8 @@
174154
"stderr": "",
175155
"data": {
176156
"markers": [
177-
{ "name": "name", "value": "parametrize" },
178-
{ "name": "args", "value": "('y', [2, 3])" },
179-
{ "name": "kwargs", "value": "{}" },
180-
{ "name": "name", "value": "parametrize" },
181-
{ "name": "args", "value": "('x', [0, 1])" },
182-
{ "name": "kwargs", "value": "{}" }
157+
{ "name": "parametrize", "value": "('x', [0, 1])" },
158+
{ "name": "parametrize", "value": "('y', [2, 3])" }
183159
],
184160
"lineNumber": 12
185161
}
@@ -196,11 +172,7 @@
196172
"stdout": "",
197173
"stderr": "",
198174
"data": {
199-
"markers": [
200-
{ "name": "name", "value": "foo" },
201-
{ "name": "args", "value": "()" },
202-
{ "name": "kwargs", "value": "{}" }
203-
],
175+
"markers": [{ "name": "foo", "value": "" }],
204176
"lineNumber": 3
205177
}
206178
},
@@ -216,11 +188,7 @@
216188
"stdout": "",
217189
"stderr": "",
218190
"data": {
219-
"markers": [
220-
{ "name": "name", "value": "bar" },
221-
{ "name": "args", "value": "()" },
222-
{ "name": "kwargs", "value": "{}" }
223-
],
191+
"markers": [{ "name": "bar", "value": "" }],
224192
"lineNumber": 8
225193
}
226194
}

tests/data/pytest/record_test_result_json.json

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@
2020
"status": 1,
2121
"stdout": "",
2222
"stderr": "",
23-
"data": null
23+
"data": {
24+
"markers": [
25+
{
26+
"name": "parametrize",
27+
"value": "[\"x\", [0, 1]]"
28+
},
29+
{
30+
"name": "slow",
31+
"value": "[]"
32+
}
33+
]
34+
}
2435
},
2536
{
2637
"type": "case",
@@ -64,7 +75,14 @@
6475
"status": 1,
6576
"stdout": "",
6677
"stderr": "",
67-
"data": null
78+
"data": {
79+
"markers": [
80+
{
81+
"name": "dependency",
82+
"value": "{\"name\": \"test1\", \"depends\": [\"test0\"]}"
83+
}
84+
]
85+
}
6886
},
6987
{
7088
"type": "case",
@@ -108,7 +126,14 @@
108126
"status": 1,
109127
"stdout": "",
110128
"stderr": "",
111-
"data": null
129+
"data": {
130+
"markers": [
131+
{
132+
"name": "order",
133+
"value": "[2]"
134+
}
135+
]
136+
}
112137
},
113138
{
114139
"type": "case",

tests/data/pytest/report.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@
66
{"nodeid": "tests/data/pytest/tests/fooo/func4_test.py", "outcome": "passed", "longrepr": null, "result": null, "sections": [], "$report_type": "CollectReport"}
77
{"nodeid": "tests/data/pytest/tests/fooo/__init__.py", "outcome": "passed", "longrepr": null, "result": null, "sections": [], "$report_type": "CollectReport"}
88
{"nodeid": "tests/data/pytest/tests/funcs3_test.py::test_func4", "location": ["tests/data/pytest/tests/funcs3_test.py", 0, "test_func4"], "keywords": {"tests/data/pytest/tests/funcs3_test.py": 1, "test_func4": 1, "cli": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.05127562699999999, "$report_type": "TestReport"}
9-
{"nodeid": "tests/data/pytest/tests/funcs3_test.py::test_func4", "location": ["tests/data/pytest/tests/funcs3_test.py", 0, "test_func4"], "keywords": {"tests/data/pytest/tests/funcs3_test.py": 1, "test_func4": 1, "cli": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 0.001, "$report_type": "TestReport"}
9+
{"nodeid": "tests/data/pytest/tests/funcs3_test.py::test_func4", "location": ["tests/data/pytest/tests/funcs3_test.py", 0, "test_func4"], "keywords": {"tests/data/pytest/tests/funcs3_test.py": 1, "test_func4": 1, "cli": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [["name", "parametrize"], ["args", ["x", [0, 1]]], ["kwargs", {}], ["name", "slow"], ["args", []], ["kwargs", {}]], "sections": [], "duration": 0.001, "$report_type": "TestReport"}
1010
{"nodeid": "tests/data/pytest/tests/funcs3_test.py::test_func4", "location": ["tests/data/pytest/tests/funcs3_test.py", 0, "test_func4"], "keywords": {"tests/data/pytest/tests/funcs3_test.py": 1, "test_func4": 1, "cli": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.001, "$report_type": "TestReport"}
1111
{"nodeid": "tests/data/pytest/tests/funcs3_test.py::test_func5", "location": ["tests/data/pytest/tests/funcs3_test.py", 4, "test_func5"], "keywords": {"tests/data/pytest/tests/funcs3_test.py": 1, "test_func5": 1, "cli": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00023489200000004207, "$report_type": "TestReport"}
1212
{"nodeid": "tests/data/pytest/tests/funcs3_test.py::test_func5", "location": ["tests/data/pytest/tests/funcs3_test.py", 4, "test_func5"], "keywords": {"tests/data/pytest/tests/funcs3_test.py": 1, "test_func5": 1, "cli": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "/Users/yabuki-ryosuke/src/github.com/launchableinc/cli/tests/data/pytest/tests/funcs3_test.py", "lineno": 6, "message": "assert 1 == False"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_func5():", "> assert 1 == False", "E assert 1 == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "tests/funcs3_test.py", "lineno": 6, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_func5():", "> assert 1 == False", "E assert 1 == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "tests/funcs3_test.py", "lineno": 6, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/yabuki-ryosuke/src/github.com/launchableinc/cli/tests/data/pytest/tests/funcs3_test.py", "lineno": 6, "message": "assert 1 == False"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 0.001, "$report_type": "TestReport"}
1313
{"nodeid": "tests/data/pytest/tests/funcs3_test.py::test_func5", "location": ["tests/data/pytest/tests/funcs3_test.py", 4, "test_func5"], "keywords": {"tests/data/pytest/tests/funcs3_test.py": 1, "test_func5": 1, "cli": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00021273599999993564, "$report_type": "TestReport"}
1414
{"nodeid": "tests/data/pytest/tests/test_funcs1.py::test_func1", "location": ["tests/data/pytest/tests/test_funcs1.py", 0, "test_func1"], "keywords": {"test_func1": 1, "cli": 1, "tests/data/pytest/tests/test_funcs1.py": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00019361299999998138, "$report_type": "TestReport"}
15-
{"nodeid": "tests/data/pytest/tests/test_funcs1.py::test_func1", "location": ["tests/data/pytest/tests/test_funcs1.py", 0, "test_func1"], "keywords": {"test_func1": 1, "cli": 1, "tests/data/pytest/tests/test_funcs1.py": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 0.001, "$report_type": "TestReport"}
15+
{"nodeid": "tests/data/pytest/tests/test_funcs1.py::test_func1", "location": ["tests/data/pytest/tests/test_funcs1.py", 0, "test_func1"], "keywords": {"test_func1": 1, "cli": 1, "tests/data/pytest/tests/test_funcs1.py": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [["name", "dependency"], ["args", []], ["kwargs", {"name": "test1", "depends": ["test0"]}]], "sections": [], "duration": 0.001, "$report_type": "TestReport"}
1616
{"nodeid": "tests/data/pytest/tests/test_funcs1.py::test_func1", "location": ["tests/data/pytest/tests/test_funcs1.py", 0, "test_func1"], "keywords": {"test_func1": 1, "cli": 1, "tests/data/pytest/tests/test_funcs1.py": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.0001104009999999267, "$report_type": "TestReport"}
1717
{"nodeid": "tests/data/pytest/tests/test_funcs1.py::test_func2", "location": ["tests/data/pytest/tests/test_funcs1.py", 4, "test_func2"], "keywords": {"cli": 1, "tests/data/pytest/tests/test_funcs1.py": 1, "test_func2": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00017335500000004167, "$report_type": "TestReport"}
1818
{"nodeid": "tests/data/pytest/tests/test_funcs1.py::test_func2", "location": ["tests/data/pytest/tests/test_funcs1.py", 4, "test_func2"], "keywords": {"cli": 1, "tests/data/pytest/tests/test_funcs1.py": 1, "test_func2": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "/Users/yabuki-ryosuke/src/github.com/launchableinc/cli/tests/data/pytest/tests/test_funcs1.py", "lineno": 6, "message": "assert 1 == False"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_func2():", "> assert 1 == False", "E assert 1 == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "tests/test_funcs1.py", "lineno": 6, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_func2():", "> assert 1 == False", "E assert 1 == False"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": "tests/test_funcs1.py", "lineno": 6, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "/Users/yabuki-ryosuke/src/github.com/launchableinc/cli/tests/data/pytest/tests/test_funcs1.py", "lineno": 6, "message": "assert 1 == False"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 0.001, "$report_type": "TestReport"}
1919
{"nodeid": "tests/data/pytest/tests/test_funcs1.py::test_func2", "location": ["tests/data/pytest/tests/test_funcs1.py", 4, "test_func2"], "keywords": {"cli": 1, "tests/data/pytest/tests/test_funcs1.py": 1, "test_func2": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.0001447880000000623, "$report_type": "TestReport"}
2020
{"nodeid": "tests/data/pytest/tests/test_funcs2.py::test_func3", "location": ["tests/data/pytest/tests/test_funcs2.py", 0, "test_func3"], "keywords": {"test_func3": 1, "cli": 1, "tests/data/pytest/tests/test_funcs2.py": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00016672299999997975, "$report_type": "TestReport"}
21-
{"nodeid": "tests/data/pytest/tests/test_funcs2.py::test_func3", "location": ["tests/data/pytest/tests/test_funcs2.py", 0, "test_func3"], "keywords": {"test_func3": 1, "cli": 1, "tests/data/pytest/tests/test_funcs2.py": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 0.001, "$report_type": "TestReport"}
21+
{"nodeid": "tests/data/pytest/tests/test_funcs2.py::test_func3", "location": ["tests/data/pytest/tests/test_funcs2.py", 0, "test_func3"], "keywords": {"test_func3": 1, "cli": 1, "tests/data/pytest/tests/test_funcs2.py": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [["name", "order"], ["args", [2]], ["kwargs", {}]], "sections": [], "duration": 0.001, "$report_type": "TestReport"}
2222
{"nodeid": "tests/data/pytest/tests/test_funcs2.py::test_func3", "location": ["tests/data/pytest/tests/test_funcs2.py", 0, "test_func3"], "keywords": {"test_func3": 1, "cli": 1, "tests/data/pytest/tests/test_funcs2.py": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00023961299999997188, "$report_type": "TestReport"}
2323
{"nodeid": "tests/data/pytest/tests/test_funcs2.py::test_func4", "location": ["tests/data/pytest/tests/test_funcs2.py", 4, "test_func4"], "keywords": {"test_func4": 1, "cli": 1, "tests/data/pytest/tests/test_funcs2.py": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.00015693899999991157, "$report_type": "TestReport"}
2424
{"nodeid": "tests/data/pytest/tests/test_funcs2.py::test_func4", "location": ["tests/data/pytest/tests/test_funcs2.py", 4, "test_func4"], "keywords": {"test_func4": 1, "cli": 1, "tests/data/pytest/tests/test_funcs2.py": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 0.001, "$report_type": "TestReport"}

0 commit comments

Comments
 (0)