blob: 44b79275f6f282d8580a75d1def202353295ba8f [file] [log] [blame]
// 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.
@Timeout(Duration(minutes: 3))
library;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'test_utils.dart';
enum StreamType {
stdout,
stderr,
}
const processTimeout = Duration(minutes: 1);
void main() {
final testRunner = TestRunner();
setUpAll(testRunner.setUpAll);
tearDownAll(testRunner.tearDownAll);
Future<void> expectStdoutAndCleanExit(Process process,
{required String expectedStdout}) async {
final stdoutCompleter = _captureOutput(
process,
streamType: StreamType.stdout,
stopCaptureFuture: process.exitCode,
);
final stderrCompleter = _captureOutput(
process,
streamType: StreamType.stderr,
stopCaptureFuture: process.exitCode,
);
final exitCode = await _waitForExitOrTimeout(process);
final stderrLogs = await stderrCompleter.future;
final stdoutLogs = await stdoutCompleter.future;
expect(
exitCode,
equals(0),
// Include the stderr and stdout logs if the process does not terminate
// cleanly:
reason: 'stderr: $stderrLogs, stdout: $stdoutLogs',
);
expect(
stderrLogs,
isEmpty,
);
expect(
stdoutLogs,
contains(expectedStdout),
);
}
test('non-existent commands create errors', () async {
final process = await testRunner.runWebDev(['monkey']);
await expectLater(
process.stdout, emits('Could not find a command named "monkey".'));
await process.shouldExit(64);
});
test('passing extra args to build fails with bad usage', () async {
final process = await testRunner.runWebDev(['build', 'extra', 'args']);
await expectLater(process.stdout,
emits('Arguments were provided that are not supported: "extra args".'));
await process.shouldExit(64);
});
test('Errors with `build_runner` should not surface `build_daemon` issues',
() async {
await d.file('pubspec.yaml', _pubspecYaml).create();
await d
.file(
'pubspec.lock',
_pubspecLock(
runnerVersion: '1.2.8',
daemonVersion: '0.4.0',
))
.create();
await d.dir('.dart_tool', [d.file('package_config.json', '')]).create();
await d.file('.dart_tool/package_config.json', '').create();
final process =
await testRunner.runWebDev(['serve'], workingDirectory: d.sandbox);
final output = await process.stdout.rest.toList();
expect(output, isNot(contains(contains('`build_daemon`'))));
await process.shouldExit(78);
});
final invalidRanges = <String, List<String>>{
'build_runner': ['0.8.9', '3.0.0'],
'build_web_compilers': ['0.3.5', '5.0.0'],
'build_daemon': ['0.3.0', '5.0.0'],
};
for (final command in ['build', 'serve', 'daemon']) {
group('`$command` command', () {
group('missing dependency on', () {
test('`build_runner` should fail', () async {
await d.file('pubspec.yaml', _pubspecYaml).create();
await d
.file('pubspec.lock', _pubspecLock(runnerVersion: null))
.create();
await d
.dir('.dart_tool', [d.file('package_config.json', '')]).create();
await d.file('.dart_tool/package_config.json', '').create();
final process = await testRunner
.runWebDev([command], workingDirectory: d.sandbox);
await checkProcessStdout(process, ['webdev could not run']);
await process.shouldExit(78);
});
test('`build_web_compilers` should fail', () async {
await d.file('pubspec.yaml', _pubspecYaml).create();
await d
.file('pubspec.lock', _pubspecLock(webCompilersVersion: null))
.create();
await d
.dir('.dart_tool', [d.file('package_config.json', '')]).create();
await d.file('.dart_tool/package_config.json', '').create();
final process = await testRunner
.runWebDev(['serve'], workingDirectory: d.sandbox);
await checkProcessStdout(process, ['webdev could not run']);
await process.shouldExit(78);
});
test(
'`build_web_compilers` should be ignored with '
'--no-build-web-compilers', () async {
await d.file('pubspec.yaml', _pubspecYaml).create();
await d
.file('pubspec.lock', _pubspecLock(webCompilersVersion: null))
.create();
await d
.dir('.dart_tool', [d.file('package_config.json', '')]).create();
await d.file('.dart_tool/package_config.json', '').create();
// Required for webdev to not complain about nothing to serve.
await d.dir('web').create();
final process = await testRunner.runWebDev(
['serve', '--no-build-web-compilers'],
workingDirectory: d.sandbox);
await process.shouldExit();
});
});
for (final entry in invalidRanges.entries) {
group('with package `${entry.key}`', () {
for (final version in entry.value) {
test('at invalid version `$version` should fail', () async {
var buildRunnerVersion = _supportedBuildRunnerVersion;
var webCompilersVersion = _supportedWebCompilersVersion;
var buildDaemonVersion = _supportedBuildDaemonVersion;
switch (entry.key) {
case 'build_runner':
buildRunnerVersion = version;
break;
case 'build_web_compilers':
webCompilersVersion = version;
break;
case 'build_daemon':
buildDaemonVersion = version;
}
await d.file('pubspec.yaml', _pubspecYaml).create();
await d
.file(
'pubspec.lock',
_pubspecLock(
runnerVersion: buildRunnerVersion,
webCompilersVersion: webCompilersVersion,
daemonVersion: buildDaemonVersion))
.create();
await d.dir(
'.dart_tool', [d.file('package_config.json', '')]).create();
await d.file('.dart_tool/package_config.json', '').create();
final process = await testRunner
.runWebDev(['serve'], workingDirectory: d.sandbox);
await checkProcessStdout(process, ['webdev could not run']);
await process.shouldExit(78);
});
}
});
}
test('no pubspec.yaml', () async {
final process =
await testRunner.runWebDev(['serve'], workingDirectory: d.sandbox);
await checkProcessStdout(process, ['webdev could not run']);
await process.shouldExit(78);
});
test(
'pubspec.yaml, no pubspec.lock',
() async {
await d.file('pubspec.yaml', _pubspecYaml).create();
final process = await testRunner
.runWebDev(['serve'], workingDirectory: d.sandbox);
await checkProcessStdout(process, ['webdev could not run']);
await process.shouldExit(78);
},
);
test('should fail if there has been a dependency change', () async {
await d.file('pubspec.lock', _pubspecLock()).create();
await d.dir('.dart_tool', [d.file('package_config.json', '')]).create();
await d.file('.dart_tool/package_config.json', '').create();
// Ensure there is a noticeable delta in the creation times
await Future.delayed(const Duration(milliseconds: 1100));
await d.file('pubspec.yaml', '''
name: sample
dependencies:
args: ^1.0.0
''').create();
final process =
await testRunner.runWebDev(['serve'], workingDirectory: d.sandbox);
await checkProcessStdout(process, ['webdev could not run']);
await process.shouldExit(78);
});
if (command != 'daemon') {
test('failure with offline and unresolved dependencies', () async {
final createProcess = await Process.start(
'dart',
['create', '--no-pub', '--template', 'web', 'temp_app'],
workingDirectory: d.sandbox,
);
await expectStdoutAndCleanExit(createProcess,
expectedStdout: 'Created project temp_app');
final appPath = p.join(d.sandbox, 'temp_app');
final process = await testRunner
.runWebDev([command, '--offline'], workingDirectory: appPath);
await checkProcessStdout(process, ['webdev could not run']);
await process.shouldExit(78);
});
}
});
}
}
const _supportedBuildRunnerVersion = '2.4.0';
const _supportedWebCompilersVersion = '4.0.4';
const _supportedBuildDaemonVersion = '4.0.0';
String _pubspecYaml = '''
name: sample
''';
String _pubspecLock(
{String? runnerVersion = _supportedBuildRunnerVersion,
String? webCompilersVersion = _supportedWebCompilersVersion,
String? daemonVersion = _supportedBuildDaemonVersion,
List<String> extraPkgs = const []}) {
final buffer = StringBuffer('''
# Copy-pasted from a valid run
packages:
''');
if (runnerVersion != null) {
buffer.writeln('''
build_runner:
dependency: "direct dev"
description:
name: build_runner
url: "https://pub.dev"
source: hosted
version: "$runnerVersion"
''');
}
if (webCompilersVersion != null) {
buffer.writeln('''
build_web_compilers:
dependency: "direct dev"
description:
name: build_web_compilers
url: "https://pub.dev"
source: hosted
version: "$webCompilersVersion"
''');
}
if (daemonVersion != null) {
buffer.writeln('''
build_daemon:
dependency: "direct transitive"
description:
name: build_daemon
url: "https://pub.dev"
source: hosted
version: "$daemonVersion"
''');
}
for (final pkg in extraPkgs) {
buffer.writeln('''
$pkg:
dependency: "direct"
description:
name: $pkg
url: "https://pub.dev"
source: hosted
version: "1.0.0"
''');
}
return buffer.toString();
}
Future<int> _waitForExitOrTimeout(Process process) {
Timer(processTimeout, () {
process.kill(ProcessSignal.sigint);
});
return process.exitCode;
}
Completer<String> _captureOutput(
Process process, {
required StreamType streamType,
required Future stopCaptureFuture,
}) {
final stream =
streamType == StreamType.stdout ? process.stdout : process.stderr;
final completer = Completer<String>();
var output = '';
stream.transform(utf8.decoder).listen((line) {
output += line;
if (line.contains('[SEVERE]')) {
process.kill(ProcessSignal.sigint);
if (!completer.isCompleted) {
completer.complete(output);
}
}
});
unawaited(stopCaptureFuture.then((_) {
if (!completer.isCompleted) {
completer.complete(output);
}
}));
return completer;
}