flutter_command_runner_test.dart 13.3 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
import 'package:file/memory.dart';
6
import 'package:flutter_tools/src/base/context.dart';
7
import 'package:flutter_tools/src/base/file_system.dart';
8 9
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/terminal.dart';
10
import 'package:flutter_tools/src/cache.dart';
11
import 'package:flutter_tools/src/globals.dart' as globals;
12 13
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
14
import 'package:flutter_tools/src/version.dart';
15
import 'package:mockito/mockito.dart';
16
import 'package:platform/platform.dart';
17
import 'package:process/process.dart';
18

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

23 24
const String _kFlutterRoot = '/flutter/flutter';
const String _kEngineRoot = '/flutter/engine';
25
const String _kArbitraryEngineRoot = '/arbitrary/engine';
26 27 28
const String _kProjectRoot = '/project';
const String _kDotPackages = '.packages';

29 30
void main() {
  group('FlutterCommandRunner', () {
31 32 33
    MemoryFileSystem fs;
    Platform platform;
    FlutterCommandRunner runner;
34
    ProcessManager processManager;
35 36 37

    setUpAll(() {
      Cache.disableLocking();
38
      Cache.flutterRoot = FlutterCommandRunner.defaultFlutterRoot;
39 40 41
    });

    setUp(() {
42
      fs = MemoryFileSystem();
43 44 45 46
      fs.directory(_kFlutterRoot).createSync(recursive: true);
      fs.directory(_kProjectRoot).createSync(recursive: true);
      fs.currentDirectory = _kProjectRoot;

47 48 49 50 51 52
      platform = FakePlatform(
        environment: <String, String>{
          'FLUTTER_ROOT': _kFlutterRoot,
        },
        version: '1 2 3 4 5',
      );
53

54
      runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner;
55
      processManager = MockProcessManager();
56 57 58 59
    });

    group('run', () {
      testUsingContext('checks that Flutter installation is up-to-date', () async {
60
        final MockFlutterVersion version = globals.flutterVersion as MockFlutterVersion;
61 62 63 64
        bool versionChecked = false;
        when(version.checkFlutterVersionFreshness()).thenAnswer((_) async {
          versionChecked = true;
        });
65

66 67 68 69 70
        await runner.run(<String>['dummy']);

        expect(versionChecked, isTrue);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
71
        ProcessManager: () => FakeProcessManager.any(),
72 73 74
        Platform: () => platform,
      }, initializeFlutterRoot: false);

75
      testUsingContext('does not check that Flutter installation is up-to-date with --machine flag', () async {
76
        final MockFlutterVersion version = globals.flutterVersion as MockFlutterVersion;
77 78 79 80 81 82 83 84 85 86 87 88 89 90
        bool versionChecked = false;
        when(version.checkFlutterVersionFreshness()).thenAnswer((_) async {
          versionChecked = true;
        });

        await runner.run(<String>['dummy', '--machine', '--version']);

        expect(versionChecked, isFalse);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
        Platform: () => platform,
      }, initializeFlutterRoot: false);

91 92 93 94 95 96 97 98 99 100 101 102
      testUsingContext('Fetches tags when --version is used', () async {
        final MockFlutterVersion version = globals.flutterVersion as MockFlutterVersion;

        await runner.run(<String>['--version']);

        verify(version.fetchTagsAndUpdate()).called(1);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
        Platform: () => platform,
      }, initializeFlutterRoot: false);

103
      testUsingContext('throw tool exit if the version file cannot be written', () async {
104
        final MockFlutterVersion version = globals.flutterVersion as MockFlutterVersion;
105 106
        when(version.ensureVersionFile()).thenThrow(const FileSystemException());

Dan Field's avatar
Dan Field committed
107
        expect(() async => await runner.run(<String>['dummy']), throwsToolExit());
108 109 110

      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
111
        ProcessManager: () => FakeProcessManager.any(),
112 113 114
        Platform: () => platform,
      }, initializeFlutterRoot: false);

115 116 117 118 119
      testUsingContext('works if --local-engine is specified and --local-engine-src-path is determined by sky_engine', () async {
        fs.directory('$_kArbitraryEngineRoot/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/').createSync(recursive: true);
        fs.directory('$_kArbitraryEngineRoot/src/out/host_debug').createSync(recursive: true);
        fs.file(_kDotPackages).writeAsStringSync('sky_engine:file://$_kArbitraryEngineRoot/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/');
        await runner.run(<String>['dummy', '--local-engine=ios_debug']);
120 121

        // Verify that this also works if the sky_engine path is a symlink to the engine root.
122
        fs.link('/symlink').createSync(_kArbitraryEngineRoot);
123 124
        fs.file(_kDotPackages).writeAsStringSync('sky_engine:file:///symlink/src/out/ios_debug/gen/dart-pkg/sky_engine/lib/');
        await runner.run(<String>['dummy', '--local-engine=ios_debug']);
125 126
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
127
        ProcessManager: () => FakeProcessManager.any(),
128 129 130 131 132 133 134 135 136
        Platform: () => platform,
      }, initializeFlutterRoot: false);

      testUsingContext('works if --local-engine is specified and --local-engine-src-path is specified', () async {
        fs.directory('$_kArbitraryEngineRoot/src/out/ios_debug').createSync(recursive: true);
        fs.directory('$_kArbitraryEngineRoot/src/out/host_debug').createSync(recursive: true);
        await runner.run(<String>['dummy', '--local-engine-src-path=$_kArbitraryEngineRoot/src', '--local-engine=ios_debug']);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
137
        ProcessManager: () => FakeProcessManager.any(),
138 139 140 141
        Platform: () => platform,
      }, initializeFlutterRoot: false);

      testUsingContext('works if --local-engine is specified and --local-engine-src-path is determined by flutter root', () async {
142 143 144 145 146
        fs.directory('$_kEngineRoot/src/out/ios_debug').createSync(recursive: true);
        fs.directory('$_kEngineRoot/src/out/host_debug').createSync(recursive: true);
        await runner.run(<String>['dummy', '--local-engine=ios_debug']);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
147
        ProcessManager: () => FakeProcessManager.any(),
148 149
        Platform: () => platform,
      }, initializeFlutterRoot: false);
150
    });
151

152 153 154 155 156 157 158 159 160 161
    testUsingContext('Doesnt crash on invalid .packages file', () async {
      fs.file('pubspec.yaml').createSync();
      fs.file('.packages')
        ..createSync()
        ..writeAsStringSync('Not a valid package');

      await runner.run(<String>['dummy']);

    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
162
      ProcessManager: () => FakeProcessManager.any(),
163 164 165
      Platform: () => platform,
    }, initializeFlutterRoot: false);

166 167 168 169
    group('version', () {
      testUsingContext('checks that Flutter toJson output reports the flutter framework version', () async {
        final ProcessResult result = ProcessResult(0, 0, 'random', '0');

170
        when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%H'.split(' ')),
171 172 173 174 175 176 177
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
        when(processManager.runSync('git rev-parse --abbrev-ref --symbolic @{u}'.split(' '),
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
        when(processManager.runSync('git rev-parse --abbrev-ref HEAD'.split(' '),
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
        when(processManager.runSync('git ls-remote --get-url master'.split(' '),
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
178
        when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%ar'.split(' ')),
179
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
180 181
        when(processManager.runSync('git fetch https://github.com/flutter/flutter.git --tags'.split(' '),
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
182 183
        when(processManager.runSync('git describe --match v*.*.* --first-parent --long --tags'.split(' '),
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
184
        when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%ad --date=iso'.split(' ')),
185 186 187 188 189 190 191 192 193
          workingDirectory: Cache.flutterRoot)).thenReturn(result);

        final FakeFlutterVersion version = FakeFlutterVersion();

        // Because the hash depends on the time, we just use the 0.0.0-unknown here.
        expect(version.toJson()['frameworkVersion'], '0.10.3');
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => processManager,
194
        Platform: () => platform,
195 196 197
      }, initializeFlutterRoot: false);
    });

198
    group('getRepoPackages', () {
Dan Field's avatar
Dan Field committed
199 200
      String oldFlutterRoot;

201
      setUp(() {
Dan Field's avatar
Dan Field committed
202 203
        oldFlutterRoot = Cache.flutterRoot;
        Cache.flutterRoot = _kFlutterRoot;
204 205 206 207 208 209
        fs.directory(fs.path.join(_kFlutterRoot, 'examples'))
            .createSync(recursive: true);
        fs.directory(fs.path.join(_kFlutterRoot, 'packages'))
            .createSync(recursive: true);
        fs.directory(fs.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool'))
            .createSync(recursive: true);
210

211 212 213 214 215
        fs.file(fs.path.join(_kFlutterRoot, 'dev', 'tools', 'pubspec.yaml'))
            .createSync();
        fs.file(fs.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool', 'pubspec.yaml'))
            .createSync();
      });
216

Dan Field's avatar
Dan Field committed
217 218 219 220
      tearDown(() {
        Cache.flutterRoot = oldFlutterRoot;
      });

221 222 223 224 225 226 227 228 229
      testUsingContext('', () {
        final List<String> packagePaths = runner.getRepoPackages()
            .map((Directory d) => d.path).toList();
        expect(packagePaths, <String>[
          fs.directory(fs.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool')).path,
          fs.directory(fs.path.join(_kFlutterRoot, 'dev', 'tools')).path,
        ]);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
230
        ProcessManager: () => FakeProcessManager.any(),
231 232 233
        Platform: () => platform,
      }, initializeFlutterRoot: false);
    });
234 235 236

    group('wrapping', () {
      testUsingContext('checks that output wrapping is turned on when writing to a terminal', () async {
237
        final FakeFlutterCommand fakeCommand = FakeFlutterCommand();
238 239 240 241 242
        runner.addCommand(fakeCommand);
        await runner.run(<String>['fake']);
        expect(fakeCommand.preferences.wrapText, isTrue);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
243
        ProcessManager: () => FakeProcessManager.any(),
244 245 246 247
        Stdio: () => FakeStdio(hasFakeTerminal: true),
      }, initializeFlutterRoot: false);

      testUsingContext('checks that output wrapping is turned off when not writing to a terminal', () async {
248
        final FakeFlutterCommand fakeCommand = FakeFlutterCommand();
249 250 251 252 253
        runner.addCommand(fakeCommand);
        await runner.run(<String>['fake']);
        expect(fakeCommand.preferences.wrapText, isFalse);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
254
        ProcessManager: () => FakeProcessManager.any(),
255 256 257 258
        Stdio: () => FakeStdio(hasFakeTerminal: false),
      }, initializeFlutterRoot: false);

      testUsingContext('checks that output wrapping is turned off when set on the command line and writing to a terminal', () async {
259
        final FakeFlutterCommand fakeCommand = FakeFlutterCommand();
260 261 262 263 264
        runner.addCommand(fakeCommand);
        await runner.run(<String>['--no-wrap', 'fake']);
        expect(fakeCommand.preferences.wrapText, isFalse);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
265
        ProcessManager: () => FakeProcessManager.any(),
266 267 268 269
        Stdio: () => FakeStdio(hasFakeTerminal: true),
      }, initializeFlutterRoot: false);

      testUsingContext('checks that output wrapping is turned on when set on the command line, but not writing to a terminal', () async {
270
        final FakeFlutterCommand fakeCommand = FakeFlutterCommand();
271 272 273 274 275
        runner.addCommand(fakeCommand);
        await runner.run(<String>['--wrap', 'fake']);
        expect(fakeCommand.preferences.wrapText, isTrue);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
276
        ProcessManager: () => FakeProcessManager.any(),
277 278 279
        Stdio: () => FakeStdio(hasFakeTerminal: false),
      }, initializeFlutterRoot: false);
    });
280
  });
281
}
282 283 284 285 286 287
class MockProcessManager extends Mock implements ProcessManager {}

class FakeFlutterVersion extends FlutterVersion {
  @override
  String get frameworkVersion => '0.10.3';
}
288

289
class FakeFlutterCommand extends FlutterCommand {
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
  OutputPreferences preferences;

  @override
  Future<FlutterCommandResult> runCommand() {
    preferences = outputPreferences;
    return Future<FlutterCommandResult>.value(const FlutterCommandResult(ExitStatus.success));
  }

  @override
  String get description => null;

  @override
  String get name => 'fake';
}

class FakeStdio extends Stdio {
  FakeStdio({this.hasFakeTerminal});

  final bool hasFakeTerminal;

  @override
  bool get hasTerminal => hasFakeTerminal;

  @override
  int get terminalColumns => hasFakeTerminal ? 80 : null;

  @override
  int get terminalLines => hasFakeTerminal ? 24 : null;
  @override
  bool get supportsAnsiEscapes => hasFakeTerminal;
}