mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
355 lines
11 KiB
Dart
355 lines
11 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
@TestOn('vm')
|
|
library;
|
|
|
|
import 'dart:io' as io;
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:file/local.dart';
|
|
import 'package:file_testing/file_testing.dart';
|
|
import 'package:platform/platform.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// ✨ THINKING OF MOVING/REFACTORING THIS FILE? READ ME FIRST! ✨ //
|
|
// //
|
|
// There is a link to this file in //docs/tool/Engine-artfiacts.md //
|
|
// and it would be very kind of you to update the link, if needed. //
|
|
// //
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void main() {
|
|
const FileSystem localFs = LocalFileSystem();
|
|
final _FlutterRootUnderTest flutterRoot = _FlutterRootUnderTest.findWithin();
|
|
|
|
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) {
|
|
print('Running "$executable ${args.join(" ")}"');
|
|
final io.ProcessResult result = io.Process.runSync(
|
|
executable,
|
|
args,
|
|
environment: environment,
|
|
workingDirectory: testRoot.root.absolute.path,
|
|
includeParentEnvironment: false,
|
|
);
|
|
if (result.exitCode != 0) {
|
|
print('exitCode: ${result.exitCode}');
|
|
}
|
|
printIfNotEmpty('stdout', (result.stdout as String).trim());
|
|
printIfNotEmpty('stderr', (result.stderr as String).trim());
|
|
return result;
|
|
}
|
|
|
|
setUp(() async {
|
|
tmpDir = localFs.systemTempDirectory.createTempSync('update_engine_version_test.');
|
|
testRoot = _FlutterRootUnderTest.fromPath(tmpDir.childDirectory('flutter').path);
|
|
|
|
environment = <String, String>{};
|
|
environment.addAll(io.Platform.environment);
|
|
environment.remove('FLUTTER_PREBUILT_ENGINE_VERSION');
|
|
|
|
// Copy the update_engine_version script and create a rough directory structure.
|
|
flutterRoot.binInternalUpdateEngineVersion.copySyncRecursive(
|
|
testRoot.binInternalUpdateEngineVersion.path,
|
|
);
|
|
});
|
|
|
|
tearDown(() {
|
|
tmpDir.deleteSync(recursive: true);
|
|
});
|
|
|
|
io.ProcessResult runUpdateEngineVersion() {
|
|
final (String executable, List<String> args) =
|
|
const LocalPlatform().isWindows
|
|
? ('powershell', <String>[testRoot.binInternalUpdateEngineVersion.path])
|
|
: (testRoot.binInternalUpdateEngineVersion.path, <String>[]);
|
|
return run(executable, args);
|
|
}
|
|
|
|
void setupRepo({required String branch}) {
|
|
for (final File f in <File>[testRoot.deps, testRoot.engineSrcGn]) {
|
|
f.createSync(recursive: true);
|
|
}
|
|
|
|
run('git', <String>['init', '--initial-branch', 'master']);
|
|
run('git', <String>['add', '.']);
|
|
run('git', <String>['commit', '-m', 'Initial commit']);
|
|
if (branch != 'master') {
|
|
run('git', <String>['checkout', '-b', branch]);
|
|
}
|
|
}
|
|
|
|
const String engineVersionTrackedContents = 'already existing contents';
|
|
void setupTrackedEngineVersion() {
|
|
testRoot.binInternalEngineVersion.writeAsStringSync(engineVersionTrackedContents);
|
|
run('git', <String>['add', '-f', 'bin/internal/engine.version']);
|
|
run('git', <String>['commit', '-m', 'tracking engine.version']);
|
|
}
|
|
|
|
void setupRemote({required String remote}) {
|
|
run('git', <String>['remote', 'add', remote, testRoot.root.path]);
|
|
run('git', <String>['fetch', remote]);
|
|
}
|
|
|
|
group('if FLUTTER_PREBUILT_ENGINE_VERSION is set', () {
|
|
setUp(() {
|
|
environment['FLUTTER_PREBUILT_ENGINE_VERSION'] = '123abc';
|
|
setupRepo(branch: 'master');
|
|
});
|
|
|
|
test('writes it to engine.version with no git interaction', () async {
|
|
runUpdateEngineVersion();
|
|
|
|
expect(testRoot.binInternalEngineVersion, exists);
|
|
expect(
|
|
testRoot.binInternalEngineVersion.readAsStringSync(),
|
|
equalsIgnoringWhitespace('123abc'),
|
|
);
|
|
});
|
|
});
|
|
|
|
test('writes nothing, even if files are set, if we are on "stable"', () async {
|
|
setupRepo(branch: 'stable');
|
|
setupTrackedEngineVersion();
|
|
setupRemote(remote: 'upstream');
|
|
|
|
runUpdateEngineVersion();
|
|
|
|
expect(testRoot.binInternalEngineVersion, exists);
|
|
expect(
|
|
testRoot.binInternalEngineVersion.readAsStringSync(),
|
|
equalsIgnoringWhitespace(engineVersionTrackedContents),
|
|
);
|
|
});
|
|
|
|
test('writes nothing, even if files are set, if we are on "3.29.0"', () async {
|
|
setupRepo(branch: '3.29.0');
|
|
setupTrackedEngineVersion();
|
|
setupRemote(remote: 'upstream');
|
|
|
|
runUpdateEngineVersion();
|
|
|
|
expect(testRoot.binInternalEngineVersion, exists);
|
|
expect(
|
|
testRoot.binInternalEngineVersion.readAsStringSync(),
|
|
equalsIgnoringWhitespace(engineVersionTrackedContents),
|
|
);
|
|
});
|
|
|
|
test('writes nothing, even if files are set, if we are on "beta"', () async {
|
|
setupRepo(branch: 'beta');
|
|
setupTrackedEngineVersion();
|
|
setupRemote(remote: 'upstream');
|
|
|
|
runUpdateEngineVersion();
|
|
|
|
expect(testRoot.binInternalEngineVersion, exists);
|
|
expect(
|
|
testRoot.binInternalEngineVersion.readAsStringSync(),
|
|
equalsIgnoringWhitespace(engineVersionTrackedContents),
|
|
);
|
|
});
|
|
|
|
group('if DEPS and engine/src/.gn are present, engine.version is derived from', () {
|
|
setUp(() async {
|
|
setupRepo(branch: 'master');
|
|
});
|
|
|
|
test('merge-base HEAD upstream/master on non-LUCI when upstream is set', () async {
|
|
setupRemote(remote: 'upstream');
|
|
|
|
final io.ProcessResult mergeBaseHeadUpstream = run('git', <String>[
|
|
'merge-base',
|
|
'HEAD',
|
|
'upstream/master',
|
|
]);
|
|
runUpdateEngineVersion();
|
|
|
|
expect(testRoot.binInternalEngineVersion, exists);
|
|
expect(
|
|
testRoot.binInternalEngineVersion.readAsStringSync(),
|
|
equalsIgnoringWhitespace(mergeBaseHeadUpstream.stdout as String),
|
|
);
|
|
});
|
|
|
|
test('merge-base HEAD origin/master on non-LUCI when upstream is not set', () async {
|
|
setupRemote(remote: 'origin');
|
|
|
|
final io.ProcessResult mergeBaseHeadOrigin = run('git', <String>[
|
|
'merge-base',
|
|
'HEAD',
|
|
'origin/master',
|
|
]);
|
|
runUpdateEngineVersion();
|
|
|
|
expect(testRoot.binInternalEngineVersion, exists);
|
|
expect(
|
|
testRoot.binInternalEngineVersion.readAsStringSync(),
|
|
equalsIgnoringWhitespace(mergeBaseHeadOrigin.stdout as String),
|
|
);
|
|
});
|
|
|
|
test('rev-parse HEAD when running on LUCI', () async {
|
|
environment['LUCI_CONTEXT'] = '_NON_NULL_AND_NON_EMPTY_STRING';
|
|
runUpdateEngineVersion();
|
|
|
|
final io.ProcessResult revParseHead = run('git', <String>['rev-parse', 'HEAD']);
|
|
expect(testRoot.binInternalEngineVersion, exists);
|
|
expect(
|
|
testRoot.binInternalEngineVersion.readAsStringSync(),
|
|
equalsIgnoringWhitespace(revParseHead.stdout as String),
|
|
);
|
|
});
|
|
});
|
|
|
|
group('if DEPS or engine/src/.gn are omitted', () {
|
|
setUp(() {
|
|
for (final File f in <File>[testRoot.deps, testRoot.engineSrcGn]) {
|
|
f.createSync(recursive: true);
|
|
}
|
|
setupRepo(branch: 'master');
|
|
setupRemote(remote: 'origin');
|
|
});
|
|
|
|
test('[DEPS] engine.version is blank', () async {
|
|
testRoot.deps.deleteSync();
|
|
|
|
runUpdateEngineVersion();
|
|
|
|
expect(testRoot.binInternalEngineVersion, exists);
|
|
expect(testRoot.binInternalEngineVersion.readAsStringSync(), equalsIgnoringWhitespace(''));
|
|
});
|
|
|
|
test('[engine/src/.gn] engine.version is blank', () async {
|
|
testRoot.engineSrcGn.deleteSync();
|
|
|
|
runUpdateEngineVersion();
|
|
|
|
expect(testRoot.binInternalEngineVersion, exists);
|
|
expect(testRoot.binInternalEngineVersion.readAsStringSync(), equalsIgnoringWhitespace(''));
|
|
});
|
|
});
|
|
}
|
|
|
|
/// A FrUT, or "Flutter Root"-Under Test (parallel to a SUT, System Under Test).
|
|
///
|
|
/// For the intent of this test case, the "Flutter Root" is a directory
|
|
/// structure with the following elements:
|
|
///
|
|
/// ```txt
|
|
/// ├── bin
|
|
/// │ ├── internal
|
|
/// │ │ ├── engine.version
|
|
/// │ │ ├── engine.realm
|
|
/// │ │ └── update_engine_version.{sh|ps1}
|
|
/// │ └── engine
|
|
/// │ └── src
|
|
/// │ └── .gn
|
|
/// └── DEPS
|
|
/// ```
|
|
final class _FlutterRootUnderTest {
|
|
/// Creates a root-under test using [path] as the root directory.
|
|
///
|
|
/// It is assumed the files already exist or will be created if needed.
|
|
factory _FlutterRootUnderTest.fromPath(
|
|
String path, {
|
|
FileSystem fileSystem = const LocalFileSystem(),
|
|
Platform platform = const LocalPlatform(),
|
|
}) {
|
|
final Directory root = fileSystem.directory(path);
|
|
return _FlutterRootUnderTest._(
|
|
root,
|
|
deps: root.childFile('DEPS'),
|
|
engineSrcGn: root.childFile(fileSystem.path.join('engine', 'src', '.gn')),
|
|
binInternalEngineVersion: root.childFile(
|
|
fileSystem.path.join('bin', 'internal', 'engine.version'),
|
|
),
|
|
binInternalEngineRealm: root.childFile(
|
|
fileSystem.path.join('bin', 'internal', 'engine.realm'),
|
|
),
|
|
binInternalUpdateEngineVersion: root.childFile(
|
|
fileSystem.path.join(
|
|
'bin',
|
|
'internal',
|
|
'update_engine_version.${platform.isWindows ? 'ps1' : 'sh'}',
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
factory _FlutterRootUnderTest.findWithin([
|
|
String? path,
|
|
FileSystem fileSystem = const LocalFileSystem(),
|
|
]) {
|
|
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);
|
|
}
|
|
|
|
const _FlutterRootUnderTest._(
|
|
this.root, {
|
|
required this.deps,
|
|
required this.engineSrcGn,
|
|
required this.binInternalEngineVersion,
|
|
required this.binInternalEngineRealm,
|
|
required this.binInternalUpdateEngineVersion,
|
|
});
|
|
|
|
final Directory root;
|
|
|
|
/// `DEPS`.
|
|
///
|
|
/// The presenence of this file is an indicator we are in a fused (mono) repo.
|
|
final File deps;
|
|
|
|
/// `engine/src/.gn`.
|
|
///
|
|
/// The presenence of this file is an indicator we are in a fused (mono) repo.
|
|
final File engineSrcGn;
|
|
|
|
/// `bin/internal/engine.version`.
|
|
///
|
|
/// This file contains a SHA of which engine binaries to download.
|
|
final File binInternalEngineVersion;
|
|
|
|
/// `bin/internal/engine.realm`.
|
|
///
|
|
/// It is a mystery what this file contains, but it's set by `FLUTTER_REALM`.
|
|
final File binInternalEngineRealm;
|
|
|
|
/// `bin/internal/update_engine_version.{sh|ps1}`.
|
|
///
|
|
/// This file contains a shell script that conditionally writes, on execution:
|
|
/// - [binInternalEngineVersion]
|
|
/// - [binInternalEngineRealm]
|
|
final File binInternalUpdateEngineVersion;
|
|
}
|
|
|
|
extension on File {
|
|
void copySyncRecursive(String newPath) {
|
|
fileSystem.directory(fileSystem.path.dirname(newPath)).createSync(recursive: true);
|
|
copySync(newPath);
|
|
}
|
|
}
|