diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 880dbc3..a944f94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,30 +36,48 @@ jobs: run: dart analyze --fatal-infos if: always() && steps.install.outcome == 'success' - # Run tests on a matrix consisting of two dimensions: - # 1. OS: ubuntu-latest, (macos-latest, windows-latest) - # 2. release channel: dev - test: - needs: analyze + test_dart: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - # Add macos-latest and/or windows-latest if relevant for this package. - os: [ubuntu-latest] - sdk: [2.12.0, dev] + os: [ubuntu-latest, macos-latest, windows-latest] + sdk: [2.12.3, dev] steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v1.0 - with: - sdk: ${{ matrix.sdk }} - - id: install - run: dart pub get - - name: make git happy - run: | - git config --global user.email "you@example.com" - git config --global user.name "Your Name" - - run: dart test -x presubmit-only - if: always() && steps.install.outcome == 'success' - - run: dart test --run-skipped -t presubmit-only - if: always() && steps.install.outcome == 'success' + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1.0 + with: + sdk: ${{ matrix.sdk }} + - id: install + run: dart pub get + - name: make git happy + run: | + git config --global user.email "you@example.com" + git config --global user.name "Your Name" + - run: dart test -x presubmit-only + if: always() && steps.install.outcome == 'success' + - run: dart test --run-skipped -t presubmit-only + if: always() && steps.install.outcome == 'success' + + test_flutter: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + flutter-channel: [beta, stable] + steps: + - uses: actions/checkout@v2 + - uses: subosito/flutter-action@v1.5.3 + with: + flutter-channel: ${{ matrix.flutter-channel }} + - id: install + run: dart pub get + - name: make git happy + run: | + git config --global user.email "you@example.com" + git config --global user.name "Your Name" + - run: flutter test -x presubmit-only + if: always() && steps.install.outcome == 'success' + - run: flutter test --run-skipped -t presubmit-only + if: always() && steps.install.outcome == 'success' diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fd230d..a3a60fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -## 2.0.1-dev +## 3.0.0 + +- `expectBuildClean` is now async. (Returns `Future` instead of `void`.) +- `defaultCommand` now uses `dart run build_runner...`. +- Now supports `flutter test`. +- Executed commands are now printed to the console. +- Require Dart `2.12.3`. ## 2.0.0 diff --git a/lib/build_verify.dart b/lib/build_verify.dart index 33eb47a..86abba6 100644 --- a/lib/build_verify.dart +++ b/lib/build_verify.dart @@ -2,8 +2,9 @@ import 'dart:io'; import 'src/impl.dart'; +/// The default value for `customCommand` in [expectBuildClean]. const defaultCommand = [ - pubPlaceHolder, + dartPlaceHolder, 'run', 'build_runner', 'build', @@ -18,7 +19,7 @@ const defaultCommand = [ /// If the first value is `PUB` or `DART` (case-sensitive), it will be replaced /// with the full, platform-specific path to the corresponding executable in the /// currently executing SDK. -void expectBuildClean({ +Future expectBuildClean({ String? packageRelativeDirectory, List customCommand = defaultCommand, }) => diff --git a/lib/src/impl.dart b/lib/src/impl.dart index bc373b9..d78627e 100644 --- a/lib/src/impl.dart +++ b/lib/src/impl.dart @@ -1,23 +1,25 @@ +import 'dart:convert'; import 'dart:io'; +import 'package:io/ansi.dart' as ansi; import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'utils.dart'; -const pubPlaceHolder = 'PUB'; +const dartPlaceHolder = 'DART'; -void expectBuildCleanImpl( +Future expectBuildCleanImpl( String workingDir, List command, { String? packageRelativeDirectory, -}) { +}) async { if (command.isEmpty) { throw ArgumentError.value(command, 'customCommand', 'Cannot be empty'); } final repoRoot = - _runProc('git', ['rev-parse', '--show-toplevel'], workingDir); + await _runProc('git', ['rev-parse', '--show-toplevel'], workingDir); final pkgDir = p.join(repoRoot, packageRelativeDirectory); if (!p.equals(pkgDir, workingDir)) { throw StateError('Expected the git root ($repoRoot) ' @@ -26,27 +28,25 @@ void expectBuildCleanImpl( // 1 - get a list of modified files files - should be empty expect( - _changedGeneratedFiles(workingDir), + await _changedGeneratedFiles(workingDir), isEmpty, reason: 'The working directory should be clean before running build.', ); var executable = command.first; - if (executable == pubPlaceHolder) { - executable = pubPath; - } else if (executable == 'DART') { + if (executable == 'DART') { executable = dartPath; } final arguments = command.skip(1).toList(); // 2 - run build - should be no output, since nothing should change - final result = _runProc(executable, arguments, workingDir); + final result = await _runProc(executable, arguments, workingDir); expectResultOutputSucceeds(result); // 3 - get a list of modified files after the build - should still be empty - expect(_changedGeneratedFiles(workingDir), isEmpty); + expect(await _changedGeneratedFiles(workingDir), isEmpty); } void expectResultOutputSucceeds(String result) { @@ -54,17 +54,44 @@ void expectResultOutputSucceeds(String result) { contains(RegExp(r'\[INFO\] Succeeded after .+ with \d+ outputs'))); } -String _changedGeneratedFiles(String workingDir) => +Future _changedGeneratedFiles(String workingDir) => _runProc('git', ['diff'], workingDir); -String _runProc(String proc, List args, String workingDir) { - final result = Process.runSync(proc, args, workingDirectory: workingDir); - - if (result.exitCode != 0) { - print(result.stdout); - print(result.stderr); - throw ProcessException(proc, args, 'Process failed', result.exitCode); +Future _runProc( + String proc, List args, String workingDir) async { + print('Running: `${ansi.cyan.wrap([ + proc, + ...args + ].join(' '))}` in `${ansi.cyan.wrap(workingDir)}`'); + + final process = await Process.start(proc, args, workingDirectory: workingDir); + + Future transform( + Stream> standardChannel, + List lines, + ) => + standardChannel + .transform(const SystemEncoding().decoder) + .transform(const LineSplitter()) + .forEach((element) { + print(element); + lines.add(element); + }); + + final stdoutContent = []; + final stderrContent = []; + + final result = await Future.wait([ + process.exitCode, + transform(process.stderr, stderrContent), + transform(process.stdout, stdoutContent), + ]); + + final exitCode = result.first as int; + + if (exitCode != 0) { + throw ProcessException(proc, args, 'Process failed', exitCode); } - return (result.stdout as String).trim(); + return stdoutContent.join('\n').trim(); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index ec5fc01..64e85d0 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -3,15 +3,17 @@ import 'dart:io'; import 'package:path/path.dart' as p; /// The path to the root directory of the SDK. -final String _sdkDir = (() { - // The Dart executable is in "/path/to/sdk/bin/dart", so two levels up is - // "/path/to/sdk". - final aboveExecutable = p.dirname(p.dirname(Platform.resolvedExecutable)); - assert(FileSystemEntity.isFileSync(p.join(aboveExecutable, 'version'))); - return aboveExecutable; -})(); +final String dartPath = (() { + final executableBaseName = p.basename(Platform.resolvedExecutable); -final String pubPath = - p.join(_sdkDir, 'bin', Platform.isWindows ? 'pub.bat' : 'pub'); + if (executableBaseName == 'flutter_tester') { + final flutterRoot = Platform.environment['FLUTTER_ROOT']!; -final String dartPath = p.join(_sdkDir, 'bin', 'dart'); + return p.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart'); + } else { + assert(executableBaseName == 'dart'); + // The Dart executable is in "/path/to/sdk/bin/dart", so two levels up is + // "/path/to/sdk". + return Platform.resolvedExecutable; + } +})(); diff --git a/lib/src/version.dart b/lib/src/version.dart index 3bb9e67..0ed1620 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '2.0.1-dev'; +const packageVersion = '3.0.0'; diff --git a/pubspec.yaml b/pubspec.yaml index 58f20c6..0df5d5e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,13 +2,14 @@ name: build_verify description: >- Test utility to ensure generated Dart code within a package is up-to-date when using package:build. -version: 2.0.1-dev +version: 3.0.0 repository: https://github.com/kevmoo/build_verify environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.12.3 <3.0.0' dependencies: + io: ^1.0.0 path: ^1.0.0 test: ^1.0.0 diff --git a/test/ensure_build_test.dart b/test/ensure_build_test.dart index 22e50bd..5dd7f4a 100644 --- a/test/ensure_build_test.dart +++ b/test/ensure_build_test.dart @@ -4,5 +4,9 @@ import 'package:build_verify/build_verify.dart'; import 'package:test/test.dart'; void main() { - test('ensure_build', expectBuildClean); + test( + 'ensure_build', + expectBuildClean, + timeout: const Timeout.factor(2), + ); } diff --git a/test/integration_test.dart b/test/integration_test.dart index 168362c..95146d3 100644 --- a/test/integration_test.dart +++ b/test/integration_test.dart @@ -35,13 +35,15 @@ const packageVersion = '1.2.3'; await gitDir.runCommand(['commit', '-am', 'test']); final process = await TestProcess.start( - pubPath, ['get', '--offline', '--no-precompile'], - workingDirectory: d.sandbox); + dartPath, + ['pub', 'get'], + workingDirectory: d.sandbox, + ); await process.shouldExit(0); }); test('success unit test', () async { - expectBuildCleanImpl(d.sandbox, defaultCommand); + await expectBuildCleanImpl(d.sandbox, defaultCommand); }); }