upgrade_test.dart 17.9 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:flutter_tools/src/base/file_system.dart';
6
import 'package:flutter_tools/src/base/io.dart';
7
import 'package:flutter_tools/src/base/platform.dart';
8
import 'package:flutter_tools/src/cache.dart';
9
import 'package:flutter_tools/src/commands/upgrade.dart';
10
import 'package:flutter_tools/src/convert.dart';
11
import 'package:flutter_tools/src/globals.dart' as globals;
12
import 'package:flutter_tools/src/persistent_tool_state.dart';
13 14
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/version.dart';
15

16 17
import '../../src/common.dart';
import '../../src/context.dart';
18
import '../../src/fake_process_manager.dart';
19
import '../../src/fakes.dart';
20
import '../../src/test_flutter_command_runner.dart';
21

22
void main() {
23
  group('UpgradeCommandRunner', () {
24 25 26 27
    late FakeUpgradeCommandRunner fakeCommandRunner;
    late UpgradeCommandRunner realCommandRunner;
    late FakeProcessManager processManager;
    late FakePlatform fakePlatform;
28 29 30 31 32 33 34 35
    const GitTagVersion gitTagVersion = GitTagVersion(
      x: 1,
      y: 2,
      z: 3,
      hotfix: 4,
      commits: 5,
      hash: 'asd',
    );
36 37 38 39

    setUp(() {
      fakeCommandRunner = FakeUpgradeCommandRunner();
      realCommandRunner = UpgradeCommandRunner();
40
      processManager = FakeProcessManager.empty();
41
      fakeCommandRunner.willHaveUncommittedChanges = false;
42
      fakePlatform = FakePlatform()..environment = Map<String, String>.unmodifiable(<String, String>{
43 44
        'ENV1': 'irrelevant',
        'ENV2': 'irrelevant',
45
      });
46 47
    });

48
    testUsingContext('throws on unknown tag, official branch,  noforce', () async {
49
      final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta');
50
      const String upstreamRevision = '';
51
      final FakeFlutterVersion latestVersion = FakeFlutterVersion(frameworkRevision: upstreamRevision);
52 53
      fakeCommandRunner.remoteVersion = latestVersion;

54
      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
55 56 57 58 59
        force: false,
        continueFlow: false,
        testFlow: false,
        gitTagVersion: const GitTagVersion.unknown(),
        flutterVersion: flutterVersion,
60
        verifyOnly: false,
61
      );
Dan Field's avatar
Dan Field committed
62
      expect(result, throwsToolExit());
63
      expect(processManager, hasNoRemainingExpectations);
64 65
    }, overrides: <Type, Generator>{
      Platform: () => fakePlatform,
66 67
    });

68
    testUsingContext('throws tool exit with uncommitted changes', () async {
69
      final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta');
70
      const String upstreamRevision = '';
71
      final FakeFlutterVersion latestVersion = FakeFlutterVersion(frameworkRevision: upstreamRevision);
72
      fakeCommandRunner.remoteVersion = latestVersion;
73
      fakeCommandRunner.willHaveUncommittedChanges = true;
74

75
      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
76 77 78 79 80
        force: false,
        continueFlow: false,
        testFlow: false,
        gitTagVersion: gitTagVersion,
        flutterVersion: flutterVersion,
81
        verifyOnly: false,
82
      );
Dan Field's avatar
Dan Field committed
83
      expect(result, throwsToolExit());
84
      expect(processManager, hasNoRemainingExpectations);
85 86
    }, overrides: <Type, Generator>{
      Platform: () => fakePlatform,
87 88
    });

89
    testUsingContext("Doesn't continue on known tag, beta branch, no force, already up-to-date", () async {
90
      const String revision = 'abc123';
91
      final FakeFlutterVersion latestVersion = FakeFlutterVersion(frameworkRevision: revision);
92
      final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta', frameworkRevision: revision);
93
      fakeCommandRunner.alreadyUpToDate = true;
94 95
      fakeCommandRunner.remoteVersion = latestVersion;

96
      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
97 98 99 100 101
        force: false,
        continueFlow: false,
        testFlow: false,
        gitTagVersion: gitTagVersion,
        flutterVersion: flutterVersion,
102
        verifyOnly: false,
103
      );
104
      expect(await result, FlutterCommandResult.success());
105
      expect(testLogger.statusText, contains('Flutter is already up to date'));
106
      expect(processManager, hasNoRemainingExpectations);
107 108 109 110 111
    }, overrides: <Type, Generator>{
      ProcessManager: () => processManager,
      Platform: () => fakePlatform,
    });

112
    testUsingContext('correctly provides upgrade version on verify only', () async {
113
      const String revision = 'abc123';
114 115 116 117
      const String upstreamRevision = 'def456';
      const String version = '1.2.3';
      const String upstreamVersion = '4.5.6';

118
      final FakeFlutterVersion flutterVersion = FakeFlutterVersion(
119
        channel: 'beta',
120 121 122 123
        frameworkRevision: revision,
        frameworkRevisionShort: revision,
        frameworkVersion: version,
      );
124

125 126 127 128 129
      final FakeFlutterVersion latestVersion = FakeFlutterVersion(
        frameworkRevision: upstreamRevision,
        frameworkRevisionShort: upstreamRevision,
        frameworkVersion: upstreamVersion,
      );
130

131
      fakeCommandRunner.alreadyUpToDate = false;
132 133
      fakeCommandRunner.remoteVersion = latestVersion;

134 135 136 137 138 139 140 141 142 143
      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
        force: false,
        continueFlow: false,
        testFlow: false,
        gitTagVersion: gitTagVersion,
        flutterVersion: flutterVersion,
        verifyOnly: true,
      );
      expect(await result, FlutterCommandResult.success());
      expect(testLogger.statusText, contains('A new version of Flutter is available'));
144 145
      expect(testLogger.statusText, contains('The latest version: 4.5.6 (revision def456)'));
      expect(testLogger.statusText, contains('Your current version: 1.2.3 (revision abc123)'));
146
      expect(processManager, hasNoRemainingExpectations);
147 148 149 150 151
    }, overrides: <Type, Generator>{
      ProcessManager: () => processManager,
      Platform: () => fakePlatform,
    });

152
    testUsingContext('fetchLatestVersion returns version if git succeeds', () async {
153
      const String revision = 'abc123';
154
      const String version = '1.2.3';
155 156 157

      processManager.addCommands(<FakeCommand>[
        const FakeCommand(command: <String>[
158
          'git', 'fetch', '--tags',
159 160
        ]),
        const FakeCommand(command: <String>[
161
          'git', 'rev-parse', '--verify', '@{upstream}',
162 163
        ],
        stdout: revision),
164 165
        const FakeCommand(command: <String>[
          'git', 'tag', '--points-at', revision,
166
        ]),
167 168 169 170
        const FakeCommand(command: <String>[
          'git', 'describe', '--match', '*.*.*', '--long', '--tags', revision,
        ],
        stdout: version),
171 172
      ]);

173
      final FlutterVersion updateVersion = await realCommandRunner.fetchLatestVersion(localVersion: FakeFlutterVersion());
174 175 176

      expect(updateVersion.frameworkVersion, version);
      expect(updateVersion.frameworkRevision, revision);
177
      expect(processManager, hasNoRemainingExpectations);
178 179
    }, overrides: <Type, Generator>{
      ProcessManager: () => processManager,
180 181 182
      Platform: () => fakePlatform,
    });

183
    testUsingContext('fetchLatestVersion throws toolExit if HEAD is detached', () async {
184 185
      processManager.addCommands(const <FakeCommand>[
        FakeCommand(command: <String>[
186
          'git', 'fetch', '--tags',
187 188
        ]),
        FakeCommand(
189
          command: <String>['git', 'rev-parse', '--verify', '@{upstream}'],
190 191
          exception: ProcessException(
            'git',
192
            <String>['rev-parse', '--verify', '@{upstream}'],
193 194
            'fatal: HEAD does not point to a branch',
          ),
195 196 197
        ),
      ]);

198
      await expectLater(
199
        () async => realCommandRunner.fetchLatestVersion(localVersion: FakeFlutterVersion()),
200 201 202 203 204
        throwsToolExit(message: 'Unable to upgrade Flutter: Your Flutter checkout '
          'is currently not on a release branch.\n'
          'Use "flutter channel" to switch to an official channel, and retry. '
          'Alternatively, re-install Flutter by going to https://flutter.dev/docs/get-started/install.'
        ),
205
      );
206
      expect(processManager, hasNoRemainingExpectations);
207 208 209 210 211
    }, overrides: <Type, Generator>{
      ProcessManager: () => processManager,
      Platform: () => fakePlatform,
    });

212
    testUsingContext('fetchLatestVersion throws toolExit if no upstream configured', () async {
213 214
      processManager.addCommands(const <FakeCommand>[
        FakeCommand(command: <String>[
215
          'git', 'fetch', '--tags',
216 217
        ]),
        FakeCommand(
218
          command: <String>['git', 'rev-parse', '--verify', '@{upstream}'],
219 220
          exception: ProcessException(
            'git',
221
            <String>['rev-parse', '--verify', '@{upstream}'],
222 223
            'fatal: no upstream configured for branch',
          ),
224 225 226
        ),
      ]);

227
      await expectLater(
228
        () async => realCommandRunner.fetchLatestVersion(localVersion: FakeFlutterVersion()),
229 230 231
        throwsToolExit(message: 'Unable to upgrade Flutter: The current Flutter '
          'branch/channel is not tracking any remote repository.\n'
          'Re-install Flutter by going to https://flutter.dev/docs/get-started/install.'
232 233
        ),
      );
234
      expect(processManager, hasNoRemainingExpectations);
235 236 237 238 239
    }, overrides: <Type, Generator>{
      ProcessManager: () => processManager,
      Platform: () => fakePlatform,
    });

240 241 242
    testUsingContext('git exception during attemptReset throwsToolExit', () async {
      const String revision = 'abc123';
      const String errorMessage = 'fatal: Could not parse object ´$revision´';
243 244 245 246 247 248 249 250
      processManager.addCommand(
        const FakeCommand(
          command: <String>['git', 'reset', '--hard', revision],
          exception: ProcessException(
            'git',
            <String>['reset', '--hard', revision],
            errorMessage,
          ),
251
        ),
252
      );
253 254

      await expectLater(
255
            () async => realCommandRunner.attemptReset(revision),
256 257
        throwsToolExit(message: errorMessage),
      );
258
      expect(processManager, hasNoRemainingExpectations);
259 260 261 262 263
    }, overrides: <Type, Generator>{
      ProcessManager: () => processManager,
      Platform: () => fakePlatform,
    });

264
    testUsingContext('flutterUpgradeContinue passes env variables to child process', () async {
265 266 267 268 269 270 271 272 273 274 275
      processManager.addCommand(
        FakeCommand(
          command: <String>[
            globals.fs.path.join('bin', 'flutter'),
            'upgrade',
            '--continue',
            '--no-version-check',
          ],
          environment: <String, String>{'FLUTTER_ALREADY_LOCKED': 'true', ...fakePlatform.environment}
        ),
      );
276
      await realCommandRunner.flutterUpgradeContinue();
277
      expect(processManager, hasNoRemainingExpectations);
278 279 280 281 282
    }, overrides: <Type, Generator>{
      ProcessManager: () => processManager,
      Platform: () => fakePlatform,
    });

283 284 285 286 287 288
    testUsingContext('Show current version to the upgrade message.', () async {
      const String revision = 'abc123';
      const String upstreamRevision = 'def456';
      const String version = '1.2.3';
      const String upstreamVersion = '4.5.6';

289
      final FakeFlutterVersion flutterVersion = FakeFlutterVersion(
290
        channel: 'beta',
291 292 293 294 295 296 297
        frameworkRevision: revision,
        frameworkVersion: version,
      );
      final FakeFlutterVersion latestVersion = FakeFlutterVersion(
        frameworkRevision: upstreamRevision,
        frameworkVersion: upstreamVersion,
      );
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

      fakeCommandRunner.alreadyUpToDate = false;
      fakeCommandRunner.remoteVersion = latestVersion;
      fakeCommandRunner.workingDirectory = 'workingDirectory/aaa/bbb';

      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
        force: true,
        continueFlow: false,
        testFlow: true,
        gitTagVersion: gitTagVersion,
        flutterVersion: flutterVersion,
        verifyOnly: false,
      );
      expect(await result, FlutterCommandResult.success());
      expect(testLogger.statusText, contains('Upgrading Flutter to 4.5.6 from 1.2.3 in workingDirectory/aaa/bbb...'));
    }, overrides: <Type, Generator>{
      ProcessManager: () => processManager,
      Platform: () => fakePlatform,
    });

318
    testUsingContext('precacheArtifacts passes env variables to child process', () async {
319 320 321 322 323 324 325 326 327 328 329
      processManager.addCommand(
        FakeCommand(
          command: <String>[
            globals.fs.path.join('bin', 'flutter'),
            '--no-color',
            '--no-version-check',
            'precache',
          ],
          environment: <String, String>{'FLUTTER_ALREADY_LOCKED': 'true', ...fakePlatform.environment}
        ),
      );
330
      await realCommandRunner.precacheArtifacts();
331
      expect(processManager, hasNoRemainingExpectations);
332 333 334
    }, overrides: <Type, Generator>{
      ProcessManager: () => processManager,
      Platform: () => fakePlatform,
335
    });
336

337
    group('runs upgrade', () {
338
      setUp(() {
339 340 341 342 343 344 345 346
        processManager.addCommand(
          FakeCommand(command: <String>[
            globals.fs.path.join('bin', 'flutter'),
            'upgrade',
            '--continue',
            '--no-version-check',
          ]),
        );
347 348
      });

349
      testUsingContext('does not throw on unknown tag, official branch, force', () async {
350
        fakeCommandRunner.remoteVersion = FakeFlutterVersion(frameworkRevision: '1234');
351
        final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta');
352

353 354 355 356 357 358
        final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
          force: true,
          continueFlow: false,
          testFlow: false,
          gitTagVersion: const GitTagVersion.unknown(),
          flutterVersion: flutterVersion,
359
          verifyOnly: false,
360 361
        );
        expect(await result, FlutterCommandResult.success());
362
        expect(processManager, hasNoRemainingExpectations);
363 364 365
      }, overrides: <Type, Generator>{
        ProcessManager: () => processManager,
        Platform: () => fakePlatform,
366 367
      });

368
      testUsingContext('does not throw tool exit with uncommitted changes and force', () async {
369
        final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta');
370
        fakeCommandRunner.remoteVersion = FakeFlutterVersion(frameworkRevision: '1234');
371
        fakeCommandRunner.willHaveUncommittedChanges = true;
372

373 374 375 376 377 378
        final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
          force: true,
          continueFlow: false,
          testFlow: false,
          gitTagVersion: gitTagVersion,
          flutterVersion: flutterVersion,
379
          verifyOnly: false,
380
        );
381
        expect(await result, FlutterCommandResult.success());
382
        expect(processManager, hasNoRemainingExpectations);
383 384 385 386
      }, overrides: <Type, Generator>{
        ProcessManager: () => processManager,
        Platform: () => fakePlatform,
      });
387

388 389
      testUsingContext("Doesn't throw on known tag, beta branch, no force", () async {
        final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta');
390
        fakeCommandRunner.remoteVersion = FakeFlutterVersion(frameworkRevision: '1234');
391

392 393 394 395 396 397
        final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
          force: false,
          continueFlow: false,
          testFlow: false,
          gitTagVersion: gitTagVersion,
          flutterVersion: flutterVersion,
398
          verifyOnly: false,
399
        );
400
        expect(await result, FlutterCommandResult.success());
401
        expect(processManager, hasNoRemainingExpectations);
402
      }, overrides: <Type, Generator>{
403 404 405 406 407
        ProcessManager: () => processManager,
        Platform: () => fakePlatform,
      });

      group('full command', () {
408 409 410
        late FakeProcessManager fakeProcessManager;
        late Directory tempDir;
        late File flutterToolState;
411 412 413 414 415 416 417 418 419 420 421

        setUp(() {
          Cache.disableLocking();
          fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
            const FakeCommand(
              command: <String>[
                'git', 'tag', '--points-at', 'HEAD',
              ],
            ),
            const FakeCommand(
              command: <String>[
422
                'git', 'describe', '--match', '*.*.*', '--long', '--tags', 'HEAD',
423 424 425 426 427 428 429 430 431 432 433 434 435 436
              ],
              stdout: 'v1.12.16-19-gb45b676af',
            ),
          ]);
          tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_upgrade_test.');
          flutterToolState = tempDir.childFile('.flutter_tool_state');
        });

        tearDown(() {
          Cache.enableLocking();
          tryToDelete(tempDir);
        });

        testUsingContext('upgrade continue prints welcome message', () async {
437 438 439 440
          final UpgradeCommand upgradeCommand = UpgradeCommand(
            verboseHelp: false,
            commandRunner: fakeCommandRunner,
          );
441 442 443 444 445 446 447 448 449 450 451 452 453

          await createTestCommandRunner(upgradeCommand).run(
            <String>[
              'upgrade',
              '--continue',
            ],
          );

          expect(
            json.decode(flutterToolState.readAsStringSync()),
            containsPair('redisplay-welcome-message', true),
          );
        }, overrides: <Type, Generator>{
454
          FlutterVersion: () => FakeFlutterVersion(),
455 456 457 458 459 460
          ProcessManager: () => fakeProcessManager,
          PersistentToolState: () => PersistentToolState.test(
            directory: tempDir,
            logger: testLogger,
          ),
        });
461 462
      });
    });
463

464
  });
465
}
466 467

class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
468
  bool willHaveUncommittedChanges = false;
469 470
  bool alreadyUpToDate = false;

471
  late FlutterVersion remoteVersion;
472

473
  @override
474
  Future<FlutterVersion> fetchLatestVersion({FlutterVersion? localVersion}) async => remoteVersion;
475

476
  @override
477
  Future<bool> hasUncommittedChanges() async => willHaveUncommittedChanges;
478 479

  @override
480
  Future<void> attemptReset(String newRevision) async {}
481 482 483 484 485 486 487 488 489 490

  @override
  Future<void> precacheArtifacts() async {}

  @override
  Future<void> updatePackages(FlutterVersion flutterVersion) async {}

  @override
  Future<void> runDoctor() async {}
}