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 import 'dart:async'; | 5 import 'dart:async'; |
6 | 6 |
7 import 'package:path/path.dart' as p; | 7 import 'package:path/path.dart' as p; |
8 import 'package:stack_trace/stack_trace.dart'; | 8 import 'package:stack_trace/stack_trace.dart'; |
9 import 'package:test/test.dart'; | 9 import 'package:test/test.dart'; |
10 | 10 |
11 import 'utils.dart'; | 11 import 'utils.dart'; |
12 | 12 |
| 13 typedef void ChainErrorCallback(stack, Chain chain); |
| 14 |
13 void main() { | 15 void main() { |
14 group('Chain.parse()', () { | 16 group('Chain.parse()', () { |
15 test('parses a real Chain', () { | 17 test('parses a real Chain', () { |
16 return captureFuture(() => inMicrotask(() => throw 'error')) | 18 return captureFuture(() => inMicrotask(() => throw 'error')) |
17 .then((chain) { | 19 .then((chain) { |
18 expect(new Chain.parse(chain.toString()).toString(), | 20 expect(new Chain.parse(chain.toString()).toString(), |
19 equals(chain.toString())); | 21 equals(chain.toString())); |
20 }); | 22 }); |
21 }); | 23 }); |
22 | 24 |
23 test('parses an empty string', () { | 25 test('parses an empty string', () { |
24 var chain = new Chain.parse(''); | 26 var chain = new Chain.parse(''); |
25 expect(chain.traces, isEmpty); | 27 expect(chain.traces, isEmpty); |
26 }); | 28 }); |
27 | 29 |
28 test('parses a chain containing empty traces', () { | 30 test('parses a chain containing empty traces', () { |
29 var chain = new Chain.parse( | 31 var chain = |
30 '===== asynchronous gap ===========================\n' | 32 new Chain.parse('===== asynchronous gap ===========================\n' |
31 '===== asynchronous gap ===========================\n'); | 33 '===== asynchronous gap ===========================\n'); |
32 expect(chain.traces, hasLength(3)); | 34 expect(chain.traces, hasLength(3)); |
33 expect(chain.traces[0].frames, isEmpty); | 35 expect(chain.traces[0].frames, isEmpty); |
34 expect(chain.traces[1].frames, isEmpty); | 36 expect(chain.traces[1].frames, isEmpty); |
35 expect(chain.traces[2].frames, isEmpty); | 37 expect(chain.traces[2].frames, isEmpty); |
36 }); | 38 }); |
37 }); | 39 }); |
38 | 40 |
| 41 group("Chain.capture() with when: false", () { |
| 42 test("with no onError doesn't block errors", () { |
| 43 expect(Chain.capture(() => new Future.error("oh no"), when: false), |
| 44 throwsA("oh no")); |
| 45 }); |
| 46 |
| 47 test("with onError blocks errors", () { |
| 48 Chain.capture(() { |
| 49 return new Future.error("oh no"); |
| 50 }, onError: expectAsync2((error, chain) { |
| 51 expect(error, equals("oh no")); |
| 52 expect(chain, new isInstanceOf<Chain>()); |
| 53 }), when: false); |
| 54 }); |
| 55 |
| 56 test("doesn't enable chain-tracking", () { |
| 57 return Chain.disable(() { |
| 58 return Chain.capture(() { |
| 59 var completer = new Completer(); |
| 60 inMicrotask(() { |
| 61 completer.complete(new Chain.current()); |
| 62 }); |
| 63 |
| 64 return completer.future.then((chain) { |
| 65 expect(chain.traces, hasLength(1)); |
| 66 }); |
| 67 }, when: false); |
| 68 }); |
| 69 }); |
| 70 }); |
| 71 |
| 72 group("Chain.disable()", () { |
| 73 test("disables chain-tracking", () { |
| 74 return Chain.disable(() { |
| 75 var completer = new Completer(); |
| 76 inMicrotask(() => completer.complete(new Chain.current())); |
| 77 |
| 78 return completer.future.then((chain) { |
| 79 expect(chain.traces, hasLength(1)); |
| 80 }); |
| 81 }); |
| 82 }); |
| 83 |
| 84 test("Chain.capture() re-enables chain-tracking", () { |
| 85 return Chain.disable(() { |
| 86 return Chain.capture(() { |
| 87 var completer = new Completer(); |
| 88 inMicrotask(() => completer.complete(new Chain.current())); |
| 89 |
| 90 return completer.future.then((chain) { |
| 91 expect(chain.traces, hasLength(2)); |
| 92 }); |
| 93 }); |
| 94 }); |
| 95 }); |
| 96 |
| 97 test("preserves parent zones of the capture zone", () { |
| 98 // The outer disable call turns off the test package's chain-tracking. |
| 99 return Chain.disable(() { |
| 100 return runZoned(() { |
| 101 return Chain.capture(() { |
| 102 expect(Chain.disable(() => Zone.current[#enabled]), isTrue); |
| 103 }); |
| 104 }, zoneValues: {#enabled: true}); |
| 105 }); |
| 106 }); |
| 107 |
| 108 test("preserves child zones of the capture zone", () { |
| 109 // The outer disable call turns off the test package's chain-tracking. |
| 110 return Chain.disable(() { |
| 111 return Chain.capture(() { |
| 112 return runZoned(() { |
| 113 expect(Chain.disable(() => Zone.current[#enabled]), isTrue); |
| 114 }, zoneValues: {#enabled: true}); |
| 115 }); |
| 116 }); |
| 117 }); |
| 118 |
| 119 test("with when: false doesn't disable", () { |
| 120 return Chain.capture(() { |
| 121 return Chain.disable(() { |
| 122 var completer = new Completer(); |
| 123 inMicrotask(() => completer.complete(new Chain.current())); |
| 124 |
| 125 return completer.future.then((chain) { |
| 126 expect(chain.traces, hasLength(2)); |
| 127 }); |
| 128 }, when: false); |
| 129 }); |
| 130 }); |
| 131 }); |
| 132 |
39 test("toString() ensures that all traces are aligned", () { | 133 test("toString() ensures that all traces are aligned", () { |
40 var chain = new Chain([ | 134 var chain = new Chain([ |
41 new Trace.parse('short 10:11 Foo.bar\n'), | 135 new Trace.parse('short 10:11 Foo.bar\n'), |
42 new Trace.parse('loooooooooooong 10:11 Zop.zoop') | 136 new Trace.parse('loooooooooooong 10:11 Zop.zoop') |
43 ]); | 137 ]); |
44 | 138 |
45 expect(chain.toString(), equals( | 139 expect( |
46 'short 10:11 Foo.bar\n' | 140 chain.toString(), |
47 '===== asynchronous gap ===========================\n' | 141 equals('short 10:11 Foo.bar\n' |
48 'loooooooooooong 10:11 Zop.zoop\n')); | 142 '===== asynchronous gap ===========================\n' |
| 143 'loooooooooooong 10:11 Zop.zoop\n')); |
49 }); | 144 }); |
50 | 145 |
51 var userSlashCode = p.join('user', 'code.dart'); | 146 var userSlashCode = p.join('user', 'code.dart'); |
52 group('Chain.terse', () { | 147 group('Chain.terse', () { |
53 test('makes each trace terse', () { | 148 test('makes each trace terse', () { |
54 var chain = new Chain([ | 149 var chain = new Chain([ |
55 new Trace.parse( | 150 new Trace.parse('dart:core 10:11 Foo.bar\n' |
56 'dart:core 10:11 Foo.bar\n' | |
57 'dart:core 10:11 Bar.baz\n' | 151 'dart:core 10:11 Bar.baz\n' |
58 'user/code.dart 10:11 Bang.qux\n' | 152 'user/code.dart 10:11 Bang.qux\n' |
59 'dart:core 10:11 Zip.zap\n' | 153 'dart:core 10:11 Zip.zap\n' |
60 'dart:core 10:11 Zop.zoop'), | 154 'dart:core 10:11 Zop.zoop'), |
61 new Trace.parse( | 155 new Trace.parse('user/code.dart 10:11 Bang.qux\n' |
62 'user/code.dart 10:11 Bang.qux\n' | |
63 'dart:core 10:11 Foo.bar\n' | 156 'dart:core 10:11 Foo.bar\n' |
64 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' | 157 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' |
65 'dart:core 10:11 Zip.zap\n' | 158 'dart:core 10:11 Zip.zap\n' |
66 'user/code.dart 10:11 Zop.zoop') | 159 'user/code.dart 10:11 Zop.zoop') |
67 ]); | 160 ]); |
68 | 161 |
69 expect(chain.terse.toString(), equals( | 162 expect( |
70 'dart:core Bar.baz\n' | 163 chain.terse.toString(), |
71 '$userSlashCode 10:11 Bang.qux\n' | 164 equals('dart:core Bar.baz\n' |
72 '===== asynchronous gap ===========================\n' | 165 '$userSlashCode 10:11 Bang.qux\n' |
73 '$userSlashCode 10:11 Bang.qux\n' | 166 '===== asynchronous gap ===========================\n' |
74 'dart:core Zip.zap\n' | 167 '$userSlashCode 10:11 Bang.qux\n' |
75 '$userSlashCode 10:11 Zop.zoop\n')); | 168 'dart:core Zip.zap\n' |
| 169 '$userSlashCode 10:11 Zop.zoop\n')); |
76 }); | 170 }); |
77 | 171 |
78 test('eliminates internal-only traces', () { | 172 test('eliminates internal-only traces', () { |
79 var chain = new Chain([ | 173 var chain = new Chain([ |
80 new Trace.parse( | 174 new Trace.parse('user/code.dart 10:11 Foo.bar\n' |
81 'user/code.dart 10:11 Foo.bar\n' | |
82 'dart:core 10:11 Bar.baz'), | 175 'dart:core 10:11 Bar.baz'), |
83 new Trace.parse( | 176 new Trace.parse('dart:core 10:11 Foo.bar\n' |
84 'dart:core 10:11 Foo.bar\n' | |
85 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' | 177 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' |
86 'dart:core 10:11 Zip.zap'), | 178 'dart:core 10:11 Zip.zap'), |
87 new Trace.parse( | 179 new Trace.parse('user/code.dart 10:11 Foo.bar\n' |
88 'user/code.dart 10:11 Foo.bar\n' | |
89 'dart:core 10:11 Bar.baz') | 180 'dart:core 10:11 Bar.baz') |
90 ]); | 181 ]); |
91 | 182 |
92 expect(chain.terse.toString(), equals( | 183 expect( |
93 '$userSlashCode 10:11 Foo.bar\n' | 184 chain.terse.toString(), |
94 '===== asynchronous gap ===========================\n' | 185 equals('$userSlashCode 10:11 Foo.bar\n' |
95 '$userSlashCode 10:11 Foo.bar\n')); | 186 '===== asynchronous gap ===========================\n' |
| 187 '$userSlashCode 10:11 Foo.bar\n')); |
96 }); | 188 }); |
97 | 189 |
98 test("doesn't return an empty chain", () { | 190 test("doesn't return an empty chain", () { |
99 var chain = new Chain([ | 191 var chain = new Chain([ |
100 new Trace.parse( | 192 new Trace.parse('dart:core 10:11 Foo.bar\n' |
101 'dart:core 10:11 Foo.bar\n' | |
102 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' | 193 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n' |
103 'dart:core 10:11 Zip.zap'), | 194 'dart:core 10:11 Zip.zap'), |
104 new Trace.parse( | 195 new Trace.parse('dart:core 10:11 A.b\n' |
105 'dart:core 10:11 A.b\n' | |
106 'package:stack_trace/stack_trace.dart 10:11 C.d\n' | 196 'package:stack_trace/stack_trace.dart 10:11 C.d\n' |
107 'dart:core 10:11 E.f') | 197 'dart:core 10:11 E.f') |
108 ]); | 198 ]); |
109 | 199 |
110 expect(chain.terse.toString(), equals('dart:core E.f\n')); | 200 expect(chain.terse.toString(), equals('dart:core E.f\n')); |
111 }); | 201 }); |
| 202 |
| 203 // Regression test for #9 |
| 204 test("doesn't crash on empty traces", () { |
| 205 var chain = new Chain([ |
| 206 new Trace.parse('user/code.dart 10:11 Bang.qux'), |
| 207 new Trace([]), |
| 208 new Trace.parse('user/code.dart 10:11 Bang.qux') |
| 209 ]); |
| 210 |
| 211 expect( |
| 212 chain.terse.toString(), |
| 213 equals('$userSlashCode 10:11 Bang.qux\n' |
| 214 '===== asynchronous gap ===========================\n' |
| 215 '$userSlashCode 10:11 Bang.qux\n')); |
| 216 }); |
112 }); | 217 }); |
113 | 218 |
114 group('Chain.foldFrames', () { | 219 group('Chain.foldFrames', () { |
115 test('folds each trace', () { | 220 test('folds each trace', () { |
116 var chain = new Chain([ | 221 var chain = new Chain([ |
117 new Trace.parse( | 222 new Trace.parse('a.dart 10:11 Foo.bar\n' |
118 'a.dart 10:11 Foo.bar\n' | |
119 'a.dart 10:11 Bar.baz\n' | 223 'a.dart 10:11 Bar.baz\n' |
120 'b.dart 10:11 Bang.qux\n' | 224 'b.dart 10:11 Bang.qux\n' |
121 'a.dart 10:11 Zip.zap\n' | 225 'a.dart 10:11 Zip.zap\n' |
122 'a.dart 10:11 Zop.zoop'), | 226 'a.dart 10:11 Zop.zoop'), |
123 new Trace.parse( | 227 new Trace.parse('a.dart 10:11 Foo.bar\n' |
124 'a.dart 10:11 Foo.bar\n' | |
125 'a.dart 10:11 Bar.baz\n' | 228 'a.dart 10:11 Bar.baz\n' |
126 'a.dart 10:11 Bang.qux\n' | 229 'a.dart 10:11 Bang.qux\n' |
127 'a.dart 10:11 Zip.zap\n' | 230 'a.dart 10:11 Zip.zap\n' |
128 'b.dart 10:11 Zop.zoop') | 231 'b.dart 10:11 Zop.zoop') |
129 ]); | 232 ]); |
130 | 233 |
131 var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); | 234 var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); |
132 expect(folded.toString(), equals( | 235 expect( |
133 'a.dart 10:11 Bar.baz\n' | 236 folded.toString(), |
134 'b.dart 10:11 Bang.qux\n' | 237 equals('a.dart 10:11 Bar.baz\n' |
135 'a.dart 10:11 Zop.zoop\n' | 238 'b.dart 10:11 Bang.qux\n' |
136 '===== asynchronous gap ===========================\n' | 239 'a.dart 10:11 Zop.zoop\n' |
137 'a.dart 10:11 Zip.zap\n' | 240 '===== asynchronous gap ===========================\n' |
138 'b.dart 10:11 Zop.zoop\n')); | 241 'a.dart 10:11 Zip.zap\n' |
| 242 'b.dart 10:11 Zop.zoop\n')); |
139 }); | 243 }); |
140 | 244 |
141 test('with terse: true, folds core frames as well', () { | 245 test('with terse: true, folds core frames as well', () { |
142 var chain = new Chain([ | 246 var chain = new Chain([ |
143 new Trace.parse( | 247 new Trace.parse('a.dart 10:11 Foo.bar\n' |
144 'a.dart 10:11 Foo.bar\n' | |
145 'dart:async-patch/future.dart 10:11 Zip.zap\n' | 248 'dart:async-patch/future.dart 10:11 Zip.zap\n' |
146 'b.dart 10:11 Bang.qux\n' | 249 'b.dart 10:11 Bang.qux\n' |
147 'dart:core 10:11 Bar.baz\n' | 250 'dart:core 10:11 Bar.baz\n' |
148 'a.dart 10:11 Zop.zoop'), | 251 'a.dart 10:11 Zop.zoop'), |
149 new Trace.parse( | 252 new Trace.parse('a.dart 10:11 Foo.bar\n' |
150 'a.dart 10:11 Foo.bar\n' | |
151 'a.dart 10:11 Bar.baz\n' | 253 'a.dart 10:11 Bar.baz\n' |
152 'a.dart 10:11 Bang.qux\n' | 254 'a.dart 10:11 Bang.qux\n' |
153 'a.dart 10:11 Zip.zap\n' | 255 'a.dart 10:11 Zip.zap\n' |
154 'b.dart 10:11 Zop.zoop') | 256 'b.dart 10:11 Zop.zoop') |
155 ]); | 257 ]); |
156 | 258 |
157 var folded = chain.foldFrames((frame) => frame.library == 'a.dart', | 259 var folded = |
158 terse: true); | 260 chain.foldFrames((frame) => frame.library == 'a.dart', terse: true); |
159 expect(folded.toString(), equals( | 261 expect( |
160 'dart:async Zip.zap\n' | 262 folded.toString(), |
161 'b.dart 10:11 Bang.qux\n' | 263 equals('dart:async Zip.zap\n' |
162 'a.dart Zop.zoop\n' | 264 'b.dart 10:11 Bang.qux\n' |
163 '===== asynchronous gap ===========================\n' | 265 '===== asynchronous gap ===========================\n' |
164 'a.dart Zip.zap\n' | 266 'a.dart Zip.zap\n' |
165 'b.dart 10:11 Zop.zoop\n')); | 267 'b.dart 10:11 Zop.zoop\n')); |
166 }); | 268 }); |
167 | 269 |
168 test('eliminates completely-folded traces', () { | 270 test('eliminates completely-folded traces', () { |
169 var chain = new Chain([ | 271 var chain = new Chain([ |
170 new Trace.parse( | 272 new Trace.parse('a.dart 10:11 Foo.bar\n' |
171 'a.dart 10:11 Foo.bar\n' | |
172 'b.dart 10:11 Bang.qux'), | 273 'b.dart 10:11 Bang.qux'), |
173 new Trace.parse( | 274 new Trace.parse('a.dart 10:11 Foo.bar\n' |
174 'a.dart 10:11 Foo.bar\n' | |
175 'a.dart 10:11 Bang.qux'), | 275 'a.dart 10:11 Bang.qux'), |
176 new Trace.parse( | 276 new Trace.parse('a.dart 10:11 Zip.zap\n' |
177 'a.dart 10:11 Zip.zap\n' | |
178 'b.dart 10:11 Zop.zoop') | 277 'b.dart 10:11 Zop.zoop') |
179 ]); | 278 ]); |
180 | 279 |
181 var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); | 280 var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); |
182 expect(folded.toString(), equals( | 281 expect( |
183 'a.dart 10:11 Foo.bar\n' | 282 folded.toString(), |
184 'b.dart 10:11 Bang.qux\n' | 283 equals('a.dart 10:11 Foo.bar\n' |
185 '===== asynchronous gap ===========================\n' | 284 'b.dart 10:11 Bang.qux\n' |
186 'a.dart 10:11 Zip.zap\n' | 285 '===== asynchronous gap ===========================\n' |
187 'b.dart 10:11 Zop.zoop\n')); | 286 'a.dart 10:11 Zip.zap\n' |
| 287 'b.dart 10:11 Zop.zoop\n')); |
188 }); | 288 }); |
189 | 289 |
190 test("doesn't return an empty trace", () { | 290 test("doesn't return an empty trace", () { |
191 var chain = new Chain([ | 291 var chain = new Chain([ |
192 new Trace.parse( | 292 new Trace.parse('a.dart 10:11 Foo.bar\n' |
193 'a.dart 10:11 Foo.bar\n' | |
194 'a.dart 10:11 Bang.qux') | 293 'a.dart 10:11 Bang.qux') |
195 ]); | 294 ]); |
196 | 295 |
197 var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); | 296 var folded = chain.foldFrames((frame) => frame.library == 'a.dart'); |
198 expect(folded.toString(), equals('a.dart 10:11 Bang.qux\n')); | 297 expect(folded.toString(), equals('a.dart 10:11 Bang.qux\n')); |
199 }); | 298 }); |
200 }); | 299 }); |
201 | 300 |
202 test('Chain.toTrace eliminates asynchronous gaps', () { | 301 test('Chain.toTrace eliminates asynchronous gaps', () { |
203 var trace = new Chain([ | 302 var trace = new Chain([ |
204 new Trace.parse( | 303 new Trace.parse('user/code.dart 10:11 Foo.bar\n' |
205 'user/code.dart 10:11 Foo.bar\n' | |
206 'dart:core 10:11 Bar.baz'), | 304 'dart:core 10:11 Bar.baz'), |
207 new Trace.parse( | 305 new Trace.parse('user/code.dart 10:11 Foo.bar\n' |
208 'user/code.dart 10:11 Foo.bar\n' | |
209 'dart:core 10:11 Bar.baz') | 306 'dart:core 10:11 Bar.baz') |
210 ]).toTrace(); | 307 ]).toTrace(); |
211 | 308 |
212 expect(trace.toString(), equals( | 309 expect( |
213 '$userSlashCode 10:11 Foo.bar\n' | 310 trace.toString(), |
214 'dart:core 10:11 Bar.baz\n' | 311 equals('$userSlashCode 10:11 Foo.bar\n' |
215 '$userSlashCode 10:11 Foo.bar\n' | 312 'dart:core 10:11 Bar.baz\n' |
216 'dart:core 10:11 Bar.baz\n')); | 313 '$userSlashCode 10:11 Foo.bar\n' |
| 314 'dart:core 10:11 Bar.baz\n')); |
217 }); | 315 }); |
218 | 316 } |
219 group('Chain.track(Future)', () { | |
220 test('forwards the future value within Chain.capture()', () { | |
221 Chain.capture(() { | |
222 expect(Chain.track(new Future.value('value')), | |
223 completion(equals('value'))); | |
224 | |
225 var trace = new Trace.current(); | |
226 expect(Chain.track(new Future.error('error', trace)) | |
227 .catchError((e, stackTrace) { | |
228 expect(e, equals('error')); | |
229 expect(stackTrace.toString(), equals(trace.toString())); | |
230 }), completes); | |
231 }); | |
232 }); | |
233 | |
234 test('forwards the future value outside of Chain.capture()', () { | |
235 expect(Chain.track(new Future.value('value')), | |
236 completion(equals('value'))); | |
237 | |
238 var trace = new Trace.current(); | |
239 expect(Chain.track(new Future.error('error', trace)) | |
240 .catchError((e, stackTrace) { | |
241 expect(e, equals('error')); | |
242 expect(stackTrace.toString(), equals(trace.toString())); | |
243 }), completes); | |
244 }); | |
245 }); | |
246 | |
247 group('Chain.track(Stream)', () { | |
248 test('forwards stream values within Chain.capture()', () { | |
249 Chain.capture(() { | |
250 var controller = new StreamController() | |
251 ..add(1)..add(2)..add(3)..close(); | |
252 expect(Chain.track(controller.stream).toList(), | |
253 completion(equals([1, 2, 3]))); | |
254 | |
255 var trace = new Trace.current(); | |
256 controller = new StreamController()..addError('error', trace); | |
257 expect(Chain.track(controller.stream).toList() | |
258 .catchError((e, stackTrace) { | |
259 expect(e, equals('error')); | |
260 expect(stackTrace.toString(), equals(trace.toString())); | |
261 }), completes); | |
262 }); | |
263 }); | |
264 | |
265 test('forwards stream values outside of Chain.capture()', () { | |
266 Chain.capture(() { | |
267 var controller = new StreamController() | |
268 ..add(1)..add(2)..add(3)..close(); | |
269 expect(Chain.track(controller.stream).toList(), | |
270 completion(equals([1, 2, 3]))); | |
271 | |
272 var trace = new Trace.current(); | |
273 controller = new StreamController()..addError('error', trace); | |
274 expect(Chain.track(controller.stream).toList() | |
275 .catchError((e, stackTrace) { | |
276 expect(e, equals('error')); | |
277 expect(stackTrace.toString(), equals(trace.toString())); | |
278 }), completes); | |
279 }); | |
280 }); | |
281 }); | |
282 } | |
OLD | NEW |