| // 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/config.dart'; |
| import 'package:flutter_tools/src/base/error_handling_io.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/process.dart'; |
| import 'package:flutter_tools/src/base/terminal.dart'; |
| import 'package:flutter_tools/src/base/time.dart'; |
| import 'package:flutter_tools/src/base/user_messages.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/fake_process_manager.dart'; |
| import '../../src/fakes.dart'; |
| import '../../src/package_config.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; |
| late BufferLogger logger; |
| |
| setUp(() { |
| fs = MemoryFileSystem.test(); |
| fs.file('pubspec.yaml').createSync(recursive: true); |
| logger = BufferLogger.test(); |
| |
| final Directory currentDirectory = fs.currentDirectory; |
| buildDirectory = currentDirectory.childDirectory('build'); |
| buildDirectory.createSync(recursive: true); |
| }); |
| |
| testWithoutContext( |
| '$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); |
| final CommandRunner<void> runner = createTestCommandRunner( |
| createCleanCommand( |
| fs: fs, |
| logger: logger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ), |
| ); |
| await runner.run(<String>['clean', '--include-xcode-workspace']); |
| |
| 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.flutterPluginsDependenciesFile, isNot(exists)); |
| expect( |
| projectUnderTest.directory |
| .childDirectory('.dart_tool') |
| .childFile('package_config.json'), |
| 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), |
| ]); |
| }, |
| ); |
| |
| testWithoutContext('$CleanCommand does not clean Xcode by default', () async { |
| final FlutterProject projectUnderTest = setupProjectUnderTest(fs.currentDirectory, true); |
| xcodeProjectInterpreter.isInstalled = true; |
| xcodeProjectInterpreter.version = Version(1000, 0, 0); |
| final CommandRunner<void> runner = createTestCommandRunner( |
| createCleanCommand( |
| fs: fs, |
| logger: logger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ), |
| ); |
| await runner.run(<String>['clean']); |
| |
| expect(buildDirectory, isNot(exists)); |
| expect(projectUnderTest.dartTool, isNot(exists)); |
| expect(projectUnderTest.android.ephemeralDirectory, isNot(exists)); |
| expect(projectUnderTest.ios.ephemeralDirectory, isNot(exists)); |
| |
| // The workspaces should be empty since we didn't pass --include-xcode-workspace. |
| expect(xcodeProjectInterpreter.workspaces, isEmpty); |
| }); |
| |
| testWithoutContext( |
| '$CleanCommand stops progress status for each deleted directory', |
| () async { |
| setupProjectUnderTest(fs.currentDirectory, false); |
| final stdio = FakeStdio(); |
| final stdoutLogger = StdoutLogger( |
| terminal: AnsiTerminal(stdio: stdio, platform: FakePlatform()), |
| stdio: stdio, |
| outputPreferences: OutputPreferences.test(), |
| ); |
| final CommandRunner<void> runner = createTestCommandRunner( |
| createCleanCommand( |
| fs: fs, |
| logger: stdoutLogger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ), |
| ); |
| await runner.run(<String>['clean']); |
| |
| final String output = stdio.writtenToStdout.join(); |
| expect(output, contains('Deleting build...')); |
| expect(output, contains('Deleting .dart_tool...')); |
| expect(output, contains('Deleting ephemeral...')); |
| expect(output.endsWith('\n'), isTrue); |
| }, |
| ); |
| |
| testWithoutContext('$CleanCommand does not clean the example directory by default', () async { |
| setupProjectUnderTest(fs.currentDirectory, true); |
| final FlutterProject exampleProject = setupProjectUnderTest( |
| fs.currentDirectory.childDirectory('example'), |
| true, |
| ); |
| final Directory exampleBuildDir = exampleProject.directory.childDirectory('build'); |
| exampleBuildDir.createSync(recursive: true); |
| |
| xcodeProjectInterpreter.isInstalled = true; |
| xcodeProjectInterpreter.version = Version(1000, 0, 0); |
| final CommandRunner<void> runner = createTestCommandRunner( |
| createCleanCommand( |
| fs: fs, |
| logger: logger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ), |
| ); |
| await runner.run(<String>['clean', '--include-xcode-workspace']); |
| |
| expect(buildDirectory, isNot(exists)); |
| |
| expect(exampleBuildDir, exists); |
| expect(exampleProject.dartTool, exists); |
| expect(exampleProject.android.ephemeralDirectory, exists); |
| expect(exampleProject.ios.ephemeralDirectory, exists); |
| expect(exampleProject.linux.ephemeralDirectory, exists); |
| expect(exampleProject.macos.ephemeralDirectory, exists); |
| expect(exampleProject.windows.ephemeralDirectory, exists); |
| expect(exampleProject.flutterPluginsDependenciesFile, 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), |
| ]); |
| }); |
| |
| testWithoutContext( |
| '$CleanCommand cleans the example directory with --include-example', |
| () async { |
| final FlutterProject projectUnderTest = setupProjectUnderTest(fs.currentDirectory, true); |
| final FlutterProject exampleProject = setupProjectUnderTest( |
| fs.currentDirectory.childDirectory('example'), |
| true, |
| ); |
| final Directory exampleBuildDir = exampleProject.directory.childDirectory('build'); |
| exampleBuildDir.createSync(recursive: true); |
| |
| xcodeProjectInterpreter.isInstalled = true; |
| xcodeProjectInterpreter.version = Version(1000, 0, 0); |
| |
| final CommandRunner<void> runner = createTestCommandRunner( |
| createCleanCommand( |
| fs: fs, |
| logger: logger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ), |
| ); |
| await runner.run(<String>['clean', '--include-example', '--include-xcode-workspace']); |
| |
| expect(buildDirectory, isNot(exists)); |
| expect(projectUnderTest.dartTool, isNot(exists)); |
| |
| expect(exampleBuildDir, isNot(exists)); |
| expect(exampleProject.dartTool, isNot(exists)); |
| expect(exampleProject.android.ephemeralDirectory, isNot(exists)); |
| |
| expect(exampleProject.ios.ephemeralDirectory, isNot(exists)); |
| expect(exampleProject.ios.ephemeralModuleDirectory, isNot(exists)); |
| expect(exampleProject.ios.generatedXcodePropertiesFile, isNot(exists)); |
| expect(exampleProject.ios.generatedEnvironmentVariableExportScript, isNot(exists)); |
| expect(exampleProject.ios.deprecatedCompiledDartFramework, isNot(exists)); |
| expect(exampleProject.ios.deprecatedProjectFlutterFramework, isNot(exists)); |
| expect(exampleProject.ios.flutterPodspec, isNot(exists)); |
| |
| expect(exampleProject.linux.ephemeralDirectory, isNot(exists)); |
| expect(exampleProject.macos.ephemeralDirectory, isNot(exists)); |
| expect(exampleProject.windows.ephemeralDirectory, isNot(exists)); |
| expect(exampleProject.flutterPluginsDependenciesFile, 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), |
| CleanWorkspaceCall('/example/ios/Runner.xcworkspace', 'Runner', false), |
| CleanWorkspaceCall('/example/ios/Runner.xcworkspace', 'custom-scheme', false), |
| CleanWorkspaceCall('/example/macos/Runner.xcworkspace', 'Runner', false), |
| CleanWorkspaceCall('/example/macos/Runner.xcworkspace', 'custom-scheme', false), |
| ]); |
| }, |
| ); |
| |
| testWithoutContext( |
| '$CleanCommand warns when --include-example is passed but no example exists', |
| () async { |
| setupProjectUnderTest(fs.currentDirectory, true); |
| // No example directory created. |
| |
| xcodeProjectInterpreter.isInstalled = true; |
| xcodeProjectInterpreter.version = Version(1000, 0, 0); |
| final CommandRunner<void> runner = createTestCommandRunner( |
| createCleanCommand( |
| fs: fs, |
| logger: logger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ), |
| ); |
| await runner.run(<String>['clean', '--include-example', '--include-xcode-workspace']); |
| |
| expect(logger.statusText, contains('No example app found')); |
| }, |
| ); |
| |
| testWithoutContext('$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 = createCleanCommand( |
| fs: fs, |
| logger: logger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ); |
| 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), |
| ]); |
| }); |
| |
| testWithoutContext('$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); |
| final CommandRunner<void> runner = createTestCommandRunner( |
| createCleanCommand( |
| fs: fs, |
| logger: logger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ), |
| ); |
| await runner.run(<String>['clean']); |
| |
| expect(xcodeProjectInterpreter.workspaces, const <CleanWorkspaceCall>[]); |
| }); |
| |
| testWithoutContext('$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 = createCleanCommand( |
| fs: fs, |
| logger: logger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ); |
| expect( |
| () => createTestCommandRunner(command).run(<String>['clean', '--scheme']), |
| throwsUsageException(), |
| ); |
| expect( |
| () => createTestCommandRunner(command).run(<String>['clean', '--scheme=unknown']), |
| throwsToolExit(), |
| ); |
| }); |
| |
| testWithoutContext('$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); |
| |
| final CleanCommand command = createCleanCommand( |
| fs: fs, |
| logger: logger, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| verbose: true, |
| ); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['clean', '--include-xcode-workspace']); |
| |
| 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), |
| ]); |
| }); |
| }); |
| |
| group('Windows', () { |
| late FakePlatform windowsPlatform; |
| late MemoryFileSystem fileSystem; |
| late FileExceptionHandler exceptionHandler; |
| late FakeProcessManager processManager; |
| late BufferLogger logger; |
| |
| setUp(() { |
| windowsPlatform = FakePlatform(operatingSystem: 'windows'); |
| exceptionHandler = FileExceptionHandler(); |
| fileSystem = MemoryFileSystem.test(opHandle: exceptionHandler.opHandle); |
| fileSystem.file('pubspec.yaml').createSync(recursive: true); |
| processManager = FakeProcessManager.any(); |
| logger = BufferLogger.test(); |
| }); |
| |
| testWithoutContext('$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 = createCleanCommand( |
| fs: fileSystem, |
| logger: logger, |
| platform: windowsPlatform, |
| processManager: processManager, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ); |
| await command.deleteFile(file); |
| expect( |
| logger.errorText, |
| contains('A background process (e.g. Gradle daemon or Java) is locking files'), |
| ); |
| }); |
| |
| testWithoutContext('$CleanCommand handles missing delete permissions', () async { |
| final handler = FileExceptionHandler(); |
| |
| // Ensures we handle ErrorHandlingFileSystem appropriately in prod. |
| // See https://github.com/flutter/flutter/issues/108978. |
| final FileSystem fs = ErrorHandlingFileSystem( |
| delegate: MemoryFileSystem.test(opHandle: handler.opHandle), |
| platform: windowsPlatform, |
| ); |
| final File throwingFile = fs.file('bad')..createSync(); |
| handler.addError( |
| throwingFile, |
| FileSystemOp.delete, |
| const FileSystemException('OS error: Access Denied'), |
| ); |
| |
| xcodeProjectInterpreter.isInstalled = false; |
| |
| final CleanCommand command = createCleanCommand( |
| fs: fs, |
| logger: logger, |
| platform: windowsPlatform, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ); |
| await command.deleteFile(throwingFile); |
| |
| expect( |
| logger.errorText, |
| contains( |
| 'Failed to remove bad. A background process (e.g. Gradle daemon or Java) is locking files', |
| ), |
| ); |
| expect(throwingFile, exists); |
| }); |
| |
| testWithoutContext( |
| '$CleanCommand invokes gradlew --stop and retries deletion when --stop-gradle flag is passed', |
| () async { |
| xcodeProjectInterpreter.isInstalled = false; |
| |
| var shouldThrow = true; |
| fileSystem = MemoryFileSystem.test( |
| opHandle: (String path, FileSystemOp op) { |
| if (shouldThrow && op == FileSystemOp.delete && path.endsWith('build')) { |
| throw const FileSystemException('Locked'); |
| } |
| }, |
| ); |
| fileSystem.file('pubspec.yaml').createSync(recursive: true); |
| |
| final FlutterProject project = setupProjectUnderTest(fileSystem.currentDirectory, false); |
| final File gradlewFile = project.android.hostAppGradleRoot.childFile('gradlew.bat') |
| ..createSync(recursive: true); |
| |
| final Directory buildDir = project.directory.childDirectory('build') |
| ..createSync(recursive: true); |
| buildDir.childFile('locked').createSync(recursive: true); |
| |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand( |
| command: <String>[gradlewFile.path, '--stop'], |
| workingDirectory: gradlewFile.parent.path, |
| onRun: (_) { |
| shouldThrow = false; |
| }, |
| ), |
| ]); |
| |
| final CleanCommand command = createCleanCommand( |
| fs: fileSystem, |
| logger: logger, |
| platform: windowsPlatform, |
| processManager: processManager, |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['clean', '--stop-gradle']); |
| |
| expect(logger.statusText, contains('Stopping Gradle daemons')); |
| }, |
| ); |
| |
| testWithoutContext( |
| '$CleanCommand prompts user and invokes gradlew --stop when locked on Windows interactively', |
| () async { |
| xcodeProjectInterpreter.isInstalled = false; |
| |
| var shouldThrow = true; |
| fileSystem = MemoryFileSystem.test( |
| opHandle: (String path, FileSystemOp op) { |
| if (shouldThrow && op == FileSystemOp.delete && path.endsWith('build')) { |
| throw const FileSystemException('Locked'); |
| } |
| }, |
| ); |
| fileSystem.file('pubspec.yaml').createSync(recursive: true); |
| |
| final FlutterProject project = setupProjectUnderTest(fileSystem.currentDirectory, false); |
| final File gradlewFile = project.android.hostAppGradleRoot.childFile('gradlew.bat') |
| ..createSync(recursive: true); |
| |
| final Directory buildDir = project.directory.childDirectory('build') |
| ..createSync(recursive: true); |
| buildDir.childFile('locked').createSync(recursive: true); |
| |
| processManager = FakeProcessManager.list(<FakeCommand>[ |
| FakeCommand( |
| command: <String>[gradlewFile.path, '--stop'], |
| workingDirectory: gradlewFile.parent.path, |
| onRun: (_) { |
| shouldThrow = false; |
| }, |
| ), |
| ]); |
| |
| final CleanCommand command = createCleanCommand( |
| fs: fileSystem, |
| logger: logger, |
| platform: windowsPlatform, |
| processManager: processManager, |
| terminal: FakeTerminal(), |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['clean']); |
| |
| expect(logger.statusText, contains('Stopping Gradle daemons')); |
| }, |
| ); |
| |
| testWithoutContext( |
| '$CleanCommand prompts user but skips gradlew --stop when user declines prompt', |
| () async { |
| xcodeProjectInterpreter.isInstalled = false; |
| |
| fileSystem = MemoryFileSystem.test( |
| opHandle: (String path, FileSystemOp op) { |
| if (op == FileSystemOp.delete && path.endsWith('build')) { |
| throw const FileSystemException('Locked'); |
| } |
| }, |
| ); |
| fileSystem.file('pubspec.yaml').createSync(recursive: true); |
| |
| final FlutterProject project = setupProjectUnderTest(fileSystem.currentDirectory, false); |
| project.android.hostAppGradleRoot.childFile('gradlew.bat').createSync(recursive: true); |
| |
| final Directory buildDir = project.directory.childDirectory('build') |
| ..createSync(recursive: true); |
| buildDir.childFile('locked').createSync(recursive: true); |
| |
| processManager = FakeProcessManager.empty(); |
| |
| final CleanCommand command = createCleanCommand( |
| fs: fileSystem, |
| logger: logger, |
| platform: windowsPlatform, |
| processManager: processManager, |
| terminal: FakeTerminal(response: 'n'), |
| xcode: xcode, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>['clean']); |
| |
| expect(logger.statusText, isNot(contains('Stopping Gradle daemons'))); |
| expect( |
| logger.errorText, |
| contains('A background process (e.g. Gradle daemon or Java) is locking files'), |
| ); |
| }, |
| ); |
| }); |
| }); |
| } |
| |
| class FakeTerminal extends Fake implements AnsiTerminal { |
| FakeTerminal({this.response = 'y'}); |
| |
| final String response; |
| |
| @override |
| bool get stdinHasTerminal => true; |
| |
| @override |
| bool get usesTerminalUi => true; |
| |
| @override |
| Future<String> promptForCharInput( |
| List<String> acceptedCharacters, { |
| Logger? logger, |
| String? prompt, |
| int? defaultChoiceIndex, |
| bool displayAcceptedCharacters = true, |
| }) async { |
| return response; |
| } |
| } |
| |
| FlutterProject setupProjectUnderTest(Directory currentDirectory, bool setupXcodeWorkspace) { |
| // This needs to be run within testWithoutContext and not setUp since FlutterProject uses context. |
| final FlutterProject projectUnderTest = FlutterProject.fromDirectoryTest(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); |
| writePackageConfigFiles(directory: projectUnderTest.directory, mainLibName: 'my_app'); |
| |
| 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.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( |
| XcodeBasedProject xcodeProject, { |
| String? projectFilename, |
| required Directory buildDirectory, |
| }) async { |
| return XcodeProjectInfo(const <String>[], const <String>[], <String>[ |
| 'Runner', |
| 'custom-scheme', |
| ], BufferLogger.test()); |
| } |
| |
| final workspaces = <CleanWorkspaceCall>[]; |
| |
| @override |
| Future<void> cleanWorkspace( |
| XcodeBasedProject xcodeProject, |
| String workspacePath, |
| String scheme, { |
| required Directory buildDirectory, |
| 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}'; |
| } |
| |
| CleanCommand createCleanCommand({ |
| FileSystem? fs, |
| Logger? logger, |
| Platform? platform, |
| ProcessManager? processManager, |
| ProcessUtils? processUtils, |
| SystemClock? systemClock, |
| AnsiTerminal? terminal, |
| UserMessages? userMessages, |
| Config? config, |
| Xcode? xcode, |
| XcodeProjectInterpreter? xcodeProjectInterpreter, |
| bool verbose = false, |
| }) { |
| final FileSystem resolvedFs = fs ?? MemoryFileSystem.test(); |
| final Platform resolvedPlatform = platform ?? FakePlatform(); |
| final ProcessManager resolvedProcessManager = processManager ?? FakeProcessManager.any(); |
| final Logger resolvedLogger = logger ?? BufferLogger.test(); |
| return CleanCommand( |
| toolContext: FakeToolContext( |
| fs: resolvedFs, |
| logger: resolvedLogger, |
| platform: resolvedPlatform, |
| processManager: resolvedProcessManager, |
| processUtils: |
| processUtils ?? |
| ProcessUtils(processManager: resolvedProcessManager, logger: resolvedLogger), |
| systemClock: systemClock ?? const SystemClock(), |
| terminal: terminal ?? AnsiTerminal(stdio: FakeStdio(), platform: resolvedPlatform), |
| userMessages: userMessages ?? UserMessages(), |
| config: config ?? Config.test(directory: resolvedFs.directory('/')), |
| stdio: FakeStdio(), |
| ), |
| verbose: verbose, |
| xcode: xcode ?? Xcode.test(processManager: resolvedProcessManager), |
| xcodeProjectInterpreter: xcodeProjectInterpreter ?? FakeXcodeProjectInterpreter(), |
| ); |
| } |