| // 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 'dart:io'; |
| |
| import 'package:args/command_runner.dart'; |
| import 'package:async/async.dart'; |
| import 'package:build_runner_core/build_runner_core.dart'; |
| import 'package:io/io.dart'; |
| import 'package:path/path.dart' as p; |
| import 'package:pub_semver/pub_semver.dart'; |
| import 'package:pubspec_parse/pubspec_parse.dart'; |
| |
| import '../generate/build.dart'; |
| import 'base_command.dart'; |
| import 'options.dart'; |
| |
| /// A command that does a single build and then runs tests using the compiled |
| /// assets. |
| class TestCommand extends BuildRunnerCommand { |
| TestCommand(PackageGraph packageGraph) |
| : super( |
| // Use symlinks by default, if package:test supports it. |
| symlinksDefault: |
| _packageTestSupportsSymlinks(packageGraph) && !Platform.isWindows, |
| ); |
| |
| @override |
| String get invocation => |
| '${super.invocation.replaceFirst('[arguments]', '[build-arguments]')} ' |
| '[-- [test-arguments]]'; |
| |
| @override |
| String get name => 'test'; |
| |
| @override |
| String get description => |
| 'Performs a single build of the test directory only and then runs tests ' |
| 'using the compiled assets.'; |
| |
| @override |
| SharedOptions readOptions() { |
| // This command doesn't allow specifying directories to build, instead it |
| // always builds the `test` directory. |
| // |
| // Here we validate that [argResults.rest] is exactly equal to all the |
| // arguments after the `--`. |
| if (argResults.rest.isNotEmpty) { |
| void throwUsageException() { |
| throw UsageException( |
| 'The `test` command does not support positional args before the, ' |
| '`--` separator, which should separate build args from test args.', |
| usage); |
| } |
| |
| var separatorPos = argResults.arguments.indexOf('--'); |
| if (separatorPos < 0) { |
| throwUsageException(); |
| } |
| var expectedRest = argResults.arguments.skip(separatorPos + 1).toList(); |
| if (argResults.rest.length != expectedRest.length) { |
| throwUsageException(); |
| } |
| for (var i = 0; i < argResults.rest.length; i++) { |
| if (expectedRest[i] != argResults.rest[i]) { |
| throwUsageException(); |
| } |
| } |
| } |
| |
| return SharedOptions.fromParsedArgs( |
| argResults, ['test'], packageGraph.root.name, this); |
| } |
| |
| @override |
| Future<int> run() async { |
| SharedOptions options; |
| // We always run our tests in a temp dir. |
| var tempPath = Directory.systemTemp |
| .createTempSync('build_runner_test') |
| .absolute |
| .uri |
| .toFilePath(); |
| try { |
| _ensureBuildTestDependency(packageGraph); |
| options = readOptions(); |
| var buildDirs = (options.buildDirs ?? <BuildDirectory>{}) |
| // Build test by default. |
| ..add(BuildDirectory('test', |
| outputLocation: OutputLocation(tempPath, |
| useSymlinks: options.outputSymlinksOnly, hoist: false))); |
| |
| var result = await build( |
| builderApplications, |
| deleteFilesByDefault: options.deleteFilesByDefault, |
| enableLowResourcesMode: options.enableLowResourcesMode, |
| configKey: options.configKey, |
| buildDirs: buildDirs, |
| outputSymlinksOnly: options.outputSymlinksOnly, |
| packageGraph: packageGraph, |
| trackPerformance: options.trackPerformance, |
| skipBuildScriptCheck: options.skipBuildScriptCheck, |
| verbose: options.verbose, |
| builderConfigOverrides: options.builderConfigOverrides, |
| isReleaseBuild: options.isReleaseBuild, |
| logPerformanceDir: options.logPerformanceDir, |
| buildFilters: options.buildFilters, |
| ); |
| |
| if (result.status == BuildStatus.failure) { |
| stdout.writeln('Skipping tests due to build failure'); |
| return result.failureType.exitCode; |
| } |
| |
| return await _runTests(tempPath); |
| } on _BuildTestDependencyError catch (e) { |
| stdout.writeln(e); |
| return ExitCode.config.code; |
| } finally { |
| // Clean up the output dir. |
| await Directory(tempPath).delete(recursive: true); |
| } |
| } |
| |
| /// Runs tests using [precompiledPath] as the precompiled test directory. |
| Future<int> _runTests(String precompiledPath) async { |
| stdout.writeln('Running tests...\n'); |
| var extraTestArgs = argResults.rest; |
| var testProcess = await Process.start( |
| pubBinary, |
| [ |
| 'run', |
| 'test', |
| '--precompiled', |
| precompiledPath, |
| ...extraTestArgs, |
| ], |
| mode: ProcessStartMode.inheritStdio); |
| _ensureProcessExit(testProcess); |
| return testProcess.exitCode; |
| } |
| } |
| |
| bool _packageTestSupportsSymlinks(PackageGraph packageGraph) { |
| var testPackage = packageGraph['test']; |
| if (testPackage == null) return false; |
| var pubspecPath = p.join(testPackage.path, 'pubspec.yaml'); |
| var pubspec = Pubspec.parse(File(pubspecPath).readAsStringSync()); |
| if (pubspec.version == null) return false; |
| return pubspec.version >= Version(1, 3, 0); |
| } |
| |
| void _ensureBuildTestDependency(PackageGraph packageGraph) { |
| if (!packageGraph.allPackages.containsKey('build_test')) { |
| throw _BuildTestDependencyError(); |
| } |
| } |
| |
| void _ensureProcessExit(Process process) { |
| var signalsSub = _exitProcessSignals.listen((signal) async { |
| stdout.writeln('waiting for subprocess to exit...'); |
| }); |
| process.exitCode.then((_) { |
| signalsSub?.cancel(); |
| signalsSub = null; |
| }); |
| } |
| |
| Stream<ProcessSignal> get _exitProcessSignals => Platform.isWindows |
| ? ProcessSignal.sigint.watch() |
| : StreamGroup.merge( |
| [ProcessSignal.sigterm.watch(), ProcessSignal.sigint.watch()]); |
| |
| class _BuildTestDependencyError extends StateError { |
| _BuildTestDependencyError() : super(''' |
| Missing dev dependency on package:build_test, which is required to run tests. |
| |
| Please update your dev_dependencies section of your pubspec.yaml: |
| |
| dev_dependencies: |
| build_runner: any |
| build_test: any |
| # If you need to run web tests, you will also need this dependency. |
| build_web_compilers: any |
| '''); |
| } |