Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flutter support among other things #20

Merged
merged 1 commit into from Dec 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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