// 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 _FlutterRootUnderTest flutterRoot = _FlutterRootUnderTest.findWithin( forcePowershell: usePowershellOnPosix, ); late Directory tmpDir; late _FlutterRootUnderTest testRoot; late Map 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 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) { 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', ['--version']); print('Using Powershell (${result.stdout}) on POSIX for local debugging and testing'); } }); /// Initializes a blank git repo in [testRoot.root]. void initGitRepoWithBlankInitialCommit() { run('git', ['init', '--initial-branch', 'master']); run('git', ['config', '--local', 'user.email', 'test@example.com']); run('git', ['config', '--local', 'user.name', 'Test User']); run('git', ['add', '.']); run('git', ['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 = {}; if (const LocalPlatform().isWindows || usePowershellOnPosix) { // Copy a minimal set of environment variables needed to run the update_engine_version script in PowerShell. const List powerShellVariables = ['SystemRoot', 'Path', 'PATHEXT']; for (final String 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 args; if (const LocalPlatform().isWindows) { executable = 'powershell'; args = [testRoot.binInternalLastEngineCommit.path]; } else if (usePowershellOnPosix) { executable = 'pwsh'; args = [testRoot.binInternalLastEngineCommit.path]; } else { executable = testRoot.binInternalLastEngineCommit.path; args = []; } return run(executable, args).stdout as String; } void writeCommit(Iterable files) { commitCount++; for (final String relativePath in files) { localFs.file(localFs.path.join(testRoot.root.path, relativePath)) ..createSync(recursive: true) ..writeAsStringSync('$commitCount'); } run('git', ['add', '.']); run('git', ['commit', '-m', 'Wrote ${files.length} files']); } test('returns the last engine commit', () { writeCommit(['DEPS', 'engine/README.md']); final String lastEngine = getLastEngineCommit(); expect(lastEngine, isNotEmpty); writeCommit(['CHANGELOG.md', 'dev/folder/called/engine/README.md']); expect(getLastEngineCommit(), lastEngine); }); test('considers DEPS an engine change', () { writeCommit(['DEPS', 'engine/README.md']); final String lastEngineA = getLastEngineCommit(); expect(lastEngineA, isNotEmpty); writeCommit(['DEPS']); final String lastEngineB = getLastEngineCommit(); expect(lastEngineB, allOf(isNotEmpty, isNot(equals(lastEngineA)))); }); } 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; }