blob: 20c94d1034c3806b917798ddc4189a5baa20df90 [file] [log] [blame]
// Copyright (c) 2020, 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:io';
import 'package:path/path.dart' as p;
import 'package:pub/src/exit_codes.dart';
import 'package:pub/src/io.dart' show EnvironmentKeys;
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';
import 'ensure_pubspec_resolved.dart';
const _commandRunner = 'tool/test-bin/pub_command_runner.dart';
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,
StringBuffer buffer, {
String? workingDirectory,
Map<String, String?>? environment,
dynamic exitCode = 0,
}) async {
final combinedEnvironment = getPubTestEnvironment();
(environment ?? {}).forEach((key, value) {
if (value == null) {
combinedEnvironment.remove(key);
} else {
combinedEnvironment[key] = value;
}
});
final process = await TestProcess.start(
Platform.resolvedExecutable,
['--enable-asserts', snapshot, ...args],
environment: combinedEnvironment,
workingDirectory: workingDirectory,
);
await process.shouldExit(exitCode);
final stdoutLines = await process.stdout.rest.toList();
final stderrLines = await process.stderr.rest.toList();
buffer.writeln(
[
'\$ $_commandRunner ${args.join(' ')}',
if (stdoutLines.isNotEmpty) _filter(stdoutLines.join('\n')),
if (stderrLines.isNotEmpty)
_filter(stderrLines.join('\n'))
.replaceAll(RegExp('^', multiLine: true), '[E] '),
].join('\n'),
);
buffer.write('\n');
}
extension on GoldenTestContext {
/// Runs `dart tool/test-bin/pub_command_runner.dart [args]` and compare to
/// next section in golden file.
Future<void> runEmbedding(
List<String> args, {
String? workingDirectory,
Map<String, String>? environment,
dynamic exitCode = 0,
}) async {
final buffer = StringBuffer();
await runEmbeddingToBuffer(
args,
buffer,
workingDirectory: workingDirectory,
environment: environment,
exitCode: exitCode,
);
expectNextSection(buffer.toString());
}
}
Future<void> main() async {
setUpAll(() async {
final tempDir = Directory.systemTemp.createTempSync();
snapshot = p.join(tempDir.path, 'command_runner.dart.snapshot');
final r = Process.runSync(
Platform.resolvedExecutable,
['--snapshot=$snapshot', _commandRunner],
);
expect(r.exitCode, 0, reason: r.stderr as String);
});
tearDownAll(() {
File(snapshot).parent.deleteSync(recursive: true);
});
test('Can depend on package:flutter_gen', () async {
// Regression test for https://github.com/dart-lang/pub/issues/3314.
final server = await servePackages();
server.serve(
'flutter_gen',
'1.0.0',
contents: [
d.dir('bin', [d.file('flutter_gen.dart', 'main() {print("hi");}')]),
],
);
await d.appDir(
dependencies: {'flutter_gen': '^1.0.0'},
).create();
await pubGet();
final buffer = StringBuffer();
await runEmbeddingToBuffer(
['run', 'flutter_gen'],
buffer,
workingDirectory: d.path(appPath),
environment: getPubTestEnvironment(),
);
expect(buffer.toString(), contains('hi'));
});
testWithGolden('run works, though hidden', (ctx) async {
await servePackages();
await d.dir(appPath, [
d.appPubspec(),
d.dir('bin', [
d.file('main.dart', '''
import 'dart:io';
main() {
print('Hi');
exit(123);
}
'''),
]),
]).create();
await ctx.runEmbedding(
['pub', 'get'],
workingDirectory: d.path(appPath),
);
await ctx.runEmbedding(
['pub', 'run', 'bin/main.dart'],
exitCode: 123,
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(dependencies: {'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(),
),
);
});
test('`embedding --verbose pub` is verbose', () async {
await servePackages();
final buffer = StringBuffer();
await runEmbeddingToBuffer(['--verbose', 'pub', 'logout'], buffer);
expect(buffer.toString(), contains('FINE: Pub 3.1.2+3'));
});
testWithGolden('--help', (context) async {
await servePackages();
await context.runEmbedding(
['pub', '--help'],
workingDirectory: d.path('.'),
);
});
testWithGolden('--color forces colors', (context) async {
final server = await servePackages();
server.serve('foo', '1.0.0');
server.serve('foo', '2.0.0');
await d.appDir(dependencies: {'foo': '^1.0.0'}).create();
await context.runEmbedding(
['pub', '--no-color', 'get'],
environment: getPubTestEnvironment(),
workingDirectory: d.path(appPath),
);
await context.runEmbedding(
['pub', '--color', 'get'],
workingDirectory: d.path(appPath),
environment: getPubTestEnvironment(),
);
});
test('`embedding run` does `pub get` if sdk updated', () async {
await d.dir(appPath, [
d.pubspec({
'name': 'myapp',
'environment': {'sdk': '^2.18.0'},
'dependencies': {'foo': '^1.0.0'},
}),
d.dir('bin', [
d.file('myapp.dart', 'main() {print(42);}'),
]),
]).create();
final server = await servePackages();
server.serve(
'foo',
'1.0.0',
pubspec: {
'environment': {'sdk': '^2.18.0'},
},
);
await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '2.18.3'});
// Deleting the version-listing cache will cause it to be refetched, and the
// warning will happen.
File(p.join(globalServer.cachingPath, '.cache', 'foo-versions.json'))
.deleteSync();
server.serve(
'foo',
'1.0.1',
pubspec: {
'environment': {'sdk': '^2.18.0'},
},
);
final buffer = StringBuffer();
// Just changing the patch version should not trigger a pub get.
await runEmbeddingToBuffer(
['--verbose', 'run', 'myapp'],
buffer,
workingDirectory: d.path(appPath),
environment: {'_PUB_TEST_SDK_VERSION': '2.18.4'},
);
expect(
buffer.toString(),
allOf(contains('42'), isNot(contains('Resolving dependencies'))),
);
File(p.join(globalServer.cachingPath, '.cache', 'foo-versions.json'));
buffer.clear();
// Changing the minor version should.
await runEmbeddingToBuffer(
['--verbose', 'run', 'myapp'],
buffer,
workingDirectory: d.path(appPath),
environment: {'_PUB_TEST_SDK_VERSION': '2.19.3'},
);
expect(
buffer.toString(),
allOf(
contains('42'),
contains('Resolving dependencies'),
contains('1.0.1 available'),
),
);
});
test('`embedding run` does not have output when successful and no terminal',
() async {
await d.dir(appPath, [
d.pubspec({
'name': 'myapp',
'dependencies': {'foo': '^1.0.0'},
}),
d.dir('bin', [
d.file('myapp.dart', 'main() {print(42);}'),
]),
]).create();
final server = await servePackages();
server.serve('foo', '1.0.0');
final buffer = StringBuffer();
await runEmbeddingToBuffer(
['run', 'myapp'],
buffer,
workingDirectory: d.path(appPath),
environment: {EnvironmentKeys.forceTerminalOutput: '0'},
);
expect(
buffer.toString(),
allOf(
isNot(contains('Resolving dependencies...')),
contains('42'),
),
);
});
test('`embedding run` outputs info when successful and has a terminal',
() async {
await d.dir(appPath, [
d.pubspec({
'name': 'myapp',
'dependencies': {'foo': '^1.0.0'},
}),
d.dir('bin', [
d.file('myapp.dart', 'main() {print(42);}'),
]),
]).create();
final server = await servePackages();
server.serve('foo', '1.0.0');
final buffer = StringBuffer();
await runEmbeddingToBuffer(
['run', 'myapp'],
buffer,
workingDirectory: d.path(appPath),
environment: {EnvironmentKeys.forceTerminalOutput: '1'},
);
expect(
buffer.toString(),
allOf(
contains('Resolving dependencies'),
contains('42'),
),
);
});
test('"pkg" and "packages" will trigger a suggestion of "pub"', () async {
await servePackages();
await d.appDir().create();
for (final command in ['pkg', 'packages']) {
final buffer = StringBuffer();
await runEmbeddingToBuffer(
[command, 'get'],
buffer,
workingDirectory: d.path(appPath),
exitCode: USAGE,
);
expect(
buffer.toString(),
allOf(
contains('Did you mean one of these?'),
contains(' pub'),
),
);
}
});
testEnsurePubspecResolved();
}
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'Downloading packages finished (.*)$', multiLine: true),
r'Downloading packages 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',
)
.replaceAll(
RegExp(r'x-goog-hash(.*)$', multiLine: true),
r'x-goog-hash: $CHECKSUM_HEADER',
)
.replaceAll(
RegExp(
r'Computed checksum \d+ for foo 1.0.0 with expected CRC32C of '
r'\d+\.',
multiLine: true,
),
r'Computed checksum $CRC32C for foo 1.0.0 with expected CRC32C of '
r'$CRC32C.',
)
.replaceAll(
RegExp(r'sha256: "?[0-9a-f]{64}"?', multiLine: true),
r'sha256: $SHA256',
)
.replaceAll(
RegExp(r'"archive_sha256":"[0-9a-f]{64}"', multiLine: true),
r'"archive_sha256":"$SHA256"',
)
/// TODO(sigurdm): This hack suppresses differences in stack-traces
/// between dart 2.17 and 2.18. Remove when 2.18 is stable.
.replaceAllMapped(
RegExp(
r'(^(.*)pub/src/command.dart \$LINE:\$COL(.*)$)\n\1',
multiLine: true,
),
(match) => match[1]!,
);
}