clean_test.dart 8.99 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7
import 'package:file/memory.dart';
8
import 'package:file_testing/file_testing.dart';
9
import 'package:flutter_tools/src/base/file_system.dart';
10
import 'package:flutter_tools/src/base/logger.dart';
11
import 'package:flutter_tools/src/base/platform.dart';
12
import 'package:flutter_tools/src/base/version.dart';
13
import 'package:flutter_tools/src/commands/clean.dart';
14 15 16
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/macos/xcode.dart';
import 'package:flutter_tools/src/project.dart';
17 18
import 'package:meta/meta.dart';
import 'package:test/fake.dart';
19

20 21
import '../../src/common.dart';
import '../../src/context.dart';
22 23

void main() {
24
  group('clean command', () {
25
    Xcode xcode;
26
    FakeXcodeProjectInterpreter xcodeProjectInterpreter;
27

28
    setUp(() {
29
      xcodeProjectInterpreter = FakeXcodeProjectInterpreter();
30 31
      xcode = Xcode.test(
        processManager: FakeProcessManager.any(),
32
        xcodeProjectInterpreter: xcodeProjectInterpreter,
33
      );
34 35
    });

36 37 38 39
    group('general', () {
      MemoryFileSystem fs;
      Directory buildDirectory;

40 41 42 43 44 45
      setUp(() {
        fs = MemoryFileSystem.test();

        final Directory currentDirectory = fs.currentDirectory;
        buildDirectory = currentDirectory.childDirectory('build');
        buildDirectory.createSync(recursive: true);
46 47
      });

48
      testUsingContext('$CleanCommand removes build and .dart_tool and ephemeral directories, cleans Xcode for iOS and macOS', () async {
49
        final FlutterProject projectUnderTest = setupProjectUnderTest(fs.currentDirectory);
50
        // Xcode is installed and version satisfactory.
51 52
        xcodeProjectInterpreter.isInstalled = true;
        xcodeProjectInterpreter.version = Version(1000, 0, 0);
53 54
        await CleanCommand().runCommand();

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
        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.linux.ephemeralDirectory, isNot(exists));
        expect(projectUnderTest.macos.ephemeralDirectory, 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('/macos/Runner.xcworkspace', 'Runner', false),
        ]);
79 80 81
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
82
        Xcode: () => xcode,
83
        XcodeProjectInterpreter: () => xcodeProjectInterpreter,
84 85
      });

86
      testUsingContext('$CleanCommand cleans Xcode verbosely for iOS and macOS', () async {
87
        setupProjectUnderTest(fs.currentDirectory);
88
        // Xcode is installed and version satisfactory.
89 90
        xcodeProjectInterpreter.isInstalled = true;
        xcodeProjectInterpreter.version = Version(1000, 0, 0);
91

92
        await CleanCommand(verbose: true).runCommand();
93 94 95 96 97

        expect(xcodeProjectInterpreter.workspaces, const <CleanWorkspaceCall>[
          CleanWorkspaceCall('/ios/Runner.xcworkspace', 'Runner', true),
          CleanWorkspaceCall('/macos/Runner.xcworkspace', 'Runner', true),
        ]);
98 99 100
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
101
        Xcode: () => xcode,
102
        XcodeProjectInterpreter: () => xcodeProjectInterpreter,
103
      });
104
    });
105

106
    group('Windows', () {
107
      FakePlatform windowsPlatform;
108 109
      MemoryFileSystem fileSystem;
      FileExceptionHandler exceptionHandler;
110

111
      setUp(() {
112
        windowsPlatform = FakePlatform(operatingSystem: 'windows');
113 114
        exceptionHandler = FileExceptionHandler();
        fileSystem = MemoryFileSystem.test(opHandle: exceptionHandler.opHandle);
115 116 117
      });

      testUsingContext('$CleanCommand prints a helpful error message on Windows', () async {
118
        xcodeProjectInterpreter.isInstalled = false;
119

120 121 122 123 124 125
        final File file = fileSystem.file('file')..createSync();
        exceptionHandler.addError(
          file,
          FileSystemOp.delete,
          const FileSystemException('Deletion failed'),
        );
126 127

        final CleanCommand command = CleanCommand();
128
        command.deleteFile(file);
129 130 131
        expect(testLogger.errorText, contains('A program may still be using a file'));
      }, overrides: <Type, Generator>{
        Platform: () => windowsPlatform,
132
        Xcode: () => xcode,
133 134
        FileSystem: () => fileSystem,
        ProcessManager: () => FakeProcessManager.any(),
135 136
      });

137 138 139 140 141 142
      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'));
143

144
        xcodeProjectInterpreter.isInstalled = false;
145 146

        final CleanCommand command = CleanCommand();
147 148 149 150
        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);
151 152
      }, overrides: <Type, Generator>{
        Platform: () => windowsPlatform,
153
        Xcode: () => xcode,
154
      });
155
    });
156
  });
157 158
}

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
FlutterProject setupProjectUnderTest(Directory currentDirectory) {
  // This needs to be run within testWithoutContext and not setUp since FlutterProject uses context.
  final FlutterProject projectUnderTest = FlutterProject.fromDirectory(currentDirectory);
  projectUnderTest.ios.xcodeWorkspace.createSync(recursive: true);
  projectUnderTest.macos.xcodeWorkspace.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.linux.ephemeralDirectory.createSync(recursive: true);
  projectUnderTest.macos.ephemeralDirectory.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);
192 193

  @override
194
  Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async {
195
    return XcodeProjectInfo(null, null, <String>['Runner'], BufferLogger.test());
196
  }
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221

  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
222
  int get hashCode => Object.hash(workspacePath, scheme, verbose);
223 224 225

  @override
  String toString() => '{$workspacePath, $scheme, $verbose}';
226
}