Unverified Commit cd803ac7 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Improve hot reload performance (#28152)

parent bfac6048
......@@ -70,4 +70,5 @@ dev_dependencies:
mockito: 4.0.0
test_api: 0.2.2
# PUBSPEC CHECKSUM: 422e
......@@ -15,7 +15,6 @@ import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/attach.dart';
import 'package:flutter_tools/src/commands/doctor.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
import 'package:flutter_tools/src/run_hot.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
final ArgParser parser = ArgParser()
......@@ -100,8 +99,7 @@ Future<void> main(List<String> args) async {
platformKernelDill: platformKernelDill,
flutterPatchedSdk: flutterPatchedSdk,
),
HotRunnerConfig: () => HotRunnerConfig()..computeDartDependencies = false,
},
}
);
}
......
......@@ -18,6 +18,7 @@ import '../fuchsia/fuchsia_device.dart';
import '../globals.dart';
import '../ios/devices.dart';
import '../ios/simulators.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import '../resident_runner.dart';
import '../run_cold.dart';
......@@ -129,6 +130,7 @@ class AttachCommand extends FlutterCommand {
Future<FlutterCommandResult> runCommand() async {
final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
final FlutterProject flutterProject = await FlutterProject.current();
Cache.releaseLockEarly();
......@@ -228,6 +230,7 @@ class AttachCommand extends FlutterCommand {
projectRootPath: argResults['project-root'],
dillOutputPath: argResults['output-dill'],
ipv6: usesIpv6,
flutterProject: flutterProject,
)
: ColdRunner(
flutterDevices,
......@@ -289,6 +292,7 @@ class HotRunnerFactory {
String dillOutputPath,
bool stayResident = true,
bool ipv6 = false,
FlutterProject flutterProject,
}) => HotRunner(
devices,
target: target,
......@@ -302,6 +306,7 @@ class HotRunnerFactory {
dillOutputPath: dillOutputPath,
stayResident: stayResident,
ipv6: ipv6,
flutterProject: flutterProject,
);
}
......
......@@ -19,6 +19,7 @@ import '../convert.dart';
import '../device.dart';
import '../emulator.dart';
import '../globals.dart';
import '../project.dart';
import '../resident_runner.dart';
import '../run_cold.dart';
import '../run_hot.dart';
......@@ -341,6 +342,7 @@ class AppDomain extends Domain {
if (await device.isLocalEmulator && !options.buildInfo.supportsEmulator) {
throw '${toTitleCase(options.buildInfo.friendlyModeName)} mode is not supported for emulators.';
}
final FlutterProject flutterProject = await FlutterProject.current();
// We change the current working directory for the duration of the `start` command.
final Directory cwd = fs.currentDirectory;
......@@ -368,6 +370,7 @@ class AppDomain extends Domain {
dillOutputPath: dillOutputPath,
ipv6: ipv6,
hostIsIde: true,
flutterProject: flutterProject,
);
} else {
runner = ColdRunner(
......
......@@ -13,6 +13,7 @@ import '../cache.dart';
import '../device.dart';
import '../globals.dart';
import '../ios/mac.dart';
import '../project.dart';
import '../resident_runner.dart';
import '../run_cold.dart';
import '../run_hot.dart';
......@@ -280,6 +281,7 @@ class RunCommand extends RunCommandBase {
// Enable hot mode by default if `--no-hot` was not passed and we are in
// debug mode.
final bool hotMode = shouldUseHotMode();
final FlutterProject flutterProject = await FlutterProject.current();
writePidFile(argResults['pid-file']);
......@@ -389,6 +391,7 @@ class RunCommand extends RunCommandBase {
saveCompilationTrace: argResults['train'],
stayResident: stayResident,
ipv6: ipv6,
flutterProject: flutterProject,
);
} else {
runner = ColdRunner(
......
......@@ -1093,7 +1093,7 @@ class PubspecDependency extends PubspecLine {
/// Generates the File object for the pubspec.yaml file of a given Directory.
File _pubspecFor(Directory directory) {
return fs.file('${directory.path}/pubspec.yaml');
return fs.file(fs.path.join(directory.path, 'pubspec.yaml'));
}
/// Generates the source of a fake pubspec.yaml file given a list of
......
// Copyright 2016 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.
// TODO(dnfield): This will be removed when @jonahwilliams' work on build lands
// ignore: deprecated_member_use
import 'package:analyzer/analyzer.dart' as analyzer;
import '../base/file_system.dart';
import '../dart/package_map.dart';
// List of flutter specific environment configurations.
// See https://github.com/munificent/dep-interface-libraries/blob/master/Proposal.md
// We will populate this list as required. Potentially, all of dart:* libraries
// supported by flutter would end up here.
final List<String> _configurationConstants = <String>['dart.library.io'];
String _dottedNameToString(analyzer.DottedName dottedName) {
String result = '';
for (analyzer.SimpleIdentifier identifier in dottedName.components) {
if (result.isEmpty) {
result += identifier.token.lexeme;
} else {
result += '.' + identifier.token.lexeme;
}
}
return result;
}
class DartDependencySetBuilder {
DartDependencySetBuilder(String mainScriptPath, String packagesFilePath)
: _mainScriptPath = canonicalizePath(mainScriptPath),
_mainScriptUri = fs.path.toUri(mainScriptPath),
_packagesFilePath = canonicalizePath(packagesFilePath);
final String _mainScriptPath;
final String _packagesFilePath;
final Uri _mainScriptUri;
Set<String> build() {
final List<String> dependencies = <String>[_mainScriptPath, _packagesFilePath];
final List<Uri> toProcess = <Uri>[_mainScriptUri];
final PackageMap packageMap = PackageMap(_packagesFilePath);
while (toProcess.isNotEmpty) {
final Uri currentUri = toProcess.removeLast();
final analyzer.CompilationUnit unit = _parse(currentUri.toFilePath());
for (analyzer.Directive directive in unit.directives) {
if (!(directive is analyzer.UriBasedDirective))
continue;
String uriAsString;
if (directive is analyzer.NamespaceDirective) {
final analyzer.NamespaceDirective namespaceDirective = directive;
// If the directive is a conditional import directive, we should
// select the imported uri based on the condition.
for (analyzer.Configuration configuration in namespaceDirective.configurations) {
if (_configurationConstants.contains(_dottedNameToString(configuration.name))) {
uriAsString = configuration.uri.stringValue;
break;
}
}
}
if (uriAsString == null) {
final analyzer.UriBasedDirective uriBasedDirective = directive;
uriAsString = uriBasedDirective.uri.stringValue;
}
Uri uri;
try {
uri = Uri.parse(uriAsString);
} on FormatException {
throw DartDependencyException('Unable to parse URI: $uriAsString');
}
Uri resolvedUri = analyzer.resolveRelativeUri(currentUri, uri);
if (resolvedUri.scheme.startsWith('dart'))
continue;
if (resolvedUri.scheme == 'package') {
final Uri newResolvedUri = packageMap.uriForPackage(resolvedUri);
if (newResolvedUri == null) {
throw DartDependencyException(
'The following Dart file:\n'
' ${currentUri.toFilePath()}\n'
'...refers, in an import, to the following library:\n'
' $resolvedUri\n'
'That library is in a package that is not known. Maybe you forgot to '
'mention it in your pubspec.yaml file?'
);
}
resolvedUri = newResolvedUri;
}
final String path = canonicalizePath(resolvedUri.toFilePath());
if (!dependencies.contains(path)) {
if (!fs.isFileSync(path)) {
throw DartDependencyException(
'The following Dart file:\n'
' ${currentUri.toFilePath()}\n'
'...refers, in an import, to the following library:\n'
' $path\n'
'Unfortunately, that library does not appear to exist on your file system.'
);
}
dependencies.add(path);
toProcess.add(resolvedUri);
}
}
}
return dependencies.toSet();
}
analyzer.CompilationUnit _parse(String path) {
String body;
try {
body = fs.file(path).readAsStringSync();
} on FileSystemException catch (error) {
throw DartDependencyException(
'Could not read "$path" when determining Dart dependencies.',
error,
);
}
try {
return analyzer.parseDirectives(body, name: path);
} on analyzer.AnalyzerError catch (error) {
throw DartDependencyException(
'When trying to parse this Dart file to find its dependencies:\n'
' $path\n'
'...the analyzer failed with the following error:\n'
' ${error.toString().trimRight()}',
error,
);
} on analyzer.AnalyzerErrorGroup catch (error) {
throw DartDependencyException(
'When trying to parse this Dart file to find its dependencies:\n'
' $path\n'
'...the analyzer failed with the following error:\n'
' ${error.toString().trimRight()}',
error,
);
}
}
}
class DartDependencyException implements Exception {
DartDependencyException(this.message, [this.parent]);
final String message;
final Exception parent;
@override
String toString() => message;
}
// Copyright 2016 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 'asset.dart';
import 'base/file_system.dart';
import 'dart/dependencies.dart';
import 'globals.dart';
class DependencyChecker {
DependencyChecker(this.builder, this.assets);
final DartDependencySetBuilder builder;
final Set<String> _dependencies = <String>{};
final AssetBundle assets;
/// Returns [true] if any components have been modified after [threshold] or
/// if it cannot be determined.
bool check(DateTime threshold) {
_dependencies.clear();
// Build the set of Dart dependencies.
try {
_dependencies.addAll(builder.build());
} catch (e, st) {
printTrace('DependencyChecker: error determining .dart dependencies:\n$e\n$st');
return true;
}
// TODO(johnmccutchan): Extract dependencies from the AssetBundle too.
// Check all dependency modification times.
for (String path in _dependencies) {
final File file = fs.file(path);
final FileStat stat = file.statSync();
if (stat.type == FileSystemEntityType.notFound) {
printTrace('DependencyChecker: Error stating $path.');
return true;
}
if (stat.modified.isAfter(threshold)) {
printTrace('DependencyChecker: $path is newer than $threshold');
return true;
}
}
printTrace('DependencyChecker: nothing is modified after $threshold.');
return false;
}
}
This diff is collapsed.
......@@ -18,9 +18,7 @@ import 'base/utils.dart';
import 'build_info.dart';
import 'codegen.dart';
import 'compile.dart';
import 'dart/dependencies.dart';
import 'dart/package_map.dart';
import 'dependency_checker.dart';
import 'devfs.dart';
import 'device.dart';
import 'globals.dart';
......@@ -444,10 +442,10 @@ class FlutterDevice {
DateTime firstBuildTime,
bool bundleFirstUpload = false,
bool bundleDirty = false,
Set<String> fileFilter,
bool fullRestart = false,
String projectRootPath,
String pathToReload,
@required List<String> invalidatedFiles,
}) async {
final Status devFSStatus = logger.startProgress(
'Syncing files to device ${device.name}...',
......@@ -462,13 +460,13 @@ class FlutterDevice {
firstBuildTime: firstBuildTime,
bundleFirstUpload: bundleFirstUpload,
bundleDirty: bundleDirty,
fileFilter: fileFilter,
generator: generator,
fullRestart: fullRestart,
dillOutputPath: dillOutputPath,
trackWidgetCreation: trackWidgetCreation,
projectRootPath: projectRootPath,
pathToReload: pathToReload,
invalidatedFiles: invalidatedFiles,
);
} on DevFSException {
devFSStatus.cancel();
......@@ -945,21 +943,19 @@ abstract class ResidentRunner {
}
bool hasDirtyDependencies(FlutterDevice device) {
/// When using the build system, dependency analysis is handled by build
/// runner instead.
if (experimentalBuildEnabled) {
return false;
}
final DartDependencySetBuilder dartDependencySetBuilder =
DartDependencySetBuilder(mainPath, packagesFilePath);
final DependencyChecker dependencyChecker =
DependencyChecker(dartDependencySetBuilder, assetBundle);
if (device.package.packagesFile == null || !device.package.packagesFile.existsSync()) {
return true;
}
final DateTime lastBuildTime = device.package.packagesFile.statSync().modified;
return dependencyChecker.check(lastBuildTime);
// Leave pubspec null to check all dependencies.
final ProjectFileInvalidator projectFileInvalidator = ProjectFileInvalidator(device.package.packagesFile.path, null);
projectFileInvalidator.findInvalidated();
final int lastBuildTime = device.package.packagesFile.statSync().modified.millisecondsSinceEpoch;
for (int updateTime in projectFileInvalidator.updateTime.values) {
if (updateTime > lastBuildTime) {
return true;
}
}
return false;
}
Future<void> preStop() async { }
......
This diff is collapsed.
......@@ -9,7 +9,6 @@ environment:
dependencies:
# To update these, use "flutter update-packages --force-upgrade".
analyzer: 0.35.3
archive: 2.0.8
args: 1.5.1
bsdiff: 0.1.0
......@@ -53,6 +52,7 @@ dependencies:
build_modules: 1.0.9
build_daemon: 0.4.2
analyzer: 0.35.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
async: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
bazel_worker: 0.1.20 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
boolean_selector: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
......@@ -100,8 +100,8 @@ dev_dependencies:
collection: 1.14.11
mockito: 4.0.0
file_testing: 2.1.0
test: 1.5.3
vm_service_lib: 3.14.2
test: 1.5.3
build_runner: 1.2.8
build_vm_compilers: 0.1.1+5
build_test: 0.10.6
......
......@@ -120,6 +120,7 @@ void main() {
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'),
flutterProject: anyNamed('flutterProject'),
ipv6: false,
),
).thenReturn(mockHotRunner);
......@@ -151,6 +152,7 @@ void main() {
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'),
flutterProject: anyNamed('flutterProject'),
ipv6: false,
),
)..called(1);
......@@ -225,6 +227,7 @@ void main() {
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'),
flutterProject: anyNamed('flutterProject'),
ipv6: false,
)).thenReturn(mockHotRunner);
......@@ -254,6 +257,7 @@ void main() {
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'),
flutterProject: anyNamed('flutterProject'),
ipv6: false,
)).called(1);
}, overrides: <Type, Generator>{
......
// Copyright 2016 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 'package:flutter_tools/src/dart/dependencies.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'src/common.dart';
import 'src/context.dart';
void main() {
group('DartDependencySetBuilder', () {
final String dataPath = fs.path.join(
getFlutterRoot(),
'packages',
'flutter_tools',
'test',
'data',
'dart_dependencies_test',
);
testUsingContext('good', () {
final String testPath = fs.path.join(dataPath, 'good');
final String mainPath = fs.path.join(testPath, 'main.dart');
final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder =
DartDependencySetBuilder(mainPath, packagesPath);
final Set<String> dependencies = builder.build();
expect(dependencies.contains(canonicalizePath(mainPath)), isTrue);
expect(dependencies.contains(canonicalizePath(fs.path.join(testPath, 'foo.dart'))), isTrue);
});
testUsingContext('syntax_error', () {
final String testPath = fs.path.join(dataPath, 'syntax_error');
final String mainPath = fs.path.join(testPath, 'main.dart');
final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder =
DartDependencySetBuilder(mainPath, packagesPath);
try {
builder.build();
fail('expect an exception to be thrown.');
} on DartDependencyException catch (error) {
expect(error.toString(), contains('foo.dart: Expected a string literal'));
}
});
testUsingContext('bad_path', () {
final String testPath = fs.path.join(dataPath, 'bad_path');
final String mainPath = fs.path.join(testPath, 'main.dart');
final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder =
DartDependencySetBuilder(mainPath, packagesPath);
try {
builder.build();
fail('expect an exception to be thrown.');
} on DartDependencyException catch (error) {
expect(error.toString(), contains('amaze${fs.path.separator}and${fs.path.separator}astonish.dart'));
}
});
testUsingContext('bad_package', () {
final String testPath = fs.path.join(dataPath, 'bad_package');
final String mainPath = fs.path.join(testPath, 'main.dart');
final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder =
DartDependencySetBuilder(mainPath, packagesPath);
try {
builder.build();
fail('expect an exception to be thrown.');
} on DartDependencyException catch (error) {
expect(error.toString(), contains('rochambeau'));
expect(error.toString(), contains('pubspec.yaml'));
}
});
testUsingContext('does not change ASCII casing of path', () {
final String testPath = fs.path.join(dataPath, 'asci_casing');
final String mainPath = fs.path.join(testPath, 'main.dart');
final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder = DartDependencySetBuilder(mainPath, packagesPath);
final Set<String> deps = builder.build();
expect(deps, contains(endsWith('This_Import_Has_fuNNy_casING.dart')));
});
testUsingContext('bad_import', () {
final String testPath = fs.path.join(dataPath, 'bad_import');
final String mainPath = fs.path.join(testPath, 'main.dart');
final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder =
DartDependencySetBuilder(mainPath, packagesPath);
try {
builder.build();
fail('expect an exception to be thrown.');
} on DartDependencyException catch (error) {
expect(error.toString(), contains('Unable to parse URI'));
}
});
});
}
// Copyright 2017 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.
String dummy = 'Hello';
// Copyright 2017 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 'This_Import_Has_fuNNy_casING.dart';
void main() {
print(dummy);
}
// Copyright 2017 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 'data://object.dart';
// Copyright 2016 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 'package:rochambeau/you_have_your_orders_now_go_man_go.dart';
// Copyright 2016 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 'amaze/and/astonish.dart';
flutter:file:///a/wild/non-existent/directory/has/appeared
sdk-move-test:lib/
// Copyright 2017 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.
// No content
name: sdk-move-test
environment:
# The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
// Copyright 2016 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.
// Copyright 2016 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.
// Copyright 2016 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 'foo.dart';
import 'package:self/bar.dart';
// Copyright 2016 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 bad programmer!
// Copyright 2016 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 'foo.dart';
// Copyright 2016 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 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/devices.dart';
import 'package:flutter_tools/src/dart/dependencies.dart';
import 'package:flutter_tools/src/dependency_checker.dart';
import 'src/common.dart';
import 'src/context.dart';
void main() {
group('DependencyChecker', () {
final String dataPath = fs.path.join(
getFlutterRoot(),
'packages',
'flutter_tools',
'test',
'data',
'dart_dependencies_test',
);
FileSystem testFileSystem;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
testFileSystem = MemoryFileSystem();
});
testUsingContext('good', () {
final String testPath = fs.path.join(dataPath, 'good');
final String mainPath = fs.path.join(testPath, 'main.dart');
final String fooPath = fs.path.join(testPath, 'foo.dart');
final String barPath = fs.path.join(testPath, 'lib', 'bar.dart');
final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder =
DartDependencySetBuilder(mainPath, packagesPath);
final DependencyChecker dependencyChecker =
DependencyChecker(builder, null);
// Set file modification time on all dependencies to be in the past.
final DateTime baseTime = DateTime.now();
updateFileModificationTime(packagesPath, baseTime, -10);
updateFileModificationTime(mainPath, baseTime, -10);
updateFileModificationTime(fooPath, baseTime, -10);
updateFileModificationTime(barPath, baseTime, -10);
expect(dependencyChecker.check(baseTime), isFalse);
// Set .packages file modification time to be in the future.
updateFileModificationTime(packagesPath, baseTime, 20);
expect(dependencyChecker.check(baseTime), isTrue);
// Reset .packages file modification time.
updateFileModificationTime(packagesPath, baseTime, 0);
expect(dependencyChecker.check(baseTime), isFalse);
// Set 'package:self/bar.dart' file modification time to be in the future.
updateFileModificationTime(barPath, baseTime, 10);
expect(dependencyChecker.check(baseTime), isTrue);
});
testUsingContext('syntax error', () {
final String testPath = fs.path.join(dataPath, 'syntax_error');
final String mainPath = fs.path.join(testPath, 'main.dart');
final String fooPath = fs.path.join(testPath, 'foo.dart');
final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder =
DartDependencySetBuilder(mainPath, packagesPath);
final DependencyChecker dependencyChecker =
DependencyChecker(builder, null);
final DateTime baseTime = DateTime.now();
// Set file modification time on all dependencies to be in the past.
updateFileModificationTime(packagesPath, baseTime, -10);
updateFileModificationTime(mainPath, baseTime, -10);
updateFileModificationTime(fooPath, baseTime, -10);
// Dependencies are considered dirty because there is a syntax error in
// the .dart file.
expect(dependencyChecker.check(baseTime), isTrue);
});
/// Test a flutter tool move.
///
/// Tests that the flutter tool doesn't crash and displays a warning when its own location
/// changed since it was last referenced to in a package's .packages file.
testUsingContext('moved flutter sdk', () async {
final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_dependency_checker_test.');
// Copy the golden input and let the test run in an isolated temporary in-memory file system.
const LocalFileSystem localFileSystem = LocalFileSystem();
final Directory sourcePath = localFileSystem.directory(localFileSystem.path.join(dataPath, 'changed_sdk_location'));
copyDirectorySync(sourcePath, tempDir);
fs.currentDirectory = tempDir;
// Doesn't matter what commands we run. Arbitrarily list devices here.
await createTestCommandRunner(DevicesCommand()).run(<String>['devices']);
expect(testLogger.errorText, contains('.packages'));
tryToDelete(tempDir);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
});
});
}
This diff is collapsed.
......@@ -105,13 +105,13 @@ void main() {
firstBuildTime: anyNamed('firstBuildTime'),
bundleFirstUpload: anyNamed('bundleFirstUpload'),
bundleDirty: anyNamed('bundleDirty'),
fileFilter: anyNamed('fileFilter'),
generator: anyNamed('generator'),
fullRestart: anyNamed('fullRestart'),
dillOutputPath: anyNamed('dillOutputPath'),
trackWidgetCreation: anyNamed('trackWidgetCreation'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
)).thenAnswer((Invocation _) => Future<UpdateFSReport>.value(
UpdateFSReport(success: true, syncedBytes: 1000, invalidatedSourcesCount: 1)));
when(mockDevFs.assetPathsToEvict).thenReturn(<String>{});
......@@ -122,18 +122,6 @@ void main() {
when(mockArtifacts.getArtifactPath(Artifact.flutterPatchedSdkPath)).thenReturn('some/path');
});
testUsingContext('no setup', () async {
final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(true);
final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(mockDevice, generator: residentCompiler, trackWidgetCreation: false),
];
expect((await HotRunner(devices).restart(fullRestart: true)).isOk, false);
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
});
testUsingContext('Does not hot restart when device does not support it', () async {
// Setup mocks
final MockDevice mockDevice = MockDevice();
......@@ -149,7 +137,7 @@ void main() {
expect(result.message, 'hotRestart not supported');
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true, computeDartDependencies: false),
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
});
testUsingContext('Does not hot restart when one of many devices does not support it', () async {
......@@ -171,7 +159,7 @@ void main() {
expect(result.message, 'hotRestart not supported');
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true, computeDartDependencies: false),
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
});
testUsingContext('Does hot restarts when all devices support it', () async {
......@@ -193,7 +181,7 @@ void main() {
expect(result.message, isNot('hotRestart not supported'));
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true, computeDartDependencies: false),
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
});
testUsingContext('setup function fails', () async {
......@@ -226,7 +214,7 @@ void main() {
expect(result.message, isNot('setupHotRestart failed'));
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true, computeDartDependencies: false),
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
});
group('shutdown hook tests', () {
......@@ -235,7 +223,6 @@ void main() {
setUp(() {
shutdownTestingConfig = TestHotRunnerConfig(
successfulSetup: true,
computeDartDependencies: false,
);
});
......@@ -283,10 +270,7 @@ class MockDevice extends Mock implements Device {
}
class TestHotRunnerConfig extends HotRunnerConfig {
TestHotRunnerConfig({@required this.successfulSetup, bool computeDartDependencies = true}) {
this.computeDartDependencies = computeDartDependencies;
}
TestHotRunnerConfig({@required this.successfulSetup});
bool successfulSetup;
bool shutdownHookCalled = false;
......
// Copyright 2019 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 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/run_hot.dart';
import 'package:mockito/mockito.dart';
import 'src/common.dart';
import 'src/context.dart';
void main() {
final Platform windowsPlatform = MockPlatform();
final Platform notWindowsPlatform = MockPlatform();
final BufferLogger bufferLogger = BufferLogger();
when(windowsPlatform.isWindows).thenReturn(true);
when(notWindowsPlatform.isWindows).thenReturn(false);
group('ProjectFileInvalidator linux/mac', () {
final MemoryFileSystem memoryFileSystem = MemoryFileSystem();
final File packagesFile = memoryFileSystem.file('.packages')
..createSync()
..writeAsStringSync(r'''
foo:file:///foo/lib/
bar:file:///.pub-cache/bar/lib/
baz:file:///baz/lib/
test_package:file:///lib/
''');
final File pubspecFile = memoryFileSystem.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: test_package
dependencies:
foo: any
bar:
dev_dependencies:
baz: any
''');
final File mainFile = memoryFileSystem.file('lib/main.dart')
..createSync(recursive: true)
..writeAsStringSync(r'''
void main() {}
''');
final File fooFile = memoryFileSystem.file('foo/lib/foo.dart')
..createSync(recursive: true)
..writeAsStringSync('');
memoryFileSystem.file('bar/lib/bar.dart')
..createSync(recursive: true)
..writeAsStringSync('');
final File bazFile = memoryFileSystem.file('baz/lib/baz.dart')
..createSync(recursive: true)
..writeAsStringSync('');
testUsingContext('No .packages, no pubspec', () async {
// Instead of setting up multiple filesystems, passing a .packages file which does not exist.
final ProjectFileInvalidator invalidator = ProjectFileInvalidator('.packages-wrong', null);
invalidator.findInvalidated();
expect(invalidator.packageMap, isEmpty);
expect(invalidator.updateTime, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
Platform: () => notWindowsPlatform,
});
testUsingContext('.packages only', () async {
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, null);
invalidator.findInvalidated();
expect(invalidator.packageMap, <String, Uri>{
'foo': Uri.parse('file:///foo/lib/'),
// Excluded because it is in pub cache.
// 'bar': Uri.parse('file:///.pub-cache/bar/lib/'),
'baz': Uri.parse('file:///baz/lib/'),
'test_package': Uri.parse('file:///lib/'),
});
expect(invalidator.updateTime, <String, int>{
'/baz/lib/baz.dart': bazFile.statSync().modified.millisecondsSinceEpoch,
'/lib/main.dart': mainFile.statSync().modified.millisecondsSinceEpoch,
'/foo/lib/foo.dart': fooFile.statSync().modified.millisecondsSinceEpoch,
});
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
Platform: () => notWindowsPlatform,
});
testUsingContext('.packages and pubspec', () async {
final FlutterProject flutterProject = await FlutterProject.fromDirectory(pubspecFile.parent);
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, flutterProject);
invalidator.findInvalidated();
expect(invalidator.packageMap, <String, Uri>{
'foo': Uri.parse('file:///foo/lib/'),
// Excluded because it is in pub cache.
// 'bar': Uri.parse('file:///.pub-cache/bar/lib/'),
// Excluded because it is a dev dependency/
// 'baz': Uri.parse('file:///baz/lib/'),
'test_package': Uri.parse('file:///lib/'),
});
expect(invalidator.updateTime, <String, int>{
'/foo/lib/foo.dart': fooFile.statSync().modified.millisecondsSinceEpoch,
'/lib/main.dart':mainFile.statSync().modified.millisecondsSinceEpoch,
});
expect(invalidator.findInvalidated(), isEmpty);
// Invalidate main.dart.
mainFile.writeAsStringSync('void main() { }');
expect(invalidator.findInvalidated(), <String>['/lib/main.dart']);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
Platform: () => notWindowsPlatform,
});
testUsingContext('update to .packages triggers warning', () async {
final FlutterProject flutterProject = await FlutterProject.fromDirectory(pubspecFile.parent);
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, flutterProject);
invalidator.findInvalidated();
packagesFile.writeAsStringSync(r'''
foo:file:///foo/lib/
bar:file:///.pub-cache/bar/lib/
baz:file:///baz/lib/
new_dep:file:///new_dep/lib/
test_package:file:///lib/
''');
invalidator.findInvalidated();
expect(bufferLogger.errorText, 'Warning: updated dependencies detected. The Flutter application will require a restart to safely use new packages.\n');
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
Platform: () => notWindowsPlatform,
Logger: () => bufferLogger,
});
});
group('ProjectFileInvalidator windows', () {
final MemoryFileSystem memoryFileSystem = MemoryFileSystem(style: FileSystemStyle.windows);
// On windows .packages still contains file Uris, albeit ones with the Drive prefix.
final File packagesFile = memoryFileSystem.file(r'C:\.packages')
..createSync()
..writeAsStringSync(r'''
foo:file:///C:/foo/lib/
bar:file:///C:/Pub/Cache/bar/lib/
baz:file:///C:/baz/lib/
test_package:file:///C:/lib/
''');
memoryFileSystem.file(r'C:\pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: test_package
dependencies:
foo: any
bar:
dev_dependencies:
baz: any
''');
final File mainFile = memoryFileSystem.file(r'C:\lib\main.dart')
..createSync(recursive: true)
..writeAsStringSync(r'''
void main() {}
''');
final File fooFile = memoryFileSystem.file(r'C:\foo\lib\foo.dart')
..createSync(recursive: true)
..writeAsStringSync('');
memoryFileSystem.file(r'C:\bar\lib\bar.dart')
..createSync(recursive: true)
..writeAsStringSync('');
final File bazFile = memoryFileSystem.file(r'C:\baz\lib\baz.dart')
..createSync(recursive: true)
..writeAsStringSync('');
testUsingContext('No .packages, no pubspec', () async {
// Instead of setting up multiple filesystems, passing a .packages file which does not exist.
final ProjectFileInvalidator invalidator = ProjectFileInvalidator('.packages-wrong', null);
invalidator.findInvalidated();
expect(invalidator.packageMap, isEmpty);
expect(invalidator.updateTime, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
Platform: () => windowsPlatform,
});
testUsingContext('.packages only', () async {
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, null);
invalidator.findInvalidated();
expect(invalidator.packageMap, <String, Uri>{
'foo': Uri.file(r'C:\foo\lib\', windows: true),
// Excluded because it is in pub cache.
// 'bar': Uri.parse('file:///Pub/Cache/bar/lib/'),
'baz': Uri.file(r'C:\baz\lib\', windows: true),
'test_package': Uri.file(r'C:\lib\', windows: true),
});
expect(invalidator.updateTime, <String, int>{
r'C:\baz\lib\baz.dart': bazFile.statSync().modified.millisecondsSinceEpoch,
r'C:\lib\main.dart': mainFile.statSync().modified.millisecondsSinceEpoch,
r'C:\foo\lib\foo.dart': fooFile.statSync().modified.millisecondsSinceEpoch,
});
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
Platform: () => windowsPlatform,
});
testUsingContext('.packages and pubspec', () async {
final FlutterProject flutterProject = await FlutterProject.fromDirectory(fs.directory(r'C:\'));
final ProjectFileInvalidator invalidator = ProjectFileInvalidator(packagesFile.path, flutterProject);
invalidator.findInvalidated();
expect(invalidator.packageMap, <String, Uri>{
'foo': Uri.file(r'C:\foo\lib\', windows: true),
// Excluded because it is in pub cache.
// 'bar': Uri.parse('file:///C:/Pub/Cache/bar/lib/'),
// Excluded because it is a dev dependency/
// 'baz': Uri.parse('file:///baz/lib/'),
'test_package': Uri.file(r'C:\lib\', windows: true),
});
expect(invalidator.updateTime, <String, int>{
r'C:\lib\main.dart': mainFile.statSync().modified.millisecondsSinceEpoch,
r'C:\foo\lib\foo.dart': fooFile.statSync().modified.millisecondsSinceEpoch,
});
expect(invalidator.findInvalidated(), isEmpty);
// Invalidate main.dart.
mainFile.writeAsStringSync('void main() { }');
expect(invalidator.findInvalidated(), <String>['file:///C:/lib/main.dart']);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
Platform: () => windowsPlatform,
});
});
}
class MockPlatform extends Mock implements Platform {}
......@@ -460,12 +460,6 @@ class MockDevFSOperations extends BasicMock implements DevFSOperations {
messages.add(message);
devicePathToContent[deviceUri] = content;
}
@override
Future<dynamic> deleteFile(String fsName, Uri deviceUri) async {
messages.add('deleteFile $fsName $deviceUri');
devicePathToContent.remove(deviceUri);
}
}
class MockResidentCompiler extends BasicMock implements ResidentCompiler {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment