Skip to content

Commit

Permalink
Flutter support among other things (#20)
Browse files Browse the repository at this point in the history
Prepare to publish v3

Fixes #12
  • Loading branch information
kevmoo committed Dec 6, 2021
1 parent 612d08f commit 38e4ffe
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 70 deletions.
62 changes: 40 additions & 22 deletions .github/workflows/ci.yml
Expand Up @@ -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'
8 changes: 7 additions & 1 deletion CHANGELOG.md
@@ -1,4 +1,10 @@
## 2.0.1-dev
## 3.0.0

- `expectBuildClean` is now async. (Returns `Future<void>` 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

Expand Down
1 change: 1 addition & 0 deletions analysis_options.yaml
Expand Up @@ -37,6 +37,7 @@ linter:
- prefer_interpolation_to_compose_strings
- prefer_relative_imports
- prefer_single_quotes
- require_trailing_commas
- sort_pub_dependencies
- test_types_in_equals
- throw_in_finally
Expand Down
5 changes: 3 additions & 2 deletions lib/build_verify.dart
Expand Up @@ -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',
Expand All @@ -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<void> expectBuildClean({
String? packageRelativeDirectory,
List<String> customCommand = defaultCommand,
}) =>
Expand Down
79 changes: 57 additions & 22 deletions lib/src/impl.dart
@@ -1,70 +1,105 @@
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<void> expectBuildCleanImpl(
String workingDir,
List<String> 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) '
'to match the current directory ($workingDir).');
throw StateError(
'Expected the git root ($repoRoot) '
'to match the current directory ($workingDir).',
);
}

// 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) {
expect(result,
contains(RegExp(r'\[INFO\] Succeeded after .+ with \d+ outputs')));
expect(
result,
contains(RegExp(r'\[INFO\] Succeeded after .+ with \d+ outputs')),
);
}

String _changedGeneratedFiles(String workingDir) =>
Future<String> _changedGeneratedFiles(String workingDir) =>
_runProc('git', ['diff'], workingDir);

String _runProc(String proc, List<String> args, String workingDir) {
final result = Process.runSync(proc, args, workingDirectory: workingDir);
Future<String> _runProc(
String proc,
List<String> args,
String workingDir,
) async {
print(
'Running: `${ansi.cyan.wrap(
[proc, ...args].join(' '),
)}` in `${ansi.cyan.wrap(workingDir)}`',
);

if (result.exitCode != 0) {
print(result.stdout);
print(result.stderr);
throw ProcessException(proc, args, 'Process failed', result.exitCode);
final process = await Process.start(proc, args, workingDirectory: workingDir);

Future<void> transform(
Stream<List<int>> standardChannel,
List<String> lines,
) =>
standardChannel
.transform(const SystemEncoding().decoder)
.transform(const LineSplitter())
.forEach((element) {
print(element);
lines.add(element);
});

final stdoutContent = <String>[];
final stderrContent = <String>[];

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();
}
24 changes: 14 additions & 10 deletions lib/src/utils.dart
Expand Up @@ -3,15 +3,19 @@ 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' ||
executableBaseName == 'flutter_tester.exe') {
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' || executableBaseName == 'dart.exe',
'Was not expected "$executableBaseName".',
);
return Platform.resolvedExecutable;
}
})();
2 changes: 1 addition & 1 deletion lib/src/version.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pubspec.yaml
Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion test/ensure_build_test.dart
Expand Up @@ -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),
);
}
22 changes: 15 additions & 7 deletions test/integration_test.dart
Expand Up @@ -10,7 +10,9 @@ import 'package:test_process/test_process.dart';

void main() {
setUp(() async {
await d.file('pubspec.yaml', '''
await d.file(
'pubspec.yaml',
'''
name: example
version: 1.2.3
environment:
Expand All @@ -19,14 +21,18 @@ environment:
dev_dependencies:
build_runner: ^2.0.0
build_version: ^2.0.0
''').create();
''',
).create();

await d.dir('lib', [
d.dir('src', [
d.file('version.dart', r'''
d.file(
'version.dart',
r'''
// Generated code. Do not modify.
const packageVersion = '1.2.3';
''')
''',
)
])
]).create();

Expand All @@ -35,13 +41,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);
});
}
6 changes: 4 additions & 2 deletions test/success_parse_test.dart
Expand Up @@ -18,8 +18,10 @@ void main() {
test('failing output with ${_humanReadable(duration)}', () {
final output = '[INFO] Failed after ${_humanReadable(duration)}';

expect(() => expectResultOutputSucceeds(output),
throwsA(const TypeMatcher<TestFailure>()));
expect(
() => expectResultOutputSucceeds(output),
throwsA(const TypeMatcher<TestFailure>()),
);
});
}
}
Expand Down

0 comments on commit 38e4ffe

Please sign in to comment.