Formalize update_engine_version.{sh|ps1}
. (#162118)
Towards https://github.com/flutter/flutter/issues/162201. **NOTE**: This renames the environment variable to `FLUTTER_PREBUILT_ENGINE_VERSION`. --- We occasionally break ourselves, our users, and the Dart up (or is it down? side-ways) stream repos (i.e. HHH) when we change how the undocumented [`update_engine_version.sh`](https://github.com/flutter/flutter/blob/master/bin/internal/update_engine_version.sh) script, and it's Windows counterpart [`update_engine_version.ps1`](https://github.com/flutter/flutter/blob/master/bin/internal/update_engine_version.ps1) work, but have no way of knowing until N days/weeks later when someone tells us. For example, <https://flutter-review.googlesource.com/c/recipes/+/62400>. This is my attempt to encode "this is what you can guarantee by calling this script". It _still_ will be an internal only API that we might rev at any time, but at least we: 1. Can tell the Dart team "this is tested and how it works" 2. If we want to change it, the tests will keep us from changing it without informing folks it changed These tests should (in theory) cover both Linux/MacOS and Windows. /cc @a-siva
This commit is contained in:
parent
586a59211d
commit
a3f0704f25
2
.github/workflows/files-changed.yml
vendored
2
.github/workflows/files-changed.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
run: |
|
||||
git fetch --no-tags --prune --depth=1 origin ${{ github.event.pull_request.base.sha }}
|
||||
git fetch --no-tags --prune --depth=1 origin master
|
||||
echo "FLUTTER_ENGINE_VERSION=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_ENV"
|
||||
echo "FLUTTER_PREBUILT_ENGINE_VERSION=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Initialize Dart SDK
|
||||
# This downloads the version of the Dart SDK for the current platform.
|
||||
|
@ -2,6 +2,9 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# Want to test this script?
|
||||
# $ cd dev/tools
|
||||
# $ dart test test/update_engine_version_test.dart
|
||||
|
||||
# ---------------------------------- NOTE ---------------------------------- #
|
||||
#
|
||||
@ -11,13 +14,28 @@
|
||||
#
|
||||
# -------------------------------------------------------------------------- #
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$progName = Split-Path -parent $MyInvocation.MyCommand.Definition
|
||||
$flutterRoot = (Get-Item $progName).parent.parent.FullName
|
||||
|
||||
# Allow overriding the intended engine version via FLUTTER_PREBUILT_ENGINE_VERSION.
|
||||
#
|
||||
# This is for systems, such as Github Actions, where we know ahead of time the
|
||||
# base-ref we want to use (to download the engine binaries and avoid trying
|
||||
# to compute one below), or for the Dart HH bot, which wants to try the current
|
||||
# Flutter framework/engine with a different Dart SDK.
|
||||
#
|
||||
# This environment variable is EXPERIMENTAL. If you are not on the Flutter infra
|
||||
# or Dart infra teams, this code path might be removed at anytime and cease
|
||||
# functioning. Please file an issue if you have workflow needs.
|
||||
if (![string]::IsNullOrEmpty($env:FLUTTER_PREBUILT_ENGINE_VERSION)) {
|
||||
$engineVersion = $env:FLUTTER_PREBUILT_ENGINE_VERSION
|
||||
Write-Error "[Unstable] Override: Setting engine SHA to $engineVersion"
|
||||
}
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Test for fusion repository
|
||||
if ((Test-Path "$flutterRoot\DEPS" -PathType Leaf) -and (Test-Path "$flutterRoot\engine\src\.gn" -PathType Leaf)) {
|
||||
if ([string]::IsNullOrEmpty($engineVersion) -and (Test-Path "$flutterRoot\DEPS" -PathType Leaf) -and (Test-Path "$flutterRoot\engine\src\.gn" -PathType Leaf)) {
|
||||
# Calculate the engine hash from tracked git files.
|
||||
$branch = (git -C "$flutterRoot" rev-parse --abbrev-ref HEAD)
|
||||
if ($null -eq $Env:LUCI_CONTEXT) {
|
||||
@ -34,15 +52,15 @@ if ((Test-Path "$flutterRoot\DEPS" -PathType Leaf) -and (Test-Path "$flutterRoot
|
||||
else {
|
||||
$engineVersion = (git -C "$flutterRoot" rev-parse HEAD)
|
||||
}
|
||||
}
|
||||
|
||||
if (($branch -ne "stable" -and $branch -ne "beta")) {
|
||||
# Write the engine version out so downstream tools know what to look for.
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText("$flutterRoot\bin\internal\engine.version", $engineVersion, $utf8NoBom)
|
||||
if (($branch -ne "stable" -and $branch -ne "beta")) {
|
||||
# Write the engine version out so downstream tools know what to look for.
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText("$flutterRoot\bin\internal\engine.version", $engineVersion, $utf8NoBom)
|
||||
|
||||
# The realm on CI is passed in.
|
||||
if ($Env:FLUTTER_REALM) {
|
||||
[System.IO.File]::WriteAllText("$flutterRoot\bin\internal\engine.realm", $Env:FLUTTER_REALM, $utf8NoBom)
|
||||
}
|
||||
# The realm on CI is passed in.
|
||||
if ($Env:FLUTTER_REALM) {
|
||||
[System.IO.File]::WriteAllText("$flutterRoot\bin\internal\engine.realm", $Env:FLUTTER_REALM, $utf8NoBom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# Want to test this script?
|
||||
# $ cd dev/tools
|
||||
# $ dart test test/update_engine_version_test.dart
|
||||
|
||||
# ---------------------------------- NOTE ---------------------------------- #
|
||||
#
|
||||
@ -14,17 +17,18 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Allow overriding the intended engine version via FLUTTER_ENGINE_VERSION.
|
||||
# Allow overriding the intended engine version via FLUTTER_PREBUILT_ENGINE_VERSION.
|
||||
#
|
||||
# This is for systems, such as Github Actions, where we know ahead of time the
|
||||
# base-ref we want to use (to download the engine binaries and avoid trying
|
||||
# to compute one below).
|
||||
# to compute one below), or for the Dart HH bot, which wants to try the current
|
||||
# Flutter framework/engine with a different Dart SDK.
|
||||
#
|
||||
# This environment variable is EXPERIMENTAL. If you are not on the Flutter infra
|
||||
# team, this code path might be removed at anytime and cease functioning. Please
|
||||
# file an issue if you have workflow needs.
|
||||
if [ -n "${FLUTTER_ENGINE_VERSION}" ]; then
|
||||
ENGINE_VERSION="${FLUTTER_ENGINE_VERSION}"
|
||||
# or Dart infra teams, this code path might be removed at anytime and cease
|
||||
# functioning. Please file an issue if you have workflow needs.
|
||||
if [ -n "${FLUTTER_PREBUILT_ENGINE_VERSION}" ]; then
|
||||
ENGINE_VERSION="${FLUTTER_PREBUILT_ENGINE_VERSION}"
|
||||
echo "[Unstable] Override: Setting engine SHA to $ENGINE_VERSION" 1>&2
|
||||
fi
|
||||
|
||||
|
@ -33,6 +33,8 @@ dev_dependencies:
|
||||
test: 1.25.14
|
||||
test_api: 0.7.4
|
||||
|
||||
file_testing: 3.0.2
|
||||
|
||||
_fe_analyzer_shared: 76.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
analyzer: 6.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
@ -64,4 +66,4 @@ dev_dependencies:
|
||||
web_socket_channel: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
# PUBSPEC CHECKSUM: 1d50
|
||||
# PUBSPEC CHECKSUM: 7e9e
|
||||
|
307
dev/tools/test/update_engine_version_test.dart
Normal file
307
dev/tools/test/update_engine_version_test.dart
Normal file
@ -0,0 +1,307 @@
|
||||
// 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 'package:file/file.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:file_testing/file_testing.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process_runner/process_runner.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
const FileSystem localFs = LocalFileSystem();
|
||||
final _FlutterRootUnderTest flutterRoot = _FlutterRootUnderTest.findWithin();
|
||||
|
||||
late Directory tmpDir;
|
||||
late _FlutterRootUnderTest testRoot;
|
||||
late Map<String, String> environment;
|
||||
late ProcessRunner processRunner;
|
||||
|
||||
setUp(() async {
|
||||
tmpDir = localFs.systemTempDirectory.createTempSync('update_engine_version_test.');
|
||||
testRoot = _FlutterRootUnderTest.fromPath(tmpDir.childDirectory('flutter').path);
|
||||
|
||||
environment = <String, String>{};
|
||||
processRunner = ProcessRunner(
|
||||
defaultWorkingDirectory: testRoot.root,
|
||||
environment: environment,
|
||||
printOutputDefault: true,
|
||||
);
|
||||
|
||||
// Copy the update_engine_version script and create a rough directory structure.
|
||||
flutterRoot.binInternalUpdateEngineVersion.copySyncRecursive(
|
||||
testRoot.binInternalUpdateEngineVersion.path,
|
||||
);
|
||||
|
||||
// On some systems, copying the file means losing the executable bit.
|
||||
if (const LocalPlatform().isWindows) {
|
||||
await processRunner.runProcess(<String>[
|
||||
'attrib',
|
||||
'+x',
|
||||
testRoot.binInternalUpdateEngineVersion.path,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tmpDir.deleteSync(recursive: true);
|
||||
});
|
||||
|
||||
Future<void> runUpdateEngineVersion() async {
|
||||
if (const LocalPlatform().isWindows) {
|
||||
await processRunner.runProcess(<String>[
|
||||
'powershell',
|
||||
testRoot.binInternalUpdateEngineVersion.path,
|
||||
]);
|
||||
} else {
|
||||
await processRunner.runProcess(<String>[testRoot.binInternalUpdateEngineVersion.path]);
|
||||
}
|
||||
}
|
||||
|
||||
group('if FLUTTER_PREBUILT_ENGINE_VERSION is set', () {
|
||||
setUp(() {
|
||||
environment['FLUTTER_PREBUILT_ENGINE_VERSION'] = '123abc';
|
||||
});
|
||||
|
||||
test('writes it to engine.version with no git interaction', () async {
|
||||
await runUpdateEngineVersion();
|
||||
|
||||
expect(testRoot.binInternalEngineVersion, exists);
|
||||
expect(
|
||||
testRoot.binInternalEngineVersion.readAsStringSync(),
|
||||
equalsIgnoringWhitespace('123abc'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Future<void> setupRepo({required String branch}) async {
|
||||
for (final File f in <File>[testRoot.deps, testRoot.engineSrcGn]) {
|
||||
f.createSync(recursive: true);
|
||||
}
|
||||
|
||||
await processRunner.runProcess(<String>['git', 'init', '--initial-branch', 'master']);
|
||||
await processRunner.runProcess(<String>['git', 'add', '.']);
|
||||
await processRunner.runProcess(<String>['git', 'commit', '-m', 'Initial commit']);
|
||||
if (branch != 'master') {
|
||||
await processRunner.runProcess(<String>['git', 'checkout', '-b', branch]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setupRemote({required String remote}) async {
|
||||
await processRunner.runProcess(<String>['git', 'remote', 'add', remote, testRoot.root.path]);
|
||||
await processRunner.runProcess(<String>['git', 'fetch', remote]);
|
||||
}
|
||||
|
||||
test('writes nothing, even if files are set, if we are on "stable"', () async {
|
||||
await setupRepo(branch: 'stable');
|
||||
await setupRemote(remote: 'upstream');
|
||||
|
||||
await runUpdateEngineVersion();
|
||||
|
||||
expect(testRoot.binInternalEngineVersion, isNot(exists));
|
||||
});
|
||||
|
||||
test('writes nothing, even if files are set, if we are on "beta"', () async {
|
||||
await setupRepo(branch: 'beta');
|
||||
await setupRemote(remote: 'upstream');
|
||||
|
||||
await runUpdateEngineVersion();
|
||||
|
||||
expect(testRoot.binInternalEngineVersion, isNot(exists));
|
||||
});
|
||||
|
||||
group('if DEPS and engine/src/.gn are present, engine.version is derived from', () {
|
||||
setUp(() async {
|
||||
await setupRepo(branch: 'master');
|
||||
});
|
||||
|
||||
test('merge-base HEAD upstream/master on non-LUCI when upstream is set', () async {
|
||||
await setupRemote(remote: 'upstream');
|
||||
|
||||
final ProcessRunnerResult mergeBaseHeadUpstream = await processRunner.runProcess(<String>[
|
||||
'git',
|
||||
'merge-base',
|
||||
'HEAD',
|
||||
'upstream/master',
|
||||
]);
|
||||
await runUpdateEngineVersion();
|
||||
|
||||
expect(testRoot.binInternalEngineVersion, exists);
|
||||
expect(
|
||||
testRoot.binInternalEngineVersion.readAsStringSync(),
|
||||
equalsIgnoringWhitespace(mergeBaseHeadUpstream.stdout),
|
||||
);
|
||||
});
|
||||
|
||||
test('merge-base HEAD origin/master on non-LUCI when upstream is not set', () async {
|
||||
await setupRemote(remote: 'origin');
|
||||
|
||||
final ProcessRunnerResult mergeBaseHeadOrigin = await processRunner.runProcess(<String>[
|
||||
'git',
|
||||
'merge-base',
|
||||
'HEAD',
|
||||
'origin/master',
|
||||
]);
|
||||
await runUpdateEngineVersion();
|
||||
|
||||
expect(testRoot.binInternalEngineVersion, exists);
|
||||
expect(
|
||||
testRoot.binInternalEngineVersion.readAsStringSync(),
|
||||
equalsIgnoringWhitespace(mergeBaseHeadOrigin.stdout),
|
||||
);
|
||||
});
|
||||
|
||||
test('rev-parse HEAD when running on LUCI', () async {
|
||||
environment['LUCI_CONTEXT'] = '_NON_NULL_AND_NON_EMPTY_STRING';
|
||||
await runUpdateEngineVersion();
|
||||
|
||||
final ProcessRunnerResult revParseHead = await processRunner.runProcess(<String>[
|
||||
'git',
|
||||
'rev-parse',
|
||||
'HEAD',
|
||||
]);
|
||||
expect(testRoot.binInternalEngineVersion, exists);
|
||||
expect(
|
||||
testRoot.binInternalEngineVersion.readAsStringSync(),
|
||||
equalsIgnoringWhitespace(revParseHead.stdout),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('if DEPS or engine/src/.gn are omitted', () {
|
||||
setUp(() {
|
||||
for (final File f in <File>[testRoot.deps, testRoot.engineSrcGn]) {
|
||||
f.createSync(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
test('[DEPS] engine.version is blank', () async {
|
||||
testRoot.deps.deleteSync();
|
||||
|
||||
await runUpdateEngineVersion();
|
||||
|
||||
expect(testRoot.binInternalEngineVersion, exists);
|
||||
expect(testRoot.binInternalEngineVersion.readAsStringSync(), equalsIgnoringWhitespace(''));
|
||||
});
|
||||
|
||||
test('[engine/src/.gn] engine.version is blank', () async {
|
||||
testRoot.engineSrcGn.deleteSync();
|
||||
|
||||
await 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);
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ dependencies:
|
||||
archive: 3.6.1
|
||||
args: 2.6.0
|
||||
dds: 5.0.0
|
||||
dwds: 24.3.2
|
||||
dwds: 24.3.3
|
||||
completion: 1.0.1
|
||||
coverage: 1.11.1
|
||||
crypto: 3.0.6
|
||||
@ -121,4 +121,4 @@ dartdoc:
|
||||
# Exclude this package from the hosted API docs.
|
||||
nodoc: true
|
||||
|
||||
# PUBSPEC CHECKSUM: b23b
|
||||
# PUBSPEC CHECKSUM: cf3c
|
||||
|
Loading…
x
Reference in New Issue
Block a user