@TestOn('vm')
library;
import 'dart:io' as io;
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:platform/platform.dart';
import 'package:test/test.dart';
void main() {
final bool usePowershellOnPosix = () {
return false;
}();
const FileSystem localFs = LocalFileSystem();
final flutterRoot = _FlutterRootUnderTest.findWithin(forcePowershell: usePowershellOnPosix);
late Directory tmpDir;
late _FlutterRootUnderTest testRoot;
late Map<String, String> environment;
void printIfNotEmpty(String prefix, String string) {
if (string.isNotEmpty) {
string.split(io.Platform.lineTerminator).forEach((String s) {
print('$prefix:>$s<');
});
}
}
io.ProcessResult run(String executable, List<String> args) {
final io.ProcessResult result = io.Process.runSync(
executable,
args,
environment: environment,
workingDirectory: testRoot.root.absolute.path,
includeParentEnvironment: false,
);
if (result.exitCode != 0) {
fail(
'Failed running "$executable $args" (exit code = ${result.exitCode}),'
'\nstdout: ${result.stdout}'
'\nstderr: ${result.stderr}',
);
}
printIfNotEmpty('stdout', (result.stdout as String).trim());
printIfNotEmpty('stderr', (result.stderr as String).trim());
return result;
}
setUpAll(() async {
if (usePowershellOnPosix) {
final io.ProcessResult result = io.Process.runSync('pwsh', <String>['--version']);
print(
'Using Powershell (${(result.stdout as String).trim()}) on POSIX for local debugging and testing',
);
}
});
void initGitRepoWithBlankInitialCommit() {
run('git', <String>['init', '--initial-branch', 'master']);
run('git', <String>['config', '--local', 'user.email', 'test@example.com']);
run('git', <String>['config', '--local', 'user.name', 'Test User']);
run('git', <String>['add', '.']);
run('git', <String>['commit', '--allow-empty', '-m', 'Initial commit']);
}
late int commitCount;
setUp(() async {
commitCount = 0;
tmpDir = localFs.systemTempDirectory.createTempSync('last_engine_commit_test.');
testRoot = _FlutterRootUnderTest.fromPath(
tmpDir.childDirectory('flutter').path,
forcePowershell: usePowershellOnPosix,
);
environment = <String, String>{};
if (const LocalPlatform().isWindows || usePowershellOnPosix) {
const powerShellVariables = <String>['SYSTEMROOT', 'PATH', 'PATHEXT'];
for (final key in powerShellVariables) {
final String? value = io.Platform.environment[key];
if (value != null) {
environment[key] = value;
}
}
}
flutterRoot.binInternalLastEngineCommit.copySyncRecursive(
testRoot.binInternalLastEngineCommit.path,
);
initGitRepoWithBlankInitialCommit();
});
tearDown(() {
tmpDir.deleteSync(recursive: true);
});
String getLastEngineCommit() {
final String executable;
final List<String> args;
if (const LocalPlatform().isWindows) {
executable = 'powershell';
args = <String>[testRoot.binInternalLastEngineCommit.path];
} else if (usePowershellOnPosix) {
executable = 'pwsh';
args = <String>[testRoot.binInternalLastEngineCommit.path];
} else {
executable = testRoot.binInternalLastEngineCommit.path;
args = <String>[];
}
return (run(executable, args).stdout as String).trim();
}
String getLastCommit() {
return (run('git', <String>['rev-parse', 'HEAD']).stdout as String).trim();
}
void writeCommit(Iterable<String> files) {
commitCount++;
for (final relativePath in files) {
localFs.file(localFs.path.join(testRoot.root.path, relativePath))
..createSync(recursive: true)
..writeAsStringSync('$commitCount');
}
run('git', <String>['add', '.']);
run('git', <String>['commit', '-m', 'Wrote ${files.length} files']);
}
test('returns the last engine commit', () {
writeCommit(<String>['bin/internal/release-candidate-branch.version']);
writeCommit(<String>['DEPS', 'engine/README.md']);
final String lastEngine = getLastEngineCommit();
expect(lastEngine, isNotEmpty);
writeCommit(<String>['CHANGELOG.md', 'dev/folder/called/engine/README.md']);
expect(getLastEngineCommit(), lastEngine);
});
test('considers DEPS an engine change', () {
writeCommit(<String>['bin/internal/release-candidate-branch.version']);
writeCommit(<String>['DEPS', 'engine/README.md']);
final String lastEngineA = getLastEngineCommit();
expect(lastEngineA, isNotEmpty);
writeCommit(<String>['DEPS']);
final String lastEngineB = getLastEngineCommit();
expect(lastEngineB, allOf(isNotEmpty, isNot(equals(lastEngineA))));
});
test('if there have been no engine changes, uses the first commit since the branch point', () {
final String initialStartingCommit = getLastCommit();
writeCommit(<String>['engine/README.md']);
final String engineCommitPreBranch = getLastCommit();
writeCommit(<String>['bin/internal/release-candidate-branch.version']);
final String initialBranchCommit = getLastCommit();
writeCommit(<String>['CHANGELOG.md']);
final String latestCommitIgnore = getLastCommit();
final String lastCommitToEngine = getLastEngineCommit();
expect(
lastCommitToEngine,
initialBranchCommit,
reason:
'The git history for this simulation looks like this:\n'
'master | $initialStartingCommit\n'
'master | $engineCommitPreBranch\n'
'release | $initialBranchCommit\n'
'release | $latestCommitIgnore\n'
'\n'
'We expected our script to select HEAD~2, $initialBranchCommit, but '
'instead it selected $lastCommitToEngine, which is incorrect. See '
'the table above to help debug.',
);
});
}
extension on File {
void copySyncRecursive(String newPath) {
fileSystem.directory(fileSystem.path.dirname(newPath)).createSync(recursive: true);
copySync(newPath);
}
}
final class _FlutterRootUnderTest {
factory _FlutterRootUnderTest.fromPath(
String path, {
FileSystem fileSystem = const LocalFileSystem(),
Platform platform = const LocalPlatform(),
bool forcePowershell = false,
}) {
final Directory root = fileSystem.directory(path);
return _FlutterRootUnderTest._(
root,
depsFile: root.childFile('DEPS'),
engineDirectory: root.childDirectory('engine'),
binInternalLastEngineCommit: root.childFile(
fileSystem.path.join(
'bin',
'internal',
'last_engine_commit.${platform.isWindows || forcePowershell ? 'ps1' : 'sh'}',
),
),
);
}
factory _FlutterRootUnderTest.findWithin({
String? path,
FileSystem fileSystem = const LocalFileSystem(),
bool forcePowershell = false,
}) {
path ??= fileSystem.currentDirectory.path;
Directory current = fileSystem.directory(path);
while (!current.childFile('DEPS').existsSync()) {
if (current.path == current.parent.path) {
throw ArgumentError.value(path, 'path', 'Could not resolve flutter root');
}
current = current.parent;
}
return _FlutterRootUnderTest.fromPath(current.path, forcePowershell: forcePowershell);
}
const _FlutterRootUnderTest._(
this.root, {
required this.binInternalLastEngineCommit,
required this.depsFile,
required this.engineDirectory,
});
final Directory root;
final File depsFile;
final Directory engineDirectory;
final File binInternalLastEngineCommit;
}