mirror of
https://github.com/flutter/flutter.git
synced 2026-01-09 07:51:35 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
299 lines
10 KiB
Dart
299 lines
10 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: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() {
|
|
// Want to test the powershell (update_engine_version.ps1) file, but running
|
|
// a macOS or Linux machine? You can install powershell and then opt-in to
|
|
// running `pwsh bin/internal/update_engine_version.ps1`.
|
|
//
|
|
// macOS: https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos
|
|
// linux: https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux
|
|
//
|
|
// Then, set this variable to true:
|
|
final bool usePowershellOnPosix = () {
|
|
// Intentionally not a const so that linting doesn't go wild across the test.
|
|
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',
|
|
);
|
|
}
|
|
});
|
|
|
|
/// Initializes a blank git repo in [testRoot.root].
|
|
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) {
|
|
// Copy a minimal set of environment variables needed to run the update_engine_version script in PowerShell.
|
|
const powerShellVariables = <String>['SYSTEMROOT', 'PATH', 'PATHEXT'];
|
|
for (final key in powerShellVariables) {
|
|
final String? value = io.Platform.environment[key];
|
|
if (value != null) {
|
|
environment[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the update_engine_version script and create a rough directory structure.
|
|
flutterRoot.binInternalLastEngineCommit.copySyncRecursive(
|
|
testRoot.binInternalLastEngineCommit.path,
|
|
);
|
|
|
|
initGitRepoWithBlankInitialCommit();
|
|
});
|
|
|
|
tearDown(() {
|
|
tmpDir.deleteSync(recursive: true);
|
|
});
|
|
|
|
/// Runs `bin/internal/last_engine_commit.{sh|ps1}` and returns the stdout.
|
|
///
|
|
/// - On Windows, `powershell` is used (to run `last_engine_commit.ps1`);
|
|
/// - On POSIX, if [usePowershellOnPosix] is set, `pwsh` is used (to run `last_engine_commit.ps1`);
|
|
/// - Otherwise, `last_engine_commit.sh` is used.
|
|
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();
|
|
}
|
|
|
|
/// Gets the latest commit on the current branch.
|
|
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();
|
|
|
|
// Make an engine change *before* the branch.
|
|
writeCommit(<String>['engine/README.md']);
|
|
final String engineCommitPreBranch = getLastCommit();
|
|
|
|
// Write the branch file.
|
|
writeCommit(<String>['bin/internal/release-candidate-branch.version']);
|
|
final String initialBranchCommit = getLastCommit();
|
|
|
|
// Write another commit to make sure we don't always use the latest.
|
|
writeCommit(<String>['CHANGELOG.md']);
|
|
final String latestCommitIgnore = getLastCommit();
|
|
|
|
// Get the engine commit, which should fallback to HEAD~2 (in this case).
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
/// ├── DEPS
|
|
/// ├── engine/
|
|
/// ├── bin/
|
|
/// │ ├── internal/
|
|
/// │ │ └── last_engine_commit.{sh|ps1}
|
|
/// ```
|
|
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(),
|
|
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;
|
|
|
|
/// `DEPS`.
|
|
final File depsFile;
|
|
|
|
/// The `engine/` directory.
|
|
final Directory engineDirectory;
|
|
|
|
/// `bin/internal/last_engine_commit.{sh|ps1}`.
|
|
final File binInternalLastEngineCommit;
|
|
}
|