Skip to content
Snippets Groups Projects
Commit 2aa4cb25 authored by Natalie Weizenbaum's avatar Natalie Weizenbaum
Browse files

Refactor how stack trace formatting is configured

This makes the configuration zone-scoped rather than global to a
worker.
parent 75c760c0
No related branches found
No related tags found
No related merge requests found
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:stack_trace/stack_trace.dart';
import '../util/stack_trace_mapper.dart';
import 'invoker.dart';
/// The key used to look up [StackTraceFormatter.current] in a zone.
final _currentKey = new Object();
/// A class that tracks how to format a stack trace according to the user's
/// configuration.
///
/// This can convert JavaScript stack traces to Dart using source maps, and fold
/// irrelevant frames out of the stack trace.
class StackTraceFormatter {
/// A class that converts [trace] into a Dart stack trace, or `null` to use it
/// as-is.
StackTraceMapper _mapper;
/// The list of packages to fold when producing terse [Chain]s.
var _except = new Set<String>.from(['test', 'stream_channel']);
/// If non-empty, all packages not in this list will be folded when producing
/// terse [Chain]s.
var _only = new Set<String>();
/// Returns the current manager, or `null` if this isn't called within a call
/// to [asCurrent].
static StackTraceFormatter get current =>
Zone.current[_currentKey] as StackTraceFormatter;
/// Runs [body] with [this] as [StackTraceFormatter.current].
///
/// This is zone-scoped, so [this] will be the current configuration in any
/// asynchronous callbacks transitively created by [body].
T asCurrent<T>(T body()) => runZoned(body, zoneValues: {_currentKey: this});
/// Configure how stack traces are formatted.
///
/// The [mapper] is used to convert JavaScript traces into Dart traces. The
/// [except] set indicates packages whose frames should be folded away. If
/// [only] is non-empty, it indicates packages whose frames should *not* be
/// folded away.
void configure(
{StackTraceMapper mapper, Set<String> except, Set<String> only}) {
if (mapper != null) _mapper = mapper;
if (except != null) _except = except;
if (only != null) _only = only;
}
/// Converts [stackTrace] to a [Chain] and formats it according to the user's
/// preferences.
///
/// If [verbose] is `true`, this doesn't fold out irrelevant stack frames. It
/// defaults to the current test's [Metadata.verboseTrace] configuration, or
/// `false` if there is no current test.
Chain formatStackTrace(StackTrace stackTrace, {bool verbose}) {
verbose ??=
Invoker.current?.liveTest?.test?.metadata?.verboseTrace ?? false;
var chain =
new Chain.forTrace(_mapper?.mapStackTrace(stackTrace) ?? stackTrace);
if (verbose) return chain;
return chain.foldFrames((frame) {
if (_only.isNotEmpty) return !_only.contains(frame.package);
return _except.contains(frame.package);
}, terse: true);
}
}
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:stack_trace/stack_trace.dart';
import '../backend/stack_trace_formatter.dart';
/// Converts [stackTrace] to a [Chain] according to the current test's
/// configuration.
///
/// If [verbose] is `true`, this doesn't fold out irrelevant stack frames. It
/// defaults to the current test's `verbose_trace` configuration.
Chain formatStackTrace(StackTrace stackTrace, {bool verbose}) =>
StackTraceFormatter.current.formatStackTrace(stackTrace, verbose: verbose);
......@@ -8,8 +8,8 @@ import 'package:async/async.dart';
import 'package:matcher/matcher.dart';
import '../utils.dart';
import 'test_chain.dart';
import 'async_matcher.dart';
import 'format_stack_trace.dart';
/// The type for [_StreamMatcher._matchQueue].
typedef Future<String> _MatchQueue(StreamQueue queue);
......@@ -165,7 +165,7 @@ class _StreamMatcher extends AsyncMatcher implements StreamMatcher {
return addBullet(event.asValue.value.toString());
} else {
var error = event.asError;
var chain = testChain(error.stackTrace);
var chain = formatStackTrace(error.stackTrace);
var text = "${error.error}\n$chain";
return prefixLines(text, " ", first: "! ");
}
......
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:stack_trace/stack_trace.dart';
import '../backend/invoker.dart';
import '../util/stack_trace_mapper.dart';
/// Converts [trace] into a Dart stack trace
StackTraceMapper _mapper;
/// The list of packages to fold when producing [Chain]s.
Set<String> _exceptPackages = new Set.from(['test', 'stream_channel']);
/// If non-empty, all packages not in this list will be folded when producing
/// [Chain]s.
Set<String> _onlyPackages = new Set();
/// Configure the resources used for test chaining.
///
/// [mapper] is used to convert traces into Dart stack traces.
/// [exceptPackages] is the list of packages to fold when producing a [Chain].
/// [onlyPackages] is the list of packages to keep in a [Chain]. If non-empty,
/// all packages not in this will be folded.
void configureTestChaining(
{StackTraceMapper mapper,
Set<String> exceptPackages,
Set<String> onlyPackages}) {
if (mapper != null) _mapper = mapper;
if (exceptPackages != null) _exceptPackages = exceptPackages;
if (onlyPackages != null) _onlyPackages = onlyPackages;
}
/// Returns [stackTrace] converted to a [Chain] with all irrelevant frames
/// folded together.
///
/// If [verbose] is `true`, returns the chain for [stackTrace] unmodified.
Chain terseChain(StackTrace stackTrace, {bool verbose: false}) {
var testTrace = _mapper?.mapStackTrace(stackTrace) ?? stackTrace;
if (verbose) return new Chain.forTrace(testTrace);
return new Chain.forTrace(testTrace).foldFrames((frame) {
if (_onlyPackages.isNotEmpty) {
return !_onlyPackages.contains(frame.package);
}
return _exceptPackages.contains(frame.package);
}, terse: true);
}
/// Converts [stackTrace] to a [Chain] following the test's configuration.
Chain testChain(StackTrace stackTrace) => terseChain(stackTrace,
verbose: Invoker.current?.liveTest?.test?.metadata?.verboseTrace ?? true);
......@@ -8,7 +8,7 @@ import 'package:matcher/matcher.dart';
import '../utils.dart';
import 'async_matcher.dart';
import '../frontend/test_chain.dart';
import 'format_stack_trace.dart';
/// This function is deprecated.
///
......@@ -94,7 +94,8 @@ class Throws extends AsyncMatcher {
var buffer = new StringBuffer();
buffer.writeln(indent(prettyPrint(error), first: 'threw '));
if (trace != null) {
buffer.writeln(indent(testChain(trace).toString(), first: 'stack '));
buffer
.writeln(indent(formatStackTrace(trace).toString(), first: 'stack '));
}
if (result.isNotEmpty) buffer.writeln(indent(result, first: 'which '));
return buffer.toString().trimRight();
......
......@@ -14,10 +14,10 @@ import '../backend/invoker.dart';
import '../backend/live_test.dart';
import '../backend/metadata.dart';
import '../backend/operating_system.dart';
import '../backend/stack_trace_formatter.dart';
import '../backend/suite.dart';
import '../backend/test.dart';
import '../backend/test_platform.dart';
import '../frontend/test_chain.dart';
import '../util/remote_exception.dart';
import '../util/stack_trace_mapper.dart';
import '../utils.dart';
......@@ -59,74 +59,77 @@ class RemoteListener {
channel.sink.add({"type": "print", "line": line});
});
runZoned(() {
new SuiteChannelManager().asCurrent(() async {
var main;
try {
main = getMain();
} on NoSuchMethodError catch (_) {
_sendLoadException(channel, "No top-level main() function defined.");
return;
} catch (error, stackTrace) {
new SuiteChannelManager().asCurrent(() {
new StackTraceFormatter().asCurrent(() {
runZoned(() async {
var main;
try {
main = getMain();
} on NoSuchMethodError catch (_) {
_sendLoadException(
channel, "No top-level main() function defined.");
return;
} catch (error, stackTrace) {
_sendError(channel, error, stackTrace, verboseChain);
return;
}
if (main is! Function) {
_sendLoadException(
channel, "Top-level main getter is not a function.");
return;
} else if (main is! AsyncFunction) {
_sendLoadException(
channel, "Top-level main() function takes arguments.");
return;
}
var queue = new StreamQueue(channel.stream);
var message = await queue.next;
assert(message['type'] == 'initial');
queue.rest.listen((message) {
assert(message["type"] == "suiteChannel");
SuiteChannelManager.current.connectIn(
message['name'], channel.virtualChannel(message['id']));
});
if (message['asciiGlyphs'] ?? false) glyph.ascii = true;
var metadata = new Metadata.deserialize(message['metadata']);
verboseChain = metadata.verboseTrace;
var declarer = new Declarer(
metadata: metadata,
platformVariables: new Set.from(message['platformVariables']),
collectTraces: message['collectTraces'],
noRetry: message['noRetry']);
StackTraceFormatter.current.configure(
mapper: StackTraceMapper.deserialize(message['stackTraceMapper']),
except: _deserializeSet(message['foldTraceExcept']),
only: _deserializeSet(message['foldTraceOnly']));
await declarer.declare(main);
var suite = new Suite(declarer.build(),
platform: new TestPlatform.deserialize(message['platform']),
os: message['os'] == null
? null
: OperatingSystem.find(message['os']),
path: message['path']);
runZoned(() {
Invoker.guard(
() => new RemoteListener._(suite, printZone)._listen(channel));
},
// Make the declarer visible to running tests so that they'll throw
// useful errors when calling `test()` and `group()` within a test,
// and so they can add to the declarer's `tearDownAll()` list.
zoneValues: {#test.declarer: declarer});
}, onError: (error, stackTrace) {
_sendError(channel, error, stackTrace, verboseChain);
return;
}
if (main is! Function) {
_sendLoadException(
channel, "Top-level main getter is not a function.");
return;
} else if (main is! AsyncFunction) {
_sendLoadException(
channel, "Top-level main() function takes arguments.");
return;
}
var queue = new StreamQueue(channel.stream);
var message = await queue.next;
assert(message['type'] == 'initial');
queue.rest.listen((message) {
assert(message["type"] == "suiteChannel");
SuiteChannelManager.current.connectIn(
message['name'], channel.virtualChannel(message['id']));
});
if (message['asciiGlyphs'] ?? false) glyph.ascii = true;
var metadata = new Metadata.deserialize(message['metadata']);
verboseChain = metadata.verboseTrace;
var declarer = new Declarer(
metadata: metadata,
platformVariables: new Set.from(message['platformVariables']),
collectTraces: message['collectTraces'],
noRetry: message['noRetry']);
configureTestChaining(
mapper: StackTraceMapper.deserialize(message['stackTraceMapper']),
exceptPackages: _deserializeSet(message['foldTraceExcept']),
onlyPackages: _deserializeSet(message['foldTraceOnly']));
await declarer.declare(main);
var suite = new Suite(declarer.build(),
platform: new TestPlatform.deserialize(message['platform']),
os: message['os'] == null
? null
: OperatingSystem.find(message['os']),
path: message['path']);
runZoned(() {
Invoker.guard(
() => new RemoteListener._(suite, printZone)._listen(channel));
},
// Make the declarer visible to running tests so that they'll throw
// useful errors when calling `test()` and `group()` within a test,
// and so they can add to the declarer's `tearDownAll()` list.
zoneValues: {#test.declarer: declarer});
}, zoneSpecification: spec);
});
}, onError: (error, stackTrace) {
_sendError(channel, error, stackTrace, verboseChain);
}, zoneSpecification: spec);
});
return controller.foreign;
}
......@@ -151,7 +154,9 @@ class RemoteListener {
channel.sink.add({
"type": "error",
"error": RemoteException.serialize(
error, terseChain(stackTrace, verbose: verboseChain))
error,
StackTraceFormatter.current
.formatStackTrace(stackTrace, verbose: verboseChain))
});
}
......@@ -230,7 +235,7 @@ class RemoteListener {
"type": "error",
"error": RemoteException.serialize(
asyncError.error,
terseChain(asyncError.stackTrace,
StackTraceFormatter.current.formatStackTrace(asyncError.stackTrace,
verbose: liveTest.test.metadata.verboseTrace))
});
});
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment