Skip to content

Commit 0e2e283

Browse files
authored
Merge pull request #1188 from cloudbees-oss/LCHIB-653
Add test runner for Jasmine
2 parents ee4de5f + 283ac52 commit 0e2e283

5 files changed

Lines changed: 447 additions & 0 deletions

File tree

launchable/test_runners/jasmine.py

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