Write log trace to $PUB_CACHE/log/pub_log.txt when crashing (#3240)
And also when given --verbose.
diff --git a/lib/src/command.dart b/lib/src/command.dart
index 3f06db1..556c04f 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -10,6 +10,7 @@
import 'package:collection/collection.dart' show IterableExtension;
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
import 'authentication/token_store.dart';
import 'command_runner.dart';
@@ -55,7 +56,11 @@
return a;
}
- String get directory => argResults['directory'] ?? _pubTopLevel.directory;
+ String get directory =>
+ (argResults.options.contains('directory')
+ ? argResults['directory']
+ : null) ??
+ _pubTopLevel.directory;
late final SystemCache cache = SystemCache(isOffline: isOffline);
@@ -170,12 +175,11 @@
@nonVirtual
FutureOr<int> run() async {
computeCommand(_pubTopLevel.argResults);
- if (_pubTopLevel.trace) {
- log.recordTranscript();
- }
+
log.verbosity = _pubTopLevel.verbosity;
log.fine('Pub ${sdk.version}');
+ var crashed = false;
try {
await captureErrors<void>(() async => runProtected(),
captureStackChains: _pubTopLevel.captureStackChains);
@@ -187,8 +191,24 @@
log.exception(error, chain);
if (_pubTopLevel.trace) {
- log.dumpTranscript();
+ log.dumpTranscriptToStdErr();
} else if (!isUserFacingException(error)) {
+ log.error('''
+This is an unexpected error. The full log and other details are collected in:
+
+ $transcriptPath
+
+Consider creating an issue on https://github.com/dart-lang/pub/issues/new
+and attaching the relevant parts of that log file.
+''');
+ crashed = true;
+ }
+ return _chooseExitCode(error);
+ } finally {
+ final verbose = _pubTopLevel.verbosity == log.Verbosity.all;
+
+ // Write the whole log transcript to file.
+ if (verbose || crashed) {
// Escape the argument for users to copy-paste in bash.
// Wrap with single quotation, and use '\'' to insert single quote, as
// long as we have no spaces this doesn't create a new argument.
@@ -196,16 +216,23 @@
RegExp(r'^[a-zA-Z0-9-_]+$').stringMatch(x) == null
? "'${x.replaceAll("'", r"'\''")}'"
: x;
- log.error("""
-This is an unexpected error. Please run
- dart pub --trace ${_topCommand.name} ${_topCommand.argResults!.arguments.map(protectArgument).join(' ')}
+ late final Entrypoint? e;
+ try {
+ e = entrypoint;
+ } on ApplicationException {
+ e = null;
+ }
+ log.dumpTranscriptToFile(
+ transcriptPath,
+ 'dart pub ${_topCommand.argResults!.arguments.map(protectArgument).join(' ')}',
+ e,
+ );
-and include the logs in an issue on https://github.com/dart-lang/pub/issues/new
-""");
+ if (!crashed) {
+ log.message('Logs written to $transcriptPath.');
+ }
}
- return _chooseExitCode(error);
- } finally {
httpClient.close();
}
}
@@ -288,6 +315,10 @@
}
_command = list.join(' ');
}
+
+ String get transcriptPath {
+ return p.join(cache.rootDir, 'log', 'pub_log.txt');
+ }
}
abstract class PubTopLevel {
diff --git a/lib/src/command_runner.dart b/lib/src/command_runner.dart
index 02faaf9..21f568b 100644
--- a/lib/src/command_runner.dart
+++ b/lib/src/command_runner.dart
@@ -72,6 +72,7 @@
default:
// No specific verbosity given, so check for the shortcut.
if (argResults['verbose']) return log.Verbosity.all;
+ if (runningFromTest) return log.Verbosity.testing;
return log.Verbosity.normal;
}
}
diff --git a/lib/src/log.dart b/lib/src/log.dart
index 930e0fb..db24d7a 100644
--- a/lib/src/log.dart
+++ b/lib/src/log.dart
@@ -12,9 +12,11 @@
import 'package:source_span/source_span.dart';
import 'package:stack_trace/stack_trace.dart';
+import 'entrypoint.dart';
import 'exceptions.dart';
import 'io.dart';
import 'progress.dart';
+import 'sdk.dart';
import 'transcript.dart';
import 'utils.dart';
@@ -36,7 +38,7 @@
/// The list of recorded log messages. Will only be recorded if
/// [recordTranscript()] is called.
-Transcript<_Entry>? _transcript;
+final Transcript<_Entry> _transcript = Transcript(_maxTranscript);
/// The currently-animated progress indicator, if any.
///
@@ -168,6 +170,16 @@
Level.fine: _logToStderrWithLabel
});
+ /// Shows all logs.
+ static const testing = Verbosity._('testing', {
+ Level.error: _logToStderrWithLabel,
+ Level.warning: _logToStderrWithLabel,
+ Level.message: _logToStdoutWithLabel,
+ Level.io: _logToStderrWithLabel,
+ Level.solver: _logToStderrWithLabel,
+ Level.fine: _logToStderrWithLabel
+ });
+
const Verbosity._(this.name, this._loggers);
final String name;
@@ -233,7 +245,7 @@
var logFn = verbosity._loggers[level];
if (logFn != null) logFn(entry);
- if (_transcript != null) _transcript!.add(entry);
+ _transcript.add(entry);
}
/// Logs the spawning of an [executable] process with [arguments] at [io]
@@ -313,18 +325,10 @@
}
}
-/// Enables recording of log entries.
-void recordTranscript() {
- _transcript = Transcript<_Entry>(_maxTranscript);
-}
-
-/// If [recordTranscript()] was called, then prints the previously recorded log
-/// transcript to stderr.
-void dumpTranscript() {
- if (_transcript == null) return;
-
+/// Prints the recorded log transcript to stderr.
+void dumpTranscriptToStdErr() {
stderr.writeln('---- Log transcript ----');
- _transcript!.forEach((entry) {
+ _transcript.forEach((entry) {
_printToStream(stderr, entry, showLabel: true);
}, (discarded) {
stderr.writeln('---- ($discarded discarded) ----');
@@ -332,6 +336,68 @@
stderr.writeln('---- End log transcript ----');
}
+String _limit(String input, int limit) {
+ const snip = '[...]';
+ if (input.length < limit - snip.length) return input;
+ return '${input.substring(0, limit ~/ 2 - snip.length)}'
+ '$snip'
+ '${input.substring(limit)}';
+}
+
+/// Prints relevant system information and the log transcript to [path].
+void dumpTranscriptToFile(String path, String command, Entrypoint? entrypoint) {
+ final buffer = StringBuffer();
+ buffer.writeln('''
+Information about the latest pub run.
+
+If you believe something is not working right, you can go to
+https://github.com/dart-lang/pub/issues/new to post a new issue and attach this file.
+
+Before making this file public, make sure to remove any sensitive information!
+
+Pub version: ${sdk.version}
+Created: ${DateTime.now().toIso8601String()}
+FLUTTER_ROOT: ${Platform.environment['FLUTTER_ROOT'] ?? '<not set>'}
+PUB_HOSTED_URL: ${Platform.environment['PUB_HOSTED_URL'] ?? '<not set>'}
+PUB_CACHE: "${Platform.environment['PUB_CACHE'] ?? '<not set>'}"
+Command: $command
+Platform: ${Platform.operatingSystem}
+''');
+
+ if (entrypoint != null) {
+ buffer.writeln('---- ${p.absolute(entrypoint.pubspecPath)} ----');
+ if (fileExists(entrypoint.pubspecPath)) {
+ buffer.writeln(_limit(readTextFile(entrypoint.pubspecPath), 5000));
+ } else {
+ buffer.writeln('<No pubspec.yaml>');
+ }
+ buffer.writeln('---- End pubspec.yaml ----');
+ buffer.writeln('---- ${p.absolute(entrypoint.lockFilePath)} ----');
+ if (fileExists(entrypoint.lockFilePath)) {
+ buffer.writeln(_limit(readTextFile(entrypoint.lockFilePath), 5000));
+ } else {
+ buffer.writeln('<No pubspec.lock>');
+ }
+ buffer.writeln('---- End pubspec.lock ----');
+ }
+
+ buffer.writeln('---- Log transcript ----');
+
+ _transcript.forEach((entry) {
+ _printToStream(buffer, entry, showLabel: true);
+ }, (discarded) {
+ buffer.writeln('---- ($discarded entries discarded) ----');
+ });
+ buffer.writeln('---- End log transcript ----');
+ ensureDir(p.dirname(path));
+ try {
+ writeTextFile(path, buffer.toString(), dontLogContents: true);
+ } on IOException catch (e) {
+ stderr.writeln('Failed writing log to `$path` ($e), writing it to stderr:');
+ dumpTranscriptToStdErr();
+ }
+}
+
/// Filter out normal pub output when not attached to a terminal
///
/// Unless the user has overriden the verbosity,
@@ -492,7 +558,7 @@
_printToStream(sink, entry, showLabel: showLabel);
}
-void _printToStream(IOSink sink, _Entry entry, {required bool showLabel}) {
+void _printToStream(StringSink sink, _Entry entry, {required bool showLabel}) {
_stopProgress();
var firstLine = true;
diff --git a/test/embedding/embedding_test.dart b/test/embedding/embedding_test.dart
index ccb16f6..a24d5d5 100644
--- a/test/embedding/embedding_test.dart
+++ b/test/embedding/embedding_test.dart
@@ -6,8 +6,10 @@
import 'dart:io';
import 'package:path/path.dart' as path;
+import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_process/test_process.dart';
+
import '../descriptor.dart' as d;
import '../golden_file.dart';
import '../test_pub.dart';
@@ -16,6 +18,8 @@
late String snapshot;
+final logFile = p.join(d.sandbox, cachePath, 'log', 'pub_log.txt');
+
/// Runs `dart tool/test-bin/pub_command_runner.dart [args]` and appends the output to [buffer].
Future<void> runEmbeddingToBuffer(
List<String> args,
@@ -37,12 +41,9 @@
buffer.writeln([
'\$ $_commandRunner ${args.join(' ')}',
- ...await process.stdout.rest.toList(),
+ ...await process.stdout.rest.map(_filter).toList(),
+ ...await process.stderr.rest.map((e) => '[E] ${_filter(e)}').toList(),
].join('\n'));
- final stdErr = await process.stderr.rest.toList();
- if (stdErr.isNotEmpty) {
- buffer.writeln(stdErr.map((e) => '[E] $e').join('\n'));
- }
buffer.write('\n');
}
@@ -51,7 +52,7 @@
/// next section in golden file.
Future<void> runEmbedding(
List<String> args, {
- String? workingDirextory,
+ String? workingDirectory,
Map<String, String>? environment,
dynamic exitCode = 0,
}) async {
@@ -59,7 +60,7 @@
await runEmbeddingToBuffer(
args,
buffer,
- workingDirectory: workingDirextory,
+ workingDirectory: workingDirectory,
environment: environment,
exitCode: exitCode,
);
@@ -82,6 +83,7 @@
});
testWithGolden('run works, though hidden', (ctx) async {
+ await servePackages();
await d.dir(appPath, [
d.pubspec({
'name': 'myapp',
@@ -101,12 +103,53 @@
]).create();
await ctx.runEmbedding(
['pub', 'get'],
- workingDirextory: d.path(appPath),
+ workingDirectory: d.path(appPath),
);
await ctx.runEmbedding(
['pub', 'run', 'bin/main.dart'],
exitCode: 123,
- workingDirextory: d.path(appPath),
+ workingDirectory: d.path(appPath),
+ );
+ });
+
+ testWithGolden(
+ 'logfile is written with --verbose and on unexpected exceptions',
+ (context) async {
+ final server = await servePackages();
+ server.serve('foo', '1.0.0');
+ await d.appDir({'foo': 'any'}).create();
+
+ // TODO(sigurdm) This logs the entire verbose trace to a golden file.
+ //
+ // This is fragile, and can break for all sorts of small reasons. We think
+ // this might be worth while having to have at least minimal testing of the
+ // verbose stack trace.
+ //
+ // But if you, future contributor, think this test is annoying: feel free to
+ // remove it, or rewrite it to filter out the stack-trace itself, only
+ // testing for creation of the file.
+ //
+ // It is a fragile test, and we acknowledge that it's usefulness can be
+ // debated...
+ await context.runEmbedding(
+ ['pub', '--verbose', 'get'],
+ workingDirectory: d.path(appPath),
+ );
+ context.expectNextSection(
+ _filter(
+ File(logFile).readAsStringSync(),
+ ),
+ );
+ await d.dir('empty').create();
+ await context.runEmbedding(
+ ['pub', 'fail'],
+ workingDirectory: d.path('empty'),
+ exitCode: 1,
+ );
+ context.expectNextSection(
+ _filter(
+ File(logFile).readAsStringSync(),
+ ),
);
});
@@ -175,5 +218,106 @@
}
},
});
+ // Don't write the logs to file on a normal run.
+ expect(File(logFile).existsSync(), isFalse);
});
}
+
+String _filter(String input) {
+ return input
+ .replaceAll(p.toUri(d.sandbox).toString(), r'file://$SANDBOX')
+ .replaceAll(d.sandbox, r'$SANDBOX')
+ .replaceAll(Platform.pathSeparator, '/')
+ .replaceAll(Platform.operatingSystem, r'$OS')
+ .replaceAll(globalServer.port.toString(), r'$PORT')
+ .replaceAll(
+ RegExp(r'^Created:(.*)$', multiLine: true),
+ r'Created: $TIME',
+ )
+ .replaceAll(
+ RegExp(r'Generated by pub on (.*)$', multiLine: true),
+ r'Generated by pub on $TIME',
+ )
+ .replaceAll(
+ RegExp(r'X-Pub-Session-ID(.*)$', multiLine: true),
+ r'X-Pub-Session-ID: $ID',
+ )
+ .replaceAll(
+ RegExp(r'took (.*)$', multiLine: true),
+ r'took: $TIME',
+ )
+ .replaceAll(
+ RegExp(r'date: (.*)$', multiLine: true),
+ r'date: $TIME',
+ )
+ .replaceAll(
+ RegExp(r'Creating (.*) from stream\.$', multiLine: true),
+ r'Creating $FILE from stream',
+ )
+ .replaceAll(
+ RegExp(r'Created (.*) from stream\.$', multiLine: true),
+ r'Created $FILE from stream',
+ )
+ .replaceAll(
+ RegExp(r'Renaming directory $SANDBOX/cache/_temp/(.*?) to',
+ multiLine: true),
+ r'Renaming directory $SANDBOX/cache/_temp/',
+ )
+ .replaceAll(
+ RegExp(r'Extracting .tar.gz stream to (.*?)$', multiLine: true),
+ r'Extracting .tar.gz stream to $DIR',
+ )
+ .replaceAll(
+ RegExp(r'Extracted .tar.gz to (.*?)$', multiLine: true),
+ r'Extracted .tar.gz to $DIR',
+ )
+ .replaceAll(
+ RegExp(r'Reading binary file (.*?)$', multiLine: true),
+ r'Reading binary file $FILE.',
+ )
+ .replaceAll(
+ RegExp(r'Deleting directory (.*)$', multiLine: true),
+ r'Deleting directory $DIR',
+ )
+ .replaceAll(
+ RegExp(r'Deleting directory (.*)$', multiLine: true),
+ r'Deleting directory $DIR',
+ )
+ .replaceAll(
+ RegExp(r'Resolving dependencies finished (.*)$', multiLine: true),
+ r'Resolving dependencies finished ($TIME)',
+ )
+ .replaceAll(
+ RegExp(r'Created temp directory (.*)$', multiLine: true),
+ r'Created temp directory $DIR',
+ )
+ .replaceAll(
+ RegExp(r'Renaming directory (.*)$', multiLine: true),
+ r'Renaming directory $A to $B',
+ )
+ .replaceAll(
+ RegExp(r'"_fetchedAt":"(.*)"}$', multiLine: true),
+ r'"_fetchedAt": "$TIME"}',
+ )
+ .replaceAll(
+ RegExp(r'"generated": "(.*)",$', multiLine: true),
+ r'"generated": "$TIME",',
+ )
+ .replaceAll(
+ RegExp(r'( |^)(/|[A-Z]:)(.*)/tool/test-bin/pub_command_runner.dart',
+ multiLine: true),
+ r' tool/test-bin/pub_command_runner.dart',
+ )
+ .replaceAll(
+ RegExp(r'[ ]{4,}', multiLine: true),
+ r' ',
+ )
+ .replaceAll(
+ RegExp(r' [\d]+:[\d]+ ', multiLine: true),
+ r' $LINE:$COL ',
+ )
+ .replaceAll(
+ RegExp(r'Writing \d+ characters', multiLine: true),
+ r'Writing $N characters',
+ );
+}
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 025a0a4..fbf63de 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -466,7 +466,7 @@
var dartArgs = ['--packages=$dotPackagesPath', '--enable-asserts'];
dartArgs
- ..addAll([pubPath, if (verbose) '--verbose'])
+ ..addAll([pubPath, if (!verbose) '--verbosity=normal'])
..addAll(args);
final mergedEnvironment = getPubTestEnvironment(tokenEndpoint);
diff --git a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
new file mode 100644
index 0000000..1d3a5f5
--- /dev/null
+++ b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
@@ -0,0 +1,328 @@
+# GENERATED BY: test/embedding/embedding_test.dart
+
+$ tool/test-bin/pub_command_runner.dart pub --verbose get
+MSG : Resolving dependencies...
+MSG : + foo 1.0.0
+MSG : Downloading foo 1.0.0...
+MSG : Changed 1 dependency!
+MSG : Logs written to $SANDBOX/cache/log/pub_log.txt.
+[E] FINE: Pub 0.1.2+3
+[E] SLVR: fact: myapp is 0.0.0
+[E] SLVR: derived: myapp
+[E] SLVR: fact: myapp depends on foo any
+[E] SLVR: selecting myapp
+[E] SLVR: derived: foo any
+[E] IO : Get versions from http://localhost:$PORT/api/packages/foo.
+[E] IO : HTTP GET http://localhost:$PORT/api/packages/foo
+[E] | Accept: application/vnd.pub.v2+json
+[E] | X-Pub-OS: $OS
+[E] | X-Pub-Command: get
+[E] | X-Pub-Session-ID: $ID
+[E] | X-Pub-Environment: test-environment
+[E] | X-Pub-Reason: direct
+[E] | user-agent: Dart pub 0.1.2+3
+[E] IO : HTTP response 200 OK for GET http://localhost:$PORT/api/packages/foo
+[E] | took: $TIME
+[E] | date: $TIME
+[E] | content-length: 197
+[E] | x-frame-options: SAMEORIGIN
+[E] | content-type: text/plain; charset=utf-8
+[E] | x-xss-protection: 1; mode=block
+[E] | x-content-type-options: nosniff
+[E] | server: dart:io with Shelf
+[E] IO : Writing $N characters to text file $SANDBOX/cache/hosted/localhost%58$PORT/.cache/foo-versions.json.
+[E] FINE: Contents:
+[E] | {"name":"foo","uploaders":["nweiz@google.com"],"versions":[{"pubspec":{"name":"foo","version":"1.0.0"},"version":"1.0.0","archive_url":"http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz"}],"_fetchedAt": "$TIME"}
+[E] SLVR: selecting foo 1.0.0
+[E] SLVR: Version solving took: $TIME
+[E] | Tried 1 solutions.
+[E] FINE: Resolving dependencies finished ($TIME)
+[E] IO : Get package from http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz.
+[E] IO : Created temp directory $DIR
+[E] IO : HTTP GET http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz
+[E] | X-Pub-OS: $OS
+[E] | X-Pub-Command: get
+[E] | X-Pub-Session-ID: $ID
+[E] | X-Pub-Environment: test-environment
+[E] | X-Pub-Reason: direct
+[E] | user-agent: Dart pub 0.1.2+3
+[E] IO : HTTP response 200 OK for GET http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz
+[E] | took: $TIME
+[E] | transfer-encoding: chunked
+[E] | date: $TIME
+[E] | x-frame-options: SAMEORIGIN
+[E] | content-type: text/plain; charset=utf-8
+[E] | x-xss-protection: 1; mode=block
+[E] | x-content-type-options: nosniff
+[E] | server: dart:io with Shelf
+[E] IO : Creating $FILE from stream
+[E] FINE: Created $FILE from stream
+[E] IO : Created temp directory $DIR
+[E] IO : Reading binary file $FILE.
+[E] FINE: Extracting .tar.gz stream to $DIR
+[E] IO : Creating $FILE from stream
+[E] FINE: Created $FILE from stream
+[E] IO : Creating $FILE from stream
+[E] FINE: Created $FILE from stream
+[E] FINE: Extracted .tar.gz to $DIR
+[E] IO : Renaming directory $A to $B
+[E] IO : Deleting directory $DIR
+[E] IO : Writing $N characters to text file pubspec.lock.
+[E] FINE: Contents:
+[E] | # Generated by pub
+[E] | # See https://dart.dev/tools/pub/glossary#lockfile
+[E] | packages:
+[E] | foo:
+[E] | dependency: "direct main"
+[E] | description:
+[E] | name: foo
+[E] | url: "http://localhost:$PORT"
+[E] | source: hosted
+[E] | version: "1.0.0"
+[E] | sdks:
+[E] | dart: ">=0.1.2 <1.0.0"
+[E] IO : Writing $N characters to text file .packages.
+[E] FINE: Contents:
+[E] | # This file is deprecated. Tools should instead consume
+[E] | # `.dart_tool/package_config.json`.
+[E] | #
+[E] | # For more info see: https://dart.dev/go/dot-packages-deprecation
+[E] | #
+[E] | # Generated by pub on $TIME
+[E] | foo:file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0/lib/
+[E] | myapp:lib/
+[E] IO : Writing $N characters to text file .dart_tool/package_config.json.
+[E] FINE: Contents:
+[E] | {
+[E] | "configVersion": 2,
+[E] | "packages": [
+[E] | {
+[E] | "name": "foo",
+[E] | "rootUri": "file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0",
+[E] | "packageUri": "lib/",
+[E] | "languageVersion": "2.7"
+[E] | },
+[E] | {
+[E] | "name": "myapp",
+[E] | "rootUri": "../",
+[E] | "packageUri": "lib/",
+[E] | "languageVersion": "0.1"
+[E] | }
+[E] | ],
+[E] | "generated": "$TIME",
+[E] | "generator": "pub",
+[E] | "generatorVersion": "0.1.2+3"
+[E] | }
+[E] IO : Writing $N characters to text file $SANDBOX/cache/log/pub_log.txt.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+Information about the latest pub run.
+
+If you believe something is not working right, you can go to
+https://github.com/dart-lang/pub/issues/new to post a new issue and attach this file.
+
+Before making this file public, make sure to remove any sensitive information!
+
+Pub version: 0.1.2+3
+Created: $TIME
+FLUTTER_ROOT: <not set>
+PUB_HOSTED_URL: http://localhost:$PORT
+PUB_CACHE: "$SANDBOX/cache"
+Command: dart pub --verbose get
+Platform: $OS
+
+---- $SANDBOX/myapp/pubspec.yaml ----
+{"name":"myapp","environment":{"sdk":">=0.1.2 <1.0.0"},"dependencies":{"foo":"any"}}
+---- End pubspec.yaml ----
+---- $SANDBOX/myapp/pubspec.lock ----
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ foo:
+ dependency: "direct main"
+ description:
+ name: foo
+ url: "http://localhost:$PORT"
+ source: hosted
+ version: "1.0.0"
+sdks:
+ dart: ">=0.1.2 <1.0.0"
+
+---- End pubspec.lock ----
+---- Log transcript ----
+FINE: Pub 0.1.2+3
+MSG : Resolving dependencies...
+SLVR: fact: myapp is 0.0.0
+SLVR: derived: myapp
+SLVR: fact: myapp depends on foo any
+SLVR: selecting myapp
+SLVR: derived: foo any
+IO : Get versions from http://localhost:$PORT/api/packages/foo.
+IO : HTTP GET http://localhost:$PORT/api/packages/foo
+ | Accept: application/vnd.pub.v2+json
+ | X-Pub-OS: $OS
+ | X-Pub-Command: get
+ | X-Pub-Session-ID: $ID
+ | X-Pub-Environment: test-environment
+ | X-Pub-Reason: direct
+ | user-agent: Dart pub 0.1.2+3
+IO : HTTP response 200 OK for GET http://localhost:$PORT/api/packages/foo
+ | took: $TIME
+ | date: $TIME
+ | content-length: 197
+ | x-frame-options: SAMEORIGIN
+ | content-type: text/plain; charset=utf-8
+ | x-xss-protection: 1; mode=block
+ | x-content-type-options: nosniff
+ | server: dart:io with Shelf
+IO : Writing $N characters to text file $SANDBOX/cache/hosted/localhost%58$PORT/.cache/foo-versions.json.
+FINE: Contents:
+ | {"name":"foo","uploaders":["nweiz@google.com"],"versions":[{"pubspec":{"name":"foo","version":"1.0.0"},"version":"1.0.0","archive_url":"http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz"}],"_fetchedAt": "$TIME"}
+SLVR: selecting foo 1.0.0
+SLVR: Version solving took: $TIME
+ | Tried 1 solutions.
+FINE: Resolving dependencies finished ($TIME)
+MSG : + foo 1.0.0
+IO : Get package from http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz.
+MSG : Downloading foo 1.0.0...
+IO : Created temp directory $DIR
+IO : HTTP GET http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz
+ | X-Pub-OS: $OS
+ | X-Pub-Command: get
+ | X-Pub-Session-ID: $ID
+ | X-Pub-Environment: test-environment
+ | X-Pub-Reason: direct
+ | user-agent: Dart pub 0.1.2+3
+IO : HTTP response 200 OK for GET http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz
+ | took: $TIME
+ | transfer-encoding: chunked
+ | date: $TIME
+ | x-frame-options: SAMEORIGIN
+ | content-type: text/plain; charset=utf-8
+ | x-xss-protection: 1; mode=block
+ | x-content-type-options: nosniff
+ | server: dart:io with Shelf
+IO : Creating $FILE from stream
+FINE: Created $FILE from stream
+IO : Created temp directory $DIR
+IO : Reading binary file $FILE.
+FINE: Extracting .tar.gz stream to $DIR
+IO : Creating $FILE from stream
+FINE: Created $FILE from stream
+IO : Creating $FILE from stream
+FINE: Created $FILE from stream
+FINE: Extracted .tar.gz to $DIR
+IO : Renaming directory $A to $B
+IO : Deleting directory $DIR
+IO : Writing $N characters to text file pubspec.lock.
+FINE: Contents:
+ | # Generated by pub
+ | # See https://dart.dev/tools/pub/glossary#lockfile
+ | packages:
+ | foo:
+ | dependency: "direct main"
+ | description:
+ | name: foo
+ | url: "http://localhost:$PORT"
+ | source: hosted
+ | version: "1.0.0"
+ | sdks:
+ | dart: ">=0.1.2 <1.0.0"
+MSG : Changed 1 dependency!
+IO : Writing $N characters to text file .packages.
+FINE: Contents:
+ | # This file is deprecated. Tools should instead consume
+ | # `.dart_tool/package_config.json`.
+ | #
+ | # For more info see: https://dart.dev/go/dot-packages-deprecation
+ | #
+ | # Generated by pub on $TIME
+ | foo:file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0/lib/
+ | myapp:lib/
+IO : Writing $N characters to text file .dart_tool/package_config.json.
+FINE: Contents:
+ | {
+ | "configVersion": 2,
+ | "packages": [
+ | {
+ | "name": "foo",
+ | "rootUri": "file://$SANDBOX/cache/hosted/localhost%2558$PORT/foo-1.0.0",
+ | "packageUri": "lib/",
+ | "languageVersion": "2.7"
+ | },
+ | {
+ | "name": "myapp",
+ | "rootUri": "../",
+ | "packageUri": "lib/",
+ | "languageVersion": "0.1"
+ | }
+ | ],
+ | "generated": "$TIME",
+ | "generator": "pub",
+ | "generatorVersion": "0.1.2+3"
+ | }
+---- End log transcript ----
+-------------------------------- END OF OUTPUT ---------------------------------
+
+$ tool/test-bin/pub_command_runner.dart pub fail
+[E] Bad state: Pub has crashed
+[E] tool/test-bin/pub_command_runner.dart $LINE:$COL ThrowingCommand.runProtected
+[E] package:pub/src/command.dart $LINE:$COL PubCommand.run.<fn>
+[E] package:pub/src/command.dart $LINE:$COL PubCommand.run.<fn>
+[E] dart:async new Future.sync
+[E] package:pub/src/utils.dart $LINE:$COL captureErrors.wrappedCallback
+[E] dart:async runZonedGuarded
+[E] package:pub/src/utils.dart $LINE:$COL captureErrors
+[E] package:pub/src/command.dart $LINE:$COL PubCommand.run
+[E] package:args/command_runner.dart $LINE:$COL CommandRunner.runCommand
+[E] tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.runCommand
+[E] tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.run
+[E] tool/test-bin/pub_command_runner.dart $LINE:$COL main
+[E] This is an unexpected error. The full log and other details are collected in:
+[E]
+[E] $SANDBOX/cache/log/pub_log.txt
+[E]
+[E] Consider creating an issue on https://github.com/dart-lang/pub/issues/new
+[E] and attaching the relevant parts of that log file.
+
+-------------------------------- END OF OUTPUT ---------------------------------
+
+Information about the latest pub run.
+
+If you believe something is not working right, you can go to
+https://github.com/dart-lang/pub/issues/new to post a new issue and attach this file.
+
+Before making this file public, make sure to remove any sensitive information!
+
+Pub version: 0.1.2+3
+Created: $TIME
+FLUTTER_ROOT: <not set>
+PUB_HOSTED_URL: http://localhost:$PORT
+PUB_CACHE: "$SANDBOX/cache"
+Command: dart pub fail
+Platform: $OS
+
+---- Log transcript ----
+FINE: Pub 0.1.2+3
+ERR : Bad state: Pub has crashed
+FINE: Exception type: StateError
+ERR : tool/test-bin/pub_command_runner.dart $LINE:$COL ThrowingCommand.runProtected
+ | package:pub/src/command.dart $LINE:$COL PubCommand.run.<fn>
+ | package:pub/src/command.dart $LINE:$COL PubCommand.run.<fn>
+ | dart:async new Future.sync
+ | package:pub/src/utils.dart $LINE:$COL captureErrors.wrappedCallback
+ | dart:async runZonedGuarded
+ | package:pub/src/utils.dart $LINE:$COL captureErrors
+ | package:pub/src/command.dart $LINE:$COL PubCommand.run
+ | package:args/command_runner.dart $LINE:$COL CommandRunner.runCommand
+ | tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.runCommand
+ | tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.run
+ | tool/test-bin/pub_command_runner.dart $LINE:$COL main
+ERR : This is an unexpected error. The full log and other details are collected in:
+ |
+ | $SANDBOX/cache/log/pub_log.txt
+ |
+ | Consider creating an issue on https://github.com/dart-lang/pub/issues/new
+ | and attaching the relevant parts of that log file.
+---- End log transcript ----
diff --git a/tool/test-bin/pub_command_runner.dart b/tool/test-bin/pub_command_runner.dart
index 2be51e8..206a95b 100644
--- a/tool/test-bin/pub_command_runner.dart
+++ b/tool/test-bin/pub_command_runner.dart
@@ -9,12 +9,30 @@
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:pub/pub.dart';
+import 'package:pub/src/command.dart';
import 'package:pub/src/exit_codes.dart' as exit_codes;
import 'package:pub/src/log.dart' as log;
import 'package:usage/usage.dart';
final _LoggingAnalytics loggingAnalytics = _LoggingAnalytics();
+// A command for explicitly throwing an exception, to test the handling of
+// unexpected eceptions.
+class ThrowingCommand extends PubCommand {
+ @override
+ String get name => 'fail';
+
+ @override
+ String get description => 'Throws an exception';
+
+ bool get hide => true;
+
+ @override
+ Future<int> runProtected() async {
+ throw StateError('Pub has crashed');
+ }
+}
+
class Runner extends CommandRunner<int> {
late ArgResults _options;
@@ -23,7 +41,8 @@
? PubAnalytics(() => loggingAnalytics,
dependencyKindCustomDimensionName: 'cd1')
: null;
- addCommand(pubCommand(analytics: analytics));
+ addCommand(
+ pubCommand(analytics: analytics)..addSubcommand(ThrowingCommand()));
}
@override