Skip to content

Commit d7159e2

Browse files
authored
Merge pull request #1189 from cloudbees-oss/v1-sync
2 parents 6ba6000 + 55f48c3 commit d7159e2

File tree

5 files changed

+447
-0
lines changed

5 files changed

+447
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import json
2+
from typing import Annotated, Dict, Generator, List
3+
4+
import click
5+
6+
import smart_tests.args4p.typer as typer
7+
from smart_tests.testpath import TestPath
8+
9+
from ..commands.record.case_event import CaseEvent
10+
from ..commands.record.tests import RecordTests
11+
from . import smart_tests
12+
13+
14+
@smart_tests.record.tests
15+
def record_tests(
16+
client: RecordTests,
17+
reports: Annotated[List[str], typer.Argument(
18+
multiple=True,
19+
help="Test report files to process"
20+
)],
21+
):
22+
client.parse_func = JSONReportParser(client).parse_func
23+
24+
for r in reports:
25+
client.report(r)
26+
27+
client.run()
28+
29+
30+
@smart_tests.subset
31+
def subset(client):
32+
# read lines as test file names
33+
for t in client.stdin():
34+
client.test_path(t.rstrip("\n"))
35+
36+
client.run()
37+
38+
39+
class JSONReportParser:
40+
"""
41+
Sample report format:
42+
{
43+
"suite1": {
44+
"id": "suite1",
45+
"description": "Player",
46+
"fullName": "Player",
47+
"parentSuiteId": null,
48+
"filename": "/path/to/spec/PlayerSpec.js",
49+
"failedExpectations": [],
50+
"deprecationWarnings": [],
51+
"duration": 3,
52+
"properties": null,
53+
"status": "passed",
54+
"specs": [
55+
{
56+
"id": "spec0",
57+
"description": "should be able to play a Song",
58+
"fullName": "Player should be able to play a Song",
59+
"parentSuiteId": "suite1",
60+
"filename": "/path/to/spec/PlayerSpec.js",
61+
"failedExpectations": [],
62+
"passedExpectations": [...],
63+
"deprecationWarnings": [],
64+
"pendingReason": "",
65+
"duration": 1,
66+
"properties": null,
67+
"debugLogs": null,
68+
"status": "passed"
69+
}
70+
]
71+
}
72+
}
73+
"""
74+
75+
def __init__(self, client):
76+
self.client = client
77+
78+
def parse_func(self, report_file: str) -> Generator[Dict, None, None]: # type: ignore
79+
data: Dict[str, Dict]
80+
with open(report_file, 'r') as json_file:
81+
try:
82+
data = json.load(json_file)
83+
except Exception:
84+
click.echo(
85+
click.style("Error: Failed to load Json report file: {}".format(report_file), fg='red'), err=True)
86+
return
87+
88+
if not self._validate_report_format(data):
89+
click.echo(
90+
"Error: {} does not appear to be valid format. "
91+
"Make sure you are using Jasmine >= v4.6.0 and jasmine-json-test-reporter as the reporter.".format(
92+
report_file), err=True)
93+
return
94+
95+
# If validation passes, parse the suites
96+
for suite_id, suite in data.items():
97+
for event in self._parse_suite(suite):
98+
yield event
99+
100+
def _validate_report_format(self, data: Dict) -> bool:
101+
for suite in data.values():
102+
if not isinstance(suite, dict):
103+
return False
104+
105+
if "filename" not in suite or "specs" not in suite:
106+
return False
107+
108+
specs = suite.get("specs", [])
109+
for spec in specs:
110+
if not isinstance(spec, dict):
111+
return False
112+
if "status" not in spec or "duration" not in spec:
113+
return False
114+
115+
return True
116+
117+
def _parse_suite(self, suite: Dict) -> List[Dict]:
118+
events: List[Dict] = []
119+
120+
filename = suite.get("filename", "")
121+
specs = suite.get("specs", [])
122+
for spec in specs:
123+
test_path: TestPath = [
124+
self.client.make_file_path_component(filename),
125+
{"type": "testcase", "name": spec.get("fullName", spec.get("description", ""))}
126+
]
127+
128+
duration_msec = spec.get("duration", 0)
129+
status = self._case_event_status_from_str(spec.get("status", ""))
130+
stderr = self._parse_stderr(spec)
131+
132+
events.append(CaseEvent.create(
133+
test_path=test_path,
134+
duration_secs=duration_msec / 1000 if duration_msec else 0, # convert msec to sec
135+
status=status,
136+
stderr=stderr
137+
))
138+
139+
return events
140+
141+
def _case_event_status_from_str(self, status_str: str) -> int:
142+
if status_str == "passed":
143+
return CaseEvent.TEST_PASSED
144+
elif status_str == "failed":
145+
return CaseEvent.TEST_FAILED
146+
else:
147+
return CaseEvent.TEST_SKIPPED
148+
149+
def _parse_stderr(self, spec: Dict) -> str:
150+
failed_expectations = spec.get("failedExpectations", [])
151+
if not failed_expectations:
152+
return ""
153+
154+
error_messages = []
155+
for expectation in failed_expectations:
156+
message = expectation.get("message", "")
157+
stack = expectation.get("stack", "")
158+
159+
if message:
160+
error_messages.append(message)
161+
if stack:
162+
error_messages.append(stack)
163+
164+
return "\n".join(error_messages)
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
{
2+
"suite2": {
3+
"id": "suite2",
4+
"description": "when song has been paused",
5+
"fullName": "Player when song has been paused",
6+
"parentSuiteId": "suite1",
7+
"filename": "spec/jasmine_examples/PlayerSpec.js",
8+
"failedExpectations": [],
9+
"deprecationWarnings": [],
10+
"duration": 2,
11+
"properties": null,
12+
"status": "passed",
13+
"specs": [
14+
{
15+
"id": "spec1",
16+
"description": "should indicate that the song is currently paused",
17+
"fullName": "Player when song has been paused should indicate that the song is currently paused",
18+
"parentSuiteId": "suite2",
19+
"filename": "spec/jasmine_examples/PlayerSpec.js",
20+
"failedExpectations": [],
21+
"passedExpectations": [
22+
{
23+
"matcherName": "toBeFalsy",
24+
"message": "Passed.",
25+
"stack": "",
26+
"passed": true
27+
},
28+
{
29+
"matcherName": "toBePlaying",
30+
"message": "Passed.",
31+
"stack": "",
32+
"passed": true
33+
}
34+
],
35+
"deprecationWarnings": [],
36+
"pendingReason": "",
37+
"duration": 1,
38+
"properties": null,
39+
"debugLogs": null,
40+
"status": "passed"
41+
},
42+
{
43+
"id": "spec2",
44+
"description": "should be possible to resume",
45+
"fullName": "Player when song has been paused should be possible to resume",
46+
"parentSuiteId": "suite2",
47+
"filename": "spec/jasmine_examples/PlayerSpec.js",
48+
"failedExpectations": [
49+
{
50+
"matcherName": "toBeFalsy",
51+
"message": "Expected true to be falsy.",
52+
"stack": " at <Jasmine>\n at UserContext.<anonymous> (spec/jasmine_examples/PlayerSpec.js:37:29)\n at <Jasmine>",
53+
"passed": false,
54+
"expected": [],
55+
"actual": true
56+
}
57+
],
58+
"passedExpectations": [
59+
{
60+
"matcherName": "toEqual",
61+
"message": "Passed.",
62+
"stack": "",
63+
"passed": true
64+
}
65+
],
66+
"deprecationWarnings": [],
67+
"pendingReason": "",
68+
"duration": 1,
69+
"properties": null,
70+
"debugLogs": null,
71+
"status": "failed"
72+
}
73+
]
74+
},
75+
"suite1": {
76+
"id": "suite1",
77+
"description": "Player",
78+
"fullName": "Player",
79+
"parentSuiteId": null,
80+
"filename": "spec/jasmine_examples/PlayerSpec.js",
81+
"failedExpectations": [],
82+
"deprecationWarnings": [],
83+
"duration": 2,
84+
"properties": null,
85+
"status": "passed",
86+
"specs": [
87+
{
88+
"id": "spec0",
89+
"description": "should be able to play a Song",
90+
"fullName": "Player should be able to play a Song",
91+
"parentSuiteId": "suite1",
92+
"filename": "spec/jasmine_examples/PlayerSpec.js",
93+
"failedExpectations": [],
94+
"passedExpectations": [
95+
{
96+
"matcherName": "toEqual",
97+
"message": "Passed.",
98+
"stack": "",
99+
"passed": true
100+
},
101+
{
102+
"matcherName": "toBePlaying",
103+
"message": "Passed.",
104+
"stack": "",
105+
"passed": true
106+
}
107+
],
108+
"deprecationWarnings": [],
109+
"pendingReason": "",
110+
"duration": 0,
111+
"properties": null,
112+
"debugLogs": null,
113+
"status": "passed"
114+
}
115+
]
116+
},
117+
"suite3": {
118+
"id": "suite3",
119+
"description": "User",
120+
"fullName": "User",
121+
"parentSuiteId": null,
122+
"filename": "spec/jasmine_examples/UserSpec.js",
123+
"failedExpectations": [],
124+
"deprecationWarnings": [],
125+
"duration": 0,
126+
"properties": null,
127+
"status": "passed",
128+
"specs": [
129+
{
130+
"id": "spec3",
131+
"description": "should be able to play a Song",
132+
"fullName": "User should be able to play a Song",
133+
"parentSuiteId": "suite3",
134+
"filename": "spec/jasmine_examples/UserSpec.js",
135+
"failedExpectations": [],
136+
"passedExpectations": [
137+
{
138+
"matcherName": "toEqual",
139+
"message": "Passed.",
140+
"stack": "",
141+
"passed": true
142+
},
143+
{
144+
"matcherName": "toBePlaying",
145+
"message": "Passed.",
146+
"stack": "",
147+
"passed": true
148+
}
149+
],
150+
"deprecationWarnings": [],
151+
"pendingReason": "",
152+
"duration": 0,
153+
"properties": null,
154+
"debugLogs": null,
155+
"status": "passed"
156+
}
157+
]
158+
}
159+
}

0 commit comments

Comments
 (0)