| // 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.4.12'; |
| 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; |
| } |