forked from flutter/flutter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split GoldensClient out of flutter_goldens.dart (flutter#20102)
This enables using GoldensClient from the flutter driver file for the embedded Android views test. flutter_goldens.dart has a transitive dependency on dart:ui so it cannot be included in non Flutter apps. Splitting it out allows us to use it from driver tests. This also removes the visibleForTesting annotation from GoldensClient.
- Loading branch information
Showing
2 changed files
with
180 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
// Copyright 2018 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'dart:async'; | ||
import 'dart:io' as io; | ||
|
||
import 'package:file/file.dart'; | ||
import 'package:file/local.dart'; | ||
import 'package:platform/platform.dart'; | ||
import 'package:process/process.dart'; | ||
|
||
// If you are here trying to figure out how to use golden files in the Flutter | ||
// repo itself, consider reading this wiki page: | ||
// https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package%3Aflutter | ||
|
||
const String _kFlutterRootKey = 'FLUTTER_ROOT'; | ||
|
||
/// A class that represents a clone of the https://github.com/flutter/goldens | ||
/// repository, nested within the `bin/cache` directory of the caller's Flutter | ||
/// repository. | ||
class GoldensClient { | ||
/// Create a handle to a local clone of the goldens repository. | ||
GoldensClient({ | ||
this.fs = const LocalFileSystem(), | ||
this.platform = const LocalPlatform(), | ||
this.process = const LocalProcessManager(), | ||
}); | ||
|
||
/// The file system to use for storing the local clone of the repository. | ||
/// | ||
/// This is useful in tests, where a local file system (the default) can | ||
/// be replaced by a memory file system. | ||
final FileSystem fs; | ||
|
||
/// A wrapper for the [dart:io.Platform] API. | ||
/// | ||
/// This is useful in tests, where the system platform (the default) can | ||
/// be replaced by a mock platform instance. | ||
final Platform platform; | ||
|
||
/// A controller for launching subprocesses. | ||
/// | ||
/// This is useful in tests, where the real process manager (the default) | ||
/// can be replaced by a mock process manager that doesn't really create | ||
/// subprocesses. | ||
final ProcessManager process; | ||
|
||
RandomAccessFile _lock; | ||
|
||
/// The local [Directory] where the Flutter repository is hosted. | ||
/// | ||
/// Uses the [fs] file system. | ||
Directory get flutterRoot => fs.directory(platform.environment[_kFlutterRootKey]); | ||
|
||
/// The local [Directory] where the goldens repository is hosted. | ||
/// | ||
/// Uses the [fs] file system. | ||
Directory get repositoryRoot => flutterRoot.childDirectory(fs.path.join('bin', 'cache', 'pkg', 'goldens')); | ||
|
||
/// Prepares the local clone of the `flutter/goldens` repository for golden | ||
/// file testing. | ||
/// | ||
/// This ensures that the goldens repository has been cloned into its | ||
/// expected location within `bin/cache` and that it is synced to the Git | ||
/// revision specified in `bin/internal/goldens.version`. | ||
/// | ||
/// While this is preparing the repository, it obtains a file lock such that | ||
/// [GoldensClient] instances in other processes or isolates will not | ||
/// duplicate the work that this is doing. | ||
Future<void> prepare() async { | ||
final String goldensCommit = await _getGoldensCommit(); | ||
String currentCommit = await _getCurrentCommit(); | ||
if (currentCommit != goldensCommit) { | ||
await _obtainLock(); | ||
try { | ||
// Check the current commit again now that we have the lock. | ||
currentCommit = await _getCurrentCommit(); | ||
if (currentCommit != goldensCommit) { | ||
if (currentCommit == null) { | ||
await _initRepository(); | ||
} | ||
await _syncTo(goldensCommit); | ||
} | ||
} finally { | ||
await _releaseLock(); | ||
} | ||
} | ||
} | ||
|
||
Future<String> _getGoldensCommit() async { | ||
final File versionFile = flutterRoot.childFile(fs.path.join('bin', 'internal', 'goldens.version')); | ||
return (await versionFile.readAsString()).trim(); | ||
} | ||
|
||
Future<String> _getCurrentCommit() async { | ||
if (!repositoryRoot.existsSync()) { | ||
return null; | ||
} else { | ||
final io.ProcessResult revParse = await process.run( | ||
<String>['git', 'rev-parse', 'HEAD'], | ||
workingDirectory: repositoryRoot.path, | ||
); | ||
return revParse.exitCode == 0 ? revParse.stdout.trim() : null; | ||
} | ||
} | ||
|
||
Future<void> _initRepository() async { | ||
await repositoryRoot.create(recursive: true); | ||
await _runCommands( | ||
<String>[ | ||
'git init', | ||
'git remote add upstream https://github.com/flutter/goldens.git', | ||
'git remote set-url --push upstream git@github.com:flutter/goldens.git', | ||
], | ||
workingDirectory: repositoryRoot, | ||
); | ||
} | ||
|
||
Future<void> _syncTo(String commit) async { | ||
await _runCommands( | ||
<String>[ | ||
'git pull upstream master', | ||
'git fetch upstream $commit', | ||
'git reset --hard FETCH_HEAD', | ||
], | ||
workingDirectory: repositoryRoot, | ||
); | ||
} | ||
|
||
Future<void> _runCommands( | ||
List<String> commands, { | ||
Directory workingDirectory, | ||
}) async { | ||
for (String command in commands) { | ||
final List<String> parts = command.split(' '); | ||
final io.ProcessResult result = await process.run( | ||
parts, | ||
workingDirectory: workingDirectory?.path, | ||
); | ||
if (result.exitCode != 0) { | ||
throw new NonZeroExitCode(result.exitCode, result.stderr); | ||
} | ||
} | ||
} | ||
|
||
Future<void> _obtainLock() async { | ||
final File lockFile = flutterRoot.childFile(fs.path.join('bin', 'cache', 'goldens.lockfile')); | ||
await lockFile.create(recursive: true); | ||
_lock = await lockFile.open(mode: io.FileMode.write); | ||
await _lock.lock(io.FileLock.blockingExclusive); | ||
} | ||
|
||
Future<void> _releaseLock() async { | ||
await _lock.close(); | ||
_lock = null; | ||
} | ||
} | ||
/// Exception that signals a process' exit with a non-zero exit code. | ||
class NonZeroExitCode implements Exception { | ||
/// Create an exception that represents a non-zero exit code. | ||
/// | ||
/// The first argument must be non-zero. | ||
const NonZeroExitCode(this.exitCode, this.stderr) : assert(exitCode != 0); | ||
|
||
/// The code that the process will signal to th eoperating system. | ||
/// | ||
/// By definiton, this is not zero. | ||
final int exitCode; | ||
|
||
/// The message to show on standard error. | ||
final String stderr; | ||
|
||
@override | ||
String toString() { | ||
return 'Exit code $exitCode: $stderr'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters