flutter_command_runner_test.dart 11 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
import 'package:flutter_tools/src/base/io.dart';
9
import 'package:flutter_tools/src/base/platform.dart';
10
import 'package:flutter_tools/src/base/terminal.dart';
11
import 'package:flutter_tools/src/cache.dart';
12
import 'package:flutter_tools/src/globals.dart' as globals;
13 14
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
15
import 'package:flutter_tools/src/version.dart';
16
import 'package:mockito/mockito.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 25
const String _kFlutterRoot = '/flutter/flutter';
const String _kProjectRoot = '/project';

26 27
void main() {
  group('FlutterCommandRunner', () {
28 29 30
    MemoryFileSystem fs;
    Platform platform;
    FlutterCommandRunner runner;
31
    ProcessManager processManager;
32 33 34

    setUpAll(() {
      Cache.disableLocking();
35
      Cache.flutterRoot = FlutterCommandRunner.defaultFlutterRoot;
36 37 38
    });

    setUp(() {
39
      fs = MemoryFileSystem.test();
40 41 42 43
      fs.directory(_kFlutterRoot).createSync(recursive: true);
      fs.directory(_kProjectRoot).createSync(recursive: true);
      fs.currentDirectory = _kProjectRoot;

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

51
      runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner;
52
      processManager = MockProcessManager();
53 54 55 56
    });

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

63 64 65 66 67
        await runner.run(<String>['dummy']);

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

72
      testUsingContext('does not check that Flutter installation is up-to-date with --machine flag', () async {
73
        final MockFlutterVersion version = globals.flutterVersion as MockFlutterVersion;
74 75 76 77 78 79 80 81 82 83 84 85 86 87
        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);

88 89 90 91 92 93 94 95 96 97 98 99
      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);

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

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

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

112 113 114 115 116 117 118 119 120 121
    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,
122
      ProcessManager: () => FakeProcessManager.any(),
123 124 125
      Platform: () => platform,
    }, initializeFlutterRoot: false);

126 127 128 129
    group('version', () {
      testUsingContext('checks that Flutter toJson output reports the flutter framework version', () async {
        final ProcessResult result = ProcessResult(0, 0, 'random', '0');

130
        when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%H'.split(' ')),
131 132 133 134 135 136 137
          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);
138
        when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%ar'.split(' ')),
139
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
140 141
        when(processManager.runSync('git fetch https://github.com/flutter/flutter.git --tags'.split(' '),
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
142
        when(processManager.runSync('git tag --points-at HEAD'.split(' '),
143
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
144
        when(processManager.runSync('git describe --match *.*.* --first-parent --long --tags'.split(' '),
145
          workingDirectory: Cache.flutterRoot)).thenReturn(result);
146
        when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%ad --date=iso'.split(' ')),
147 148 149 150 151 152 153 154 155
          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,
156
        Platform: () => platform,
157 158 159
      }, initializeFlutterRoot: false);
    });

160
    group('getRepoPackages', () {
Dan Field's avatar
Dan Field committed
161 162
      String oldFlutterRoot;

163
      setUp(() {
Dan Field's avatar
Dan Field committed
164 165
        oldFlutterRoot = Cache.flutterRoot;
        Cache.flutterRoot = _kFlutterRoot;
166 167 168 169 170 171
        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);
172

173 174 175 176 177
        fs.file(fs.path.join(_kFlutterRoot, 'dev', 'tools', 'pubspec.yaml'))
            .createSync();
        fs.file(fs.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool', 'pubspec.yaml'))
            .createSync();
      });
178

Dan Field's avatar
Dan Field committed
179 180 181 182
      tearDown(() {
        Cache.flutterRoot = oldFlutterRoot;
      });

183 184 185 186 187 188 189 190 191
      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,
192
        ProcessManager: () => FakeProcessManager.any(),
193 194 195
        Platform: () => platform,
      }, initializeFlutterRoot: false);
    });
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 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241

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

      testUsingContext('checks that output wrapping is turned off when not writing to a terminal', () async {
        final FakeFlutterCommand fakeCommand = FakeFlutterCommand();
        runner.addCommand(fakeCommand);
        await runner.run(<String>['fake']);
        expect(fakeCommand.preferences.wrapText, isFalse);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
        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 {
        final FakeFlutterCommand fakeCommand = FakeFlutterCommand();
        runner.addCommand(fakeCommand);
        await runner.run(<String>['--no-wrap', 'fake']);
        expect(fakeCommand.preferences.wrapText, isFalse);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
        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 {
        final FakeFlutterCommand fakeCommand = FakeFlutterCommand();
        runner.addCommand(fakeCommand);
        await runner.run(<String>['--wrap', 'fake']);
        expect(fakeCommand.preferences.wrapText, isTrue);
      }, overrides: <Type, Generator>{
        FileSystem: () => fs,
        ProcessManager: () => FakeProcessManager.any(),
        Stdio: () => FakeStdio(hasFakeTerminal: false),
      }, initializeFlutterRoot: false);
    });
242
  });
243
  });
244
}
245 246 247 248 249 250
class MockProcessManager extends Mock implements ProcessManager {}

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

252
class FakeFlutterCommand extends FlutterCommand {
253 254 255 256
  OutputPreferences preferences;

  @override
  Future<FlutterCommandResult> runCommand() {
257
    preferences = globals.outputPreferences;
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
    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;
}