OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library test.runner.reporter.json; | 5 library test.runner.reporter.json; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:convert'; | 8 import 'dart:convert'; |
9 | 9 |
| 10 import '../../backend/group.dart'; |
10 import '../../backend/live_test.dart'; | 11 import '../../backend/live_test.dart'; |
| 12 import '../../backend/metadata.dart'; |
11 import '../../frontend/expect.dart'; | 13 import '../../frontend/expect.dart'; |
12 import '../../utils.dart'; | 14 import '../../utils.dart'; |
13 import '../engine.dart'; | 15 import '../engine.dart'; |
| 16 import '../load_suite.dart'; |
14 import '../reporter.dart'; | 17 import '../reporter.dart'; |
15 import '../version.dart'; | 18 import '../version.dart'; |
16 | 19 |
17 /// A reporter that prints machine-readable JSON-formatted test results. | 20 /// A reporter that prints machine-readable JSON-formatted test results. |
18 class JsonReporter implements Reporter { | 21 class JsonReporter implements Reporter { |
19 /// Whether to use verbose stack traces. | 22 /// Whether to use verbose stack traces. |
20 final bool _verboseTrace; | 23 final bool _verboseTrace; |
21 | 24 |
22 /// The engine used to run the tests. | 25 /// The engine used to run the tests. |
23 final Engine _engine; | 26 final Engine _engine; |
24 | 27 |
25 /// A stopwatch that tracks the duration of the full run. | 28 /// A stopwatch that tracks the duration of the full run. |
26 final _stopwatch = new Stopwatch(); | 29 final _stopwatch = new Stopwatch(); |
27 | 30 |
28 /// Whether we've started [_stopwatch]. | 31 /// Whether we've started [_stopwatch]. |
29 /// | 32 /// |
30 /// We can't just use `_stopwatch.isRunning` because the stopwatch is stopped | 33 /// We can't just use `_stopwatch.isRunning` because the stopwatch is stopped |
31 /// when the reporter is paused. | 34 /// when the reporter is paused. |
32 var _stopwatchStarted = false; | 35 var _stopwatchStarted = false; |
33 | 36 |
34 /// Whether the reporter is paused. | 37 /// Whether the reporter is paused. |
35 var _paused = false; | 38 var _paused = false; |
36 | 39 |
37 /// The set of all subscriptions to various streams. | 40 /// The set of all subscriptions to various streams. |
38 final _subscriptions = new Set<StreamSubscription>(); | 41 final _subscriptions = new Set<StreamSubscription>(); |
39 | 42 |
40 /// An expando that associates unique IDs with [LiveTest]s. | 43 /// An expando that associates unique IDs with [LiveTest]s. |
41 final _ids = new Map<LiveTest, int>(); | 44 final _liveTestIDs = new Map<LiveTest, int>(); |
| 45 |
| 46 /// An expando that associates unique IDs with [Group]s. |
| 47 final _groupIDs = new Map<Group, int>(); |
42 | 48 |
43 /// The next ID to associate with a [LiveTest]. | 49 /// The next ID to associate with a [LiveTest]. |
44 var _nextID = 0; | 50 var _nextID = 0; |
45 | 51 |
46 /// Watches the tests run by [engine] and prints their results as JSON. | 52 /// Watches the tests run by [engine] and prints their results as JSON. |
47 /// | 53 /// |
48 /// If [verboseTrace] is `true`, this will print core library frames. | 54 /// If [verboseTrace] is `true`, this will print core library frames. |
49 static JsonReporter watch(Engine engine, {bool verboseTrace: false}) { | 55 static JsonReporter watch(Engine engine, {bool verboseTrace: false}) { |
50 return new JsonReporter._(engine, verboseTrace: verboseTrace); | 56 return new JsonReporter._(engine, verboseTrace: verboseTrace); |
51 } | 57 } |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
93 _subscriptions.clear(); | 99 _subscriptions.clear(); |
94 } | 100 } |
95 | 101 |
96 /// A callback called when the engine begins running [liveTest]. | 102 /// A callback called when the engine begins running [liveTest]. |
97 void _onTestStarted(LiveTest liveTest) { | 103 void _onTestStarted(LiveTest liveTest) { |
98 if (!_stopwatchStarted) { | 104 if (!_stopwatchStarted) { |
99 _stopwatchStarted = true; | 105 _stopwatchStarted = true; |
100 _stopwatch.start(); | 106 _stopwatch.start(); |
101 } | 107 } |
102 | 108 |
| 109 // Don't emit groups for load suites. They're always empty and they provide |
| 110 // unnecessary clutter. |
| 111 var groupIDs = liveTest.suite is LoadSuite |
| 112 ? [] |
| 113 : _idsForGroups(liveTest.groups); |
| 114 |
103 var id = _nextID++; | 115 var id = _nextID++; |
104 _ids[liveTest] = id; | 116 _liveTestIDs[liveTest] = id; |
105 _emit("testStart", { | 117 _emit("testStart", { |
106 "test": { | 118 "test": { |
107 "id": id, | 119 "id": id, |
108 "name": liveTest.test.name, | 120 "name": liveTest.test.name, |
109 "metadata": { | 121 "groupIDs": groupIDs, |
110 "skip": liveTest.test.metadata.skip, | 122 "metadata": _serializeMetadata(liveTest.test.metadata) |
111 "skipReason": liveTest.test.metadata.skipReason | |
112 } | |
113 } | 123 } |
114 }); | 124 }); |
115 | 125 |
116 /// Convert the future to a stream so that the subscription can be paused or | 126 /// Convert the future to a stream so that the subscription can be paused or |
117 /// canceled. | 127 /// canceled. |
118 _subscriptions.add(liveTest.onComplete.asStream().listen((_) => | 128 _subscriptions.add(liveTest.onComplete.asStream().listen((_) => |
119 _onComplete(liveTest))); | 129 _onComplete(liveTest))); |
120 | 130 |
121 _subscriptions.add(liveTest.onError.listen((error) => | 131 _subscriptions.add(liveTest.onError.listen((error) => |
122 _onError(liveTest, error.error, error.stackTrace))); | 132 _onError(liveTest, error.error, error.stackTrace))); |
123 | 133 |
124 _subscriptions.add(liveTest.onPrint.listen((line) { | 134 _subscriptions.add(liveTest.onPrint.listen((line) { |
125 _emit("print", { | 135 _emit("print", { |
126 "testID": id, | 136 "testID": id, |
127 "message": line | 137 "message": line |
128 }); | 138 }); |
129 })); | 139 })); |
130 } | 140 } |
131 | 141 |
| 142 /// Returns a list of the IDs for all the groups in [groups]. |
| 143 /// |
| 144 /// If a group doesn't have an ID yet, this assigns one and emits a new event |
| 145 /// for that group. |
| 146 List<int> _idsForGroups(Iterable<Group> groups) { |
| 147 var parentID; |
| 148 return groups.map((group) { |
| 149 if (_groupIDs.containsKey(group)) { |
| 150 parentID = _groupIDs[group]; |
| 151 return parentID; |
| 152 } |
| 153 |
| 154 var id = _nextID++; |
| 155 _groupIDs[group] = id; |
| 156 |
| 157 _emit("group", { |
| 158 "group": { |
| 159 "id": id, |
| 160 "parentID": parentID, |
| 161 "name": group.name, |
| 162 "metadata": _serializeMetadata(group.metadata) |
| 163 } |
| 164 }); |
| 165 parentID = id; |
| 166 return id; |
| 167 }).toList(); |
| 168 } |
| 169 |
| 170 /// Serializes [metadata] into a JSON-protocol-compatible map. |
| 171 Map _serializeMetadata(Metadata metadata) => |
| 172 {"skip": metadata.skip, "skipReason": metadata.skipReason}; |
| 173 |
132 /// A callback called when [liveTest] finishes running. | 174 /// A callback called when [liveTest] finishes running. |
133 void _onComplete(LiveTest liveTest) { | 175 void _onComplete(LiveTest liveTest) { |
134 _emit("testDone", { | 176 _emit("testDone", { |
135 "testID": _ids[liveTest], | 177 "testID": _liveTestIDs[liveTest], |
136 "result": liveTest.state.result.toString(), | 178 "result": liveTest.state.result.toString(), |
137 "hidden": !_engine.liveTests.contains(liveTest) | 179 "hidden": !_engine.liveTests.contains(liveTest) |
138 }); | 180 }); |
139 } | 181 } |
140 | 182 |
141 /// A callback called when [liveTest] throws [error]. | 183 /// A callback called when [liveTest] throws [error]. |
142 void _onError(LiveTest liveTest, error, StackTrace stackTrace) { | 184 void _onError(LiveTest liveTest, error, StackTrace stackTrace) { |
143 _emit("error", { | 185 _emit("error", { |
144 "testID": _ids[liveTest], | 186 "testID": _liveTestIDs[liveTest], |
145 "error": error.toString(), | 187 "error": error.toString(), |
146 "stackTrace": terseChain(stackTrace, verbose: _verboseTrace).toString(), | 188 "stackTrace": terseChain(stackTrace, verbose: _verboseTrace).toString(), |
147 "isFailure": error is TestFailure | 189 "isFailure": error is TestFailure |
148 }); | 190 }); |
149 } | 191 } |
150 | 192 |
151 /// A callback called when the engine is finished running tests. | 193 /// A callback called when the engine is finished running tests. |
152 /// | 194 /// |
153 /// [success] will be `true` if all tests passed, `false` if some tests | 195 /// [success] will be `true` if all tests passed, `false` if some tests |
154 /// failed, and `null` if the engine was closed prematurely. | 196 /// failed, and `null` if the engine was closed prematurely. |
155 void _onDone(bool success) { | 197 void _onDone(bool success) { |
156 cancel(); | 198 cancel(); |
157 _stopwatch.stop(); | 199 _stopwatch.stop(); |
158 | 200 |
159 _emit("done", {"success": success}); | 201 _emit("done", {"success": success}); |
160 } | 202 } |
161 | 203 |
162 /// Emits an event with the given type and attributes. | 204 /// Emits an event with the given type and attributes. |
163 void _emit(String type, Map attributes) { | 205 void _emit(String type, Map attributes) { |
164 attributes["type"] = type; | 206 attributes["type"] = type; |
165 attributes["time"] = _stopwatch.elapsed.inMilliseconds; | 207 attributes["time"] = _stopwatch.elapsed.inMilliseconds; |
166 print(JSON.encode(attributes)); | 208 print(JSON.encode(attributes)); |
167 } | 209 } |
168 } | 210 } |
OLD | NEW |