Unverified Commit e8ec8a0d authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

[flutter_tools] Catch lack of flutter tools source missing (#93168)

parent d21bb197
......@@ -54,6 +54,7 @@ import 'src/globals.dart' as globals;
// Files in `isolated` are intentionally excluded from google3 tooling.
import 'src/isolated/mustache_template.dart';
import 'src/isolated/resident_web_runner.dart';
import 'src/pre_run_validator.dart';
import 'src/resident_runner.dart';
import 'src/runner/flutter_command.dart';
import 'src/web/web_runner.dart';
......@@ -126,6 +127,7 @@ Future<void> main(List<String> args) async {
windows: globals.platform.isWindows,
);
},
PreRunValidator: () => PreRunValidator(fileSystem: globals.fs),
},
);
}
......
......@@ -39,6 +39,7 @@ import 'macos/cocoapods_validator.dart';
import 'macos/xcdevice.dart';
import 'macos/xcode.dart';
import 'persistent_tool_state.dart';
import 'pre_run_validator.dart';
import 'project.dart';
import 'reporting/crash_reporting.dart';
import 'reporting/reporting.dart';
......@@ -257,3 +258,5 @@ FlutterProjectFactory get projectFactory {
}
CustomDevicesConfig get customDevicesConfig => context.get<CustomDevicesConfig>()!;
PreRunValidator get preRunValidator => context.get<PreRunValidator>() ?? const NoOpPreRunValidator();
// 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.
import 'base/common.dart';
import 'base/file_system.dart';
import 'cache.dart';
/// A validator that runs before the tool runs any command.
abstract class PreRunValidator {
factory PreRunValidator({
required FileSystem fileSystem,
}) => _DefaultPreRunValidator(fileSystem: fileSystem);
void validate();
}
class _DefaultPreRunValidator implements PreRunValidator {
_DefaultPreRunValidator({
required this.fileSystem,
});
final FileSystem fileSystem;
late final Directory _toolsDir = fileSystem.directory(
fileSystem.path.join(Cache.flutterRoot!, 'packages', 'flutter_tools'),
);
@override
void validate() {
// If a user downloads the Flutter SDK via a pre-built archive and there is
// an error during extraction, the user could have a valid Dart snapshot of
// the tool but not the source directory. We still need the source, so
// validate the source directory exists and toolExit if not.
if (!_toolsDir.existsSync()) {
throwToolExit(
'Flutter SDK installation appears corrupted: expected to find the '
'directory ${_toolsDir.path} but it does not exist! Please go to '
'https://flutter.dev/setup for instructions on how to re-install '
'Flutter.',
);
}
}
}
class NoOpPreRunValidator implements PreRunValidator {
const NoOpPreRunValidator();
@override
void validate() {}
}
......@@ -1251,6 +1251,7 @@ abstract class FlutterCommand extends Command<void> {
/// rather than calling [runCommand] directly.
@mustCallSuper
Future<FlutterCommandResult> verifyThenRunCommand(String? commandPath) async {
globals.preRunValidator.validate();
// Populate the cache. We call this before pub get below so that the
// sky_engine package is available in the flutter cache for pub to find.
if (shouldUpdateCache) {
......
......@@ -31,6 +31,8 @@ void main() {
expect(fakeStdio.writtenToStdout.length, equals(1));
expect(fakeStdio.writtenToStdout.first, contains('__flutter_completion'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
Stdio: () => fakeStdio,
});
......@@ -40,6 +42,8 @@ void main() {
expect(fakeStdio.writtenToStdout.length, equals(1));
expect(fakeStdio.writtenToStdout.first, contains('__flutter_completion'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
Stdio: () => fakeStdio,
});
......
......@@ -449,6 +449,7 @@ void main() {
testUsingContext('test without bot', () async {
Cache.flutterRoot = '';
globals.fs.directory('/packages/flutter_tools').createSync(recursive: true);
globals.fs.file('pubspec.yaml').createSync();
processManager.addCommand(
const FakeCommand(command: <String>['/bin/cache/dart-sdk/bin/dart', '__deprecated_pub', 'run', 'test']),
......
......@@ -19,6 +19,7 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/pre_run_validator.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:test/fake.dart';
......@@ -34,6 +35,13 @@ void main() {
TestUsage usage;
FakeClock clock;
FakeProcessInfo processInfo;
MemoryFileSystem fileSystem;
FakeProcessManager processManager;
PreRunValidator preRunValidator;
setUpAll(() {
Cache.flutterRoot = '/path/to/sdk/flutter';
});
setUp(() {
Cache.disableLocking();
......@@ -42,6 +50,9 @@ void main() {
clock = FakeClock();
processInfo = FakeProcessInfo();
processInfo.maxRss = 10;
fileSystem = MemoryFileSystem.test();
processManager = FakeProcessManager.empty();
preRunValidator = PreRunValidator(fileSystem: fileSystem);
});
tearDown(() {
......@@ -63,6 +74,8 @@ void main() {
expect(flutterCommand.hidden, isFalse);
},
overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Cache: () => cache,
});
......@@ -78,8 +91,24 @@ void main() {
],
);
},
overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Cache: () => cache,
});
testUsingContext("throws toolExit if flutter_tools source dir doesn't exist", () async {
final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
await expectToolExitLater(
flutterCommand.run(),
contains('Flutter SDK installation appears corrupted'),
);
},
overrides: <Type, Generator>{
Cache: () => cache,
FileSystem: () => fileSystem,
PreRunValidator: () => preRunValidator,
ProcessManager: () => processManager,
});
testUsingContext('deprecated command should warn', () async {
......@@ -95,6 +124,9 @@ void main() {
'of Flutter.'));
expect(flutterCommand.deprecated, isTrue);
expect(flutterCommand.hidden, isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('uses the error handling file system', () async {
......@@ -105,6 +137,9 @@ void main() {
}
);
await flutterCommand.run();
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('finds the target file with default values', () async {
......@@ -115,8 +150,8 @@ void main() {
expect(fakeTargetCommand.cachedTargetFile, 'lib/main.dart');
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('finds the target file with specified value', () async {
......@@ -127,8 +162,8 @@ void main() {
expect(fakeTargetCommand.cachedTargetFile, 'lib/foo.dart');
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('throws tool exit if specified file does not exist', () async {
......@@ -137,13 +172,15 @@ void main() {
expect(() async => runner.run(<String>['test', '-t', 'lib/foo.dart']), throwsToolExit());
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
void testUsingCommandContext(String testName, dynamic Function() testBody) {
testUsingContext(testName, testBody, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessInfo: () => processInfo,
ProcessManager: () => processManager,
SystemClock: () => clock,
Usage: () => usage,
});
......@@ -245,6 +282,9 @@ void main() {
'http://127.0.0.1:9105',
]);
expect(command.devToolsServerAddress.toString(), equals('http://127.0.0.1:9105'));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('devToolsServerAddress returns null for bad input', () async {
......@@ -277,6 +317,9 @@ void main() {
'127.0.0.1:9101',
]);
expect(command.devToolsServerAddress, isNull);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
group('signals tests', () {
......@@ -328,6 +371,8 @@ void main() {
),
]);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
ProcessInfo: () => processInfo,
Signals: () => FakeSignals(
subForSigTerm: signalUnderTest,
......@@ -363,6 +408,8 @@ void main() {
signalController.add(mockSignal);
await completer.future;
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
ProcessInfo: () => processInfo,
Signals: () => FakeSignals(
subForSigTerm: signalUnderTest,
......@@ -499,26 +546,35 @@ void main() {
}, overrides: <Type, Generator>{
Pub: () => FakePub(),
Usage: () => usage,
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('use packagesPath to generate BuildInfo', () async {
final DummyFlutterCommand flutterCommand = DummyFlutterCommand(packagesPath: 'foo');
final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
expect(buildInfo.packagesPath, 'foo');
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('use fileSystemScheme to generate BuildInfo', () async {
final DummyFlutterCommand flutterCommand = DummyFlutterCommand(fileSystemScheme: 'foo');
final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
expect(buildInfo.fileSystemScheme, 'foo');
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('use fileSystemRoots to generate BuildInfo', () async {
final DummyFlutterCommand flutterCommand = DummyFlutterCommand(fileSystemRoots: <String>['foo', 'bar']);
final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
expect(buildInfo.fileSystemRoots, <String>['foo', 'bar']);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('includes initializeFromDill in BuildInfo', () async {
......@@ -527,6 +583,9 @@ void main() {
await runner.run(<String>['dummy', '--initialize-from-dill=/foo/bar.dill']);
final BuildInfo buildInfo = await flutterCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
expect(buildInfo.initializeFromDill, '/foo/bar.dill');
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('dds options', () async {
......@@ -535,6 +594,9 @@ void main() {
await runner.run(<String>['test', '--dds-port=1']);
expect(ddsCommand.enableDds, isTrue);
expect(ddsCommand.ddsPort, 1);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('dds options --dds', () async {
......@@ -542,6 +604,9 @@ void main() {
final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
await runner.run(<String>['test', '--dds']);
expect(ddsCommand.enableDds, isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('dds options --no-dds', () async {
......@@ -549,6 +614,9 @@ void main() {
final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
await runner.run(<String>['test', '--no-dds']);
expect(ddsCommand.enableDds, isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('dds options --disable-dds', () async {
......@@ -556,6 +624,9 @@ void main() {
final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
await runner.run(<String>['test', '--disable-dds']);
expect(ddsCommand.enableDds, isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('dds options --no-disable-dds', () async {
......@@ -563,6 +634,9 @@ void main() {
final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
await runner.run(<String>['test', '--no-disable-dds']);
expect(ddsCommand.enableDds, isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('dds options --dds --disable-dds', () async {
......@@ -570,6 +644,9 @@ void main() {
final CommandRunner<void> runner = createTestCommandRunner(ddsCommand);
await runner.run(<String>['test', '--dds', '--disable-dds']);
expect(() => ddsCommand.enableDds, throwsToolExit());
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
});
}
......
......@@ -162,6 +162,7 @@ void test(String description, FutureOr<void> Function() body, {
addTearDown(() async {
await globals.localFileSystem.dispose();
});
return body();
},
skip: skip,
......
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