mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
302 lines
13 KiB
Dart
302 lines
13 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.
|
|
|
|
import 'package:args/command_runner.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:file_testing/file_testing.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/base/version.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/commands/clean.dart';
|
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
|
import 'package:flutter_tools/src/macos/xcode.dart';
|
|
import 'package:flutter_tools/src/project.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:test/fake.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
import '../../src/test_flutter_command_runner.dart';
|
|
|
|
void main() {
|
|
group('clean command', () {
|
|
late Xcode xcode;
|
|
late FakeXcodeProjectInterpreter xcodeProjectInterpreter;
|
|
|
|
setUp(() {
|
|
Cache.disableLocking();
|
|
xcodeProjectInterpreter = FakeXcodeProjectInterpreter();
|
|
xcode = Xcode.test(
|
|
processManager: FakeProcessManager.any(),
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
);
|
|
});
|
|
|
|
group('general', () {
|
|
late MemoryFileSystem fs;
|
|
late Directory buildDirectory;
|
|
|
|
setUp(() {
|
|
fs = MemoryFileSystem.test();
|
|
fs.file('pubspec.yaml').createSync(recursive: true);
|
|
|
|
final Directory currentDirectory = fs.currentDirectory;
|
|
buildDirectory = currentDirectory.childDirectory('build');
|
|
buildDirectory.createSync(recursive: true);
|
|
});
|
|
|
|
testUsingContext('$CleanCommand removes build and .dart_tool and ephemeral directories, cleans Xcode for iOS and macOS', () async {
|
|
final FlutterProject projectUnderTest = setupProjectUnderTest(fs.currentDirectory, true);
|
|
// Xcode is installed and version satisfactory.
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(1000, 0, 0);
|
|
await CleanCommand().runCommand();
|
|
|
|
expect(buildDirectory, isNot(exists));
|
|
expect(projectUnderTest.dartTool, isNot(exists));
|
|
expect(projectUnderTest.android.ephemeralDirectory, isNot(exists));
|
|
|
|
expect(projectUnderTest.ios.ephemeralDirectory, isNot(exists));
|
|
expect(projectUnderTest.ios.ephemeralModuleDirectory, isNot(exists));
|
|
expect(projectUnderTest.ios.generatedXcodePropertiesFile, isNot(exists));
|
|
expect(projectUnderTest.ios.generatedEnvironmentVariableExportScript, isNot(exists));
|
|
expect(projectUnderTest.ios.deprecatedCompiledDartFramework, isNot(exists));
|
|
expect(projectUnderTest.ios.deprecatedProjectFlutterFramework, isNot(exists));
|
|
expect(projectUnderTest.ios.flutterPodspec, isNot(exists));
|
|
expect(projectUnderTest.ios.flutterPluginSwiftPackageDirectory, isNot(exists));
|
|
|
|
expect(projectUnderTest.linux.ephemeralDirectory, isNot(exists));
|
|
expect(projectUnderTest.macos.ephemeralDirectory, isNot(exists));
|
|
expect(projectUnderTest.macos.flutterPluginSwiftPackageDirectory, isNot(exists));
|
|
expect(projectUnderTest.windows.ephemeralDirectory, isNot(exists));
|
|
|
|
expect(projectUnderTest.flutterPluginsFile, isNot(exists));
|
|
expect(projectUnderTest.flutterPluginsDependenciesFile, isNot(exists));
|
|
expect(projectUnderTest.packagesFile, isNot(exists));
|
|
|
|
expect(xcodeProjectInterpreter.workspaces, const <CleanWorkspaceCall>[
|
|
CleanWorkspaceCall('/ios/Runner.xcworkspace', 'Runner', false),
|
|
CleanWorkspaceCall('/ios/Runner.xcworkspace', 'custom-scheme', false),
|
|
CleanWorkspaceCall('/macos/Runner.xcworkspace', 'Runner', false),
|
|
CleanWorkspaceCall('/macos/Runner.xcworkspace', 'custom-scheme', false),
|
|
]);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Xcode: () => xcode,
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
});
|
|
|
|
testUsingContext('$CleanCommand removes a specific xcode scheme --scheme', () async {
|
|
setupProjectUnderTest(fs.currentDirectory, true);
|
|
// Xcode is installed and version satisfactory.
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(1000, 0, 0);
|
|
|
|
final CleanCommand command = CleanCommand();
|
|
final CommandRunner<void> runner = createTestCommandRunner(command);
|
|
await runner.run(<String>['clean', '--scheme=custom-scheme']);
|
|
|
|
expect(xcodeProjectInterpreter.workspaces, <CleanWorkspaceCall>[
|
|
const CleanWorkspaceCall('/ios/Runner.xcworkspace', 'custom-scheme', false),
|
|
const CleanWorkspaceCall('/macos/Runner.xcworkspace', 'custom-scheme', false),
|
|
]);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Xcode: () => xcode,
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
});
|
|
|
|
testUsingContext('$CleanCommand does not run when there is no xcworkspace', () async {
|
|
setupProjectUnderTest(fs.currentDirectory, false);
|
|
// Xcode is installed and version satisfactory.
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(1000, 0, 0);
|
|
await CleanCommand().runCommand();
|
|
|
|
expect(xcodeProjectInterpreter.workspaces, const <CleanWorkspaceCall>[]);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Xcode: () => xcode,
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
});
|
|
|
|
testUsingContext('$CleanCommand throws when given an invalid value for --scheme', () async {
|
|
setupProjectUnderTest(fs.currentDirectory, true);
|
|
// Xcode is installed and version satisfactory.
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(1000, 0, 0);
|
|
|
|
final CleanCommand command = CleanCommand();
|
|
expect(
|
|
() => createTestCommandRunner(command).run(<String>['clean', '--scheme']),
|
|
throwsUsageException(),
|
|
);
|
|
expect(
|
|
() => createTestCommandRunner(command).run(<String>['clean', '--scheme=unknown']),
|
|
throwsToolExit(),
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Xcode: () => xcode,
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
});
|
|
|
|
testUsingContext('$CleanCommand cleans Xcode verbosely for iOS and macOS', () async {
|
|
setupProjectUnderTest(fs.currentDirectory, true);
|
|
// Xcode is installed and version satisfactory.
|
|
xcodeProjectInterpreter.isInstalled = true;
|
|
xcodeProjectInterpreter.version = Version(1000, 0, 0);
|
|
|
|
await CleanCommand(verbose: true).runCommand();
|
|
|
|
expect(xcodeProjectInterpreter.workspaces, const <CleanWorkspaceCall>[
|
|
CleanWorkspaceCall('/ios/Runner.xcworkspace', 'Runner', true),
|
|
CleanWorkspaceCall('/ios/Runner.xcworkspace', 'custom-scheme', true),
|
|
CleanWorkspaceCall('/macos/Runner.xcworkspace', 'Runner', true),
|
|
CleanWorkspaceCall('/macos/Runner.xcworkspace', 'custom-scheme', true),
|
|
]);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Xcode: () => xcode,
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
});
|
|
});
|
|
|
|
group('Windows', () {
|
|
late FakePlatform windowsPlatform;
|
|
late MemoryFileSystem fileSystem;
|
|
late FileExceptionHandler exceptionHandler;
|
|
|
|
setUp(() {
|
|
windowsPlatform = FakePlatform(operatingSystem: 'windows');
|
|
exceptionHandler = FileExceptionHandler();
|
|
fileSystem = MemoryFileSystem.test(opHandle: exceptionHandler.opHandle);
|
|
});
|
|
|
|
testUsingContext('$CleanCommand prints a helpful error message on Windows', () async {
|
|
xcodeProjectInterpreter.isInstalled = false;
|
|
|
|
final File file = fileSystem.file('file')..createSync();
|
|
exceptionHandler.addError(
|
|
file,
|
|
FileSystemOp.delete,
|
|
const FileSystemException('Deletion failed'),
|
|
);
|
|
|
|
final CleanCommand command = CleanCommand();
|
|
command.deleteFile(file);
|
|
expect(testLogger.errorText, contains('A program may still be using a file'));
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => windowsPlatform,
|
|
Xcode: () => xcode,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
|
|
testUsingContext('$CleanCommand handles missing delete permissions', () async {
|
|
final FileExceptionHandler handler = FileExceptionHandler();
|
|
final FileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
|
|
final File throwingFile = fileSystem.file('bad')
|
|
..createSync();
|
|
handler.addError(throwingFile, FileSystemOp.delete, const FileSystemException('OS error: Access Denied'));
|
|
|
|
xcodeProjectInterpreter.isInstalled = false;
|
|
|
|
final CleanCommand command = CleanCommand();
|
|
command.deleteFile(throwingFile);
|
|
|
|
expect(testLogger.errorText, contains('Failed to remove bad. A program may still be using a file in the directory or the directory itself'));
|
|
expect(throwingFile, exists);
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => windowsPlatform,
|
|
Xcode: () => xcode,
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
FlutterProject setupProjectUnderTest(Directory currentDirectory, bool setupXcodeWorkspace) {
|
|
// This needs to be run within testWithoutContext and not setUp since FlutterProject uses context.
|
|
final FlutterProject projectUnderTest = FlutterProject.fromDirectory(currentDirectory);
|
|
if (setupXcodeWorkspace) {
|
|
projectUnderTest.ios.hostAppRoot.childDirectory('Runner.xcworkspace').createSync(recursive: true);
|
|
projectUnderTest.macos.hostAppRoot.childDirectory('Runner.xcworkspace').createSync(recursive: true);
|
|
}
|
|
projectUnderTest.dartTool.createSync(recursive: true);
|
|
projectUnderTest.packagesFile.createSync(recursive: true);
|
|
projectUnderTest.android.ephemeralDirectory.createSync(recursive: true);
|
|
|
|
projectUnderTest.ios.ephemeralDirectory.createSync(recursive: true);
|
|
projectUnderTest.ios.ephemeralModuleDirectory.createSync(recursive: true);
|
|
projectUnderTest.ios.generatedXcodePropertiesFile.createSync(recursive: true);
|
|
projectUnderTest.ios.generatedEnvironmentVariableExportScript.createSync(recursive: true);
|
|
projectUnderTest.ios.deprecatedCompiledDartFramework.createSync(recursive: true);
|
|
projectUnderTest.ios.deprecatedProjectFlutterFramework.createSync(recursive: true);
|
|
projectUnderTest.ios.flutterPodspec.createSync(recursive: true);
|
|
projectUnderTest.ios.flutterPluginSwiftPackageDirectory.createSync(recursive: true);
|
|
|
|
projectUnderTest.linux.ephemeralDirectory.createSync(recursive: true);
|
|
projectUnderTest.macos.ephemeralDirectory.createSync(recursive: true);
|
|
projectUnderTest.macos.flutterPluginSwiftPackageDirectory.createSync(recursive: true);
|
|
projectUnderTest.windows.ephemeralDirectory.createSync(recursive: true);
|
|
projectUnderTest.flutterPluginsFile.createSync(recursive: true);
|
|
projectUnderTest.flutterPluginsDependenciesFile.createSync(recursive: true);
|
|
|
|
return projectUnderTest;
|
|
}
|
|
|
|
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
|
@override
|
|
bool isInstalled = true;
|
|
|
|
@override
|
|
Version version = Version(0, 0, 0);
|
|
|
|
@override
|
|
Future<XcodeProjectInfo> getInfo(String projectPath, {String? projectFilename}) async {
|
|
return XcodeProjectInfo(
|
|
const <String>[],
|
|
const <String>[],
|
|
<String>['Runner', 'custom-scheme'],
|
|
BufferLogger.test(),
|
|
);
|
|
}
|
|
|
|
final List<CleanWorkspaceCall> workspaces = <CleanWorkspaceCall>[];
|
|
|
|
@override
|
|
Future<void> cleanWorkspace(String workspacePath, String scheme, {bool verbose = false}) async {
|
|
workspaces.add(CleanWorkspaceCall(workspacePath, scheme, verbose));
|
|
return;
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
class CleanWorkspaceCall {
|
|
const CleanWorkspaceCall(this.workspacePath, this.scheme, this.verbose);
|
|
|
|
final String workspacePath;
|
|
final String scheme;
|
|
final bool verbose;
|
|
|
|
@override
|
|
bool operator ==(Object other) => other is CleanWorkspaceCall &&
|
|
workspacePath == other.workspacePath &&
|
|
scheme == other.scheme &&
|
|
verbose == other.verbose;
|
|
|
|
@override
|
|
int get hashCode => Object.hash(workspacePath, scheme, verbose);
|
|
|
|
@override
|
|
String toString() => '{$workspacePath, $scheme, $verbose}';
|
|
}
|