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

import 'package:file/file.dart';
import 'package:file/memory.dart';
7
import 'package:flutter_tools/src/base/bot_detector.dart';
8
import 'package:flutter_tools/src/base/common.dart';
9
import 'package:flutter_tools/src/base/file_system.dart';
10
import 'package:flutter_tools/src/base/io.dart';
11
import 'package:flutter_tools/src/base/logger.dart';
12
import 'package:flutter_tools/src/base/platform.dart';
13
import 'package:flutter_tools/src/cache.dart';
14
import 'package:flutter_tools/src/dart/pub.dart';
15
import 'package:flutter_tools/src/reporting/reporting.dart';
16 17
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
18
import 'package:fake_async/fake_async.dart';
19

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

void main() {
25
  setUpAll(() {
26
    Cache.flutterRoot = '';
27
  });
28

29 30 31 32
  tearDown(() {
    MockDirectory.findCache = false;
  });

33
  testWithoutContext('pub get 69', () async {
34
    String error;
35

36 37 38 39 40 41 42 43 44 45 46 47
    final MockProcessManager processMock = MockProcessManager(69);
    final BufferLogger logger = BufferLogger.test();
    final Pub pub = Pub(
      fileSystem: MockFileSystem(),
      logger: logger,
      processManager: processMock,
      usage: MockUsage(),
      platform: FakePlatform(
        environment: const <String, String>{},
      ),
      botDetector: const BotDetectorAlwaysNo(),
    );
48

49
    FakeAsync().run((FakeAsync time) {
Josh Soref's avatar
Josh Soref committed
50
      expect(processMock.lastPubEnvironment, isNull);
51
      expect(logger.statusText, '');
52
      pub.get(context: PubContext.flutterTests).then((void value) {
53
        error = 'test completed unexpectedly';
54 55
      }, onError: (dynamic thrownError) {
        error = 'test failed unexpectedly: $thrownError';
56 57
      });
      time.elapse(const Duration(milliseconds: 500));
58
      expect(logger.statusText,
59
        'Running "flutter pub get" in /...\n'
60
        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n',
61
      );
Josh Soref's avatar
Josh Soref committed
62
      expect(processMock.lastPubEnvironment, contains('flutter_cli:flutter_tests'));
63
      expect(processMock.lastPubCache, isNull);
64
      time.elapse(const Duration(milliseconds: 500));
65
      expect(logger.statusText,
66
        'Running "flutter pub get" in /...\n'
67 68
        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n'
        'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n',
69 70
      );
      time.elapse(const Duration(seconds: 1));
71
      expect(logger.statusText,
72
        'Running "flutter pub get" in /...\n'
73 74
        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n'
        'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n',
75 76
      );
      time.elapse(const Duration(seconds: 100)); // from t=0 to t=100
77
      expect(logger.statusText,
78
        'Running "flutter pub get" in /...\n'
79 80 81 82 83 84 85
        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n'
        'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n'
        'pub get failed (server unavailable) -- attempting retry 3 in 4 seconds...\n' // at t=1
        'pub get failed (server unavailable) -- attempting retry 4 in 8 seconds...\n' // at t=5
        'pub get failed (server unavailable) -- attempting retry 5 in 16 seconds...\n' // at t=13
        'pub get failed (server unavailable) -- attempting retry 6 in 32 seconds...\n' // at t=29
        'pub get failed (server unavailable) -- attempting retry 7 in 64 seconds...\n', // at t=61
86 87
      );
      time.elapse(const Duration(seconds: 200)); // from t=0 to t=200
88
      expect(logger.statusText,
89
        'Running "flutter pub get" in /...\n'
90 91 92 93 94 95 96 97 98 99
        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n'
        'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n'
        'pub get failed (server unavailable) -- attempting retry 3 in 4 seconds...\n'
        'pub get failed (server unavailable) -- attempting retry 4 in 8 seconds...\n'
        'pub get failed (server unavailable) -- attempting retry 5 in 16 seconds...\n'
        'pub get failed (server unavailable) -- attempting retry 6 in 32 seconds...\n'
        'pub get failed (server unavailable) -- attempting retry 7 in 64 seconds...\n'
        'pub get failed (server unavailable) -- attempting retry 8 in 64 seconds...\n' // at t=39
        'pub get failed (server unavailable) -- attempting retry 9 in 64 seconds...\n' // at t=103
        'pub get failed (server unavailable) -- attempting retry 10 in 64 seconds...\n', // at t=167
100 101
      );
    });
102
    expect(logger.errorText, isEmpty);
103
    expect(error, isNull);
104 105
  });

106 107 108 109 110 111 112 113 114 115
  testWithoutContext('pub get 66 shows message from pub', () async {
    final BufferLogger logger = BufferLogger.test();
    final Pub pub = Pub(
      platform: FakePlatform(environment: const <String, String>{}),
      fileSystem: MockFileSystem(),
      logger: logger,
      usage: MockUsage(),
      botDetector: const BotDetectorAlwaysNo(),
      processManager: MockProcessManager(66, stderr: 'err1\nerr2\nerr3\n', stdout: 'out1\nout2\nout3\n'),
    );
116
    try {
117
      await pub.get(context: PubContext.flutterTests);
118 119 120 121
      throw AssertionError('pubGet did not fail');
    } on ToolExit catch (error) {
      expect(error.message, 'pub get failed (66; err3)');
    }
122
    expect(logger.statusText,
123 124 125 126 127
      'Running "flutter pub get" in /...\n'
      'out1\n'
      'out2\n'
      'out3\n'
    );
128
    expect(logger.errorText,
129 130 131 132 133 134
      'err1\n'
      'err2\n'
      'err3\n'
    );
  });

135
  testWithoutContext('pub cache in root is used', () async {
136
    String error;
137 138 139 140 141 142 143 144 145 146
    final MockProcessManager processMock = MockProcessManager(69);
    final MockFileSystem fsMock = MockFileSystem();
    final Pub pub = Pub(
      platform: FakePlatform(environment: const <String, String>{}),
      usage: MockUsage(),
      fileSystem: fsMock,
      logger: BufferLogger.test(),
      processManager: processMock,
      botDetector: const BotDetectorAlwaysNo(),
    );
147

148
    FakeAsync().run((FakeAsync time) {
149
      MockDirectory.findCache = true;
Josh Soref's avatar
Josh Soref committed
150
      expect(processMock.lastPubEnvironment, isNull);
151
      expect(processMock.lastPubCache, isNull);
152
      pub.get(context: PubContext.flutterTests).then((void value) {
153 154 155 156 157
        error = 'test completed unexpectedly';
      }, onError: (dynamic thrownError) {
        error = 'test failed unexpectedly: $thrownError';
      });
      time.elapse(const Duration(milliseconds: 500));
158

159
      expect(processMock.lastPubCache, equals(fsMock.path.join(Cache.flutterRoot, '.pub-cache')));
160 161 162 163
      expect(error, isNull);
    });
  });

164 165 166 167 168 169 170 171 172 173 174 175 176 177
  testWithoutContext('pub cache in environment is used', () async {
    final MockProcessManager processMock = MockProcessManager(69);
    final Pub pub = Pub(
      fileSystem: MockFileSystem(),
      logger: BufferLogger.test(),
      processManager: processMock,
      usage: MockUsage(),
      botDetector: const BotDetectorAlwaysNo(),
      platform: FakePlatform(
        environment: const <String, String>{
          'PUB_CACHE': 'custom/pub-cache/path',
        },
      ),
    );
178

179
    FakeAsync().run((FakeAsync time) {
180
      MockDirectory.findCache = true;
Josh Soref's avatar
Josh Soref committed
181
      expect(processMock.lastPubEnvironment, isNull);
182
      expect(processMock.lastPubCache, isNull);
183 184

      String error;
185
      pub.get(context: PubContext.flutterTests).then((void value) {
186 187 188 189 190
        error = 'test completed unexpectedly';
      }, onError: (dynamic thrownError) {
        error = 'test failed unexpectedly: $thrownError';
      });
      time.elapse(const Duration(milliseconds: 500));
191

192
      expect(processMock.lastPubCache, equals('custom/pub-cache/path'));
193 194
      expect(error, isNull);
    });
195
  });
196

197
  testWithoutContext('analytics sent on success', () async {
198
    final FileSystem fileSystem = MemoryFileSystem.test();
199 200
    final MockUsage usage = MockUsage();
    final Pub pub = Pub(
201
      fileSystem: fileSystem,
202 203 204 205 206 207 208 209 210 211
      logger: BufferLogger.test(),
      processManager: MockProcessManager(0),
      botDetector: const BotDetectorAlwaysNo(),
      usage: usage,
      platform: FakePlatform(
        environment: const <String, String>{
          'PUB_CACHE': 'custom/pub-cache/path',
        }
      ),
    );
212 213 214 215
    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('.dart_tool/package_config.json')
      ..createSync(recursive: true)
      ..writeAsStringSync('{"configVersion": 2,"packages": []}');
216

217 218 219 220
    await pub.get(
      context: PubContext.flutterTests,
      generateSyntheticPackage: true,
    );
221 222

    verify(usage.sendEvent('pub-result', 'flutter-tests', label: 'success')).called(1);
223 224
  });

225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
  testWithoutContext('package_config_subset file is generated from packages and not timestamp', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final MockUsage usage = MockUsage();
    final Pub pub = Pub(
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      processManager: MockProcessManager(0),
      botDetector: const BotDetectorAlwaysNo(),
      usage: usage,
      platform: FakePlatform(
        environment: const <String, String>{
          'PUB_CACHE': 'custom/pub-cache/path',
        }
      ),
    );
    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('.dart_tool/package_config.json')
      ..createSync(recursive: true)
      ..writeAsStringSync('''
      {"configVersion": 2,"packages": [
        {
          "name": "flutter_tools",
          "rootUri": "../",
          "packageUri": "lib/",
          "languageVersion": "2.7"
        }
      ],"generated":"some-time"}
''');

    await pub.get(
      context: PubContext.flutterTests,
      generateSyntheticPackage: true,
    );

    expect(
      fileSystem.file('.dart_tool/package_config_subset').readAsStringSync(),
      'flutter_tools\n'
      '2.7\n'
      'file:///\n'
      'file:///lib/\n'
      '2\n',
    );
  });


270
  testWithoutContext('analytics sent on failure', () async {
271
    MockDirectory.findCache = true;
272 273 274 275 276 277 278 279 280 281 282 283 284
    final MockUsage usage = MockUsage();
    final Pub pub = Pub(
      usage: usage,
      fileSystem: MockFileSystem(),
      logger: BufferLogger.test(),
      processManager: MockProcessManager(1),
      botDetector: const BotDetectorAlwaysNo(),
      platform: FakePlatform(
        environment: const <String, String>{
          'PUB_CACHE': 'custom/pub-cache/path',
        },
      ),
    );
285
    try {
286
      await pub.get(context: PubContext.flutterTests);
287 288 289
    } on ToolExit {
      // Ignore.
    }
290 291

    verify(usage.sendEvent('pub-result', 'flutter-tests', label: 'failure')).called(1);
292 293
  });

294 295
  testWithoutContext('analytics sent on failed version solve', () async {
    final MockUsage usage = MockUsage();
296
    final FileSystem fileSystem = MemoryFileSystem.test();
297
    final Pub pub = Pub(
298
      fileSystem: fileSystem,
299 300 301 302 303 304 305 306 307 308 309 310 311
      logger: BufferLogger.test(),
      processManager: MockProcessManager(
        1,
        stderr: 'version solving failed',
      ),
      platform: FakePlatform(
        environment: <String, String>{
          'PUB_CACHE': 'custom/pub-cache/path',
        },
      ),
      usage: usage,
      botDetector: const BotDetectorAlwaysNo(),
    );
312
    fileSystem.file('pubspec.yaml').writeAsStringSync('name: foo');
313

314
    try {
315
      await pub.get(context: PubContext.flutterTests);
316 317 318
    } on ToolExit {
      // Ignore.
    }
319 320

    verify(usage.sendEvent('pub-result', 'flutter-tests', label: 'version-solving-failed')).called(1);
321
  });
322

323 324 325
  testWithoutContext('Pub error handling', () async {
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
326 327 328
    final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
      FakeCommand(
        command: const <String>[
329
          'bin/cache/dart-sdk/bin/pub',
330 331 332 333 334
          '--verbosity=warning',
          'get',
          '--no-precompile',
        ],
        onRun: () {
335
          fileSystem.file('.dart_tool/package_config.json')
336
            .setLastModifiedSync(DateTime(2002));
337 338 339 340
        }
      ),
      const FakeCommand(
        command: <String>[
341
          'bin/cache/dart-sdk/bin/pub',
342 343 344 345 346 347 348
          '--verbosity=warning',
          'get',
          '--no-precompile',
        ],
      ),
      FakeCommand(
        command: const <String>[
349
          'bin/cache/dart-sdk/bin/pub',
350 351 352 353 354
          '--verbosity=warning',
          'get',
          '--no-precompile',
        ],
        onRun: () {
355
          fileSystem.file('pubspec.yaml')
356
            .setLastModifiedSync(DateTime(2002));
357 358
        }
      ),
359 360
      const FakeCommand(
        command: <String>[
361
          'bin/cache/dart-sdk/bin/pub',
362 363 364 365 366
          '--verbosity=warning',
          'get',
          '--no-precompile',
        ],
      ),
367
    ]);
368 369 370 371 372 373
    final Pub pub = Pub(
      usage: MockUsage(),
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      platform: FakePlatform(
374 375 376
        operatingSystem: 'linux', // so that the command executed is consistent
        environment: <String, String>{},
      ),
377 378 379 380 381 382 383 384 385 386
      botDetector: const BotDetectorAlwaysNo()
    );

    // the good scenario: .packages is old, pub updates the file.
    fileSystem.file('.dart_tool/package_config.json')
      ..createSync(recursive: true)
      ..setLastModifiedSync(DateTime(2000));
    fileSystem.file('pubspec.yaml')
      ..createSync()
      ..setLastModifiedSync(DateTime(2001));
387
    await pub.get(context: PubContext.flutterTests); // pub sets date of .packages to 2002
388 389 390 391 392 393 394 395 396 397 398

    expect(logger.statusText, 'Running "flutter pub get" in /...\n');
    expect(logger.errorText, isEmpty);
    expect(fileSystem.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it
    logger.clear();

    // bad scenario 1: pub doesn't update file; doesn't matter, because we do instead
    fileSystem.file('.dart_tool/package_config.json')
      .setLastModifiedSync(DateTime(2000));
    fileSystem.file('pubspec.yaml')
      .setLastModifiedSync(DateTime(2001));
399
    await pub.get(context: PubContext.flutterTests); // pub does nothing
400 401 402 403 404

    expect(logger.statusText, 'Running "flutter pub get" in /...\n');
    expect(logger.errorText, isEmpty);
    expect(fileSystem.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it
    logger.clear();
405 406 407 408 409
  });
}

class BotDetectorAlwaysNo implements BotDetector {
  const BotDetectorAlwaysNo();
410

411
  @override
412
  Future<bool> get isRunningOnBot async => false;
413 414
}

415
typedef StartCallback = void Function(List<dynamic> command);
416 417

class MockProcessManager implements ProcessManager {
418
  MockProcessManager(this.fakeExitCode, {
419
    this.stdout = '',
420 421
    this.stderr = '',
  });
422 423

  final int fakeExitCode;
424
  final String stdout;
425
  final String stderr;
426

Josh Soref's avatar
Josh Soref committed
427
  String lastPubEnvironment;
428
  String lastPubCache;
429

430 431 432 433 434
  @override
  Future<Process> start(
    List<dynamic> command, {
    String workingDirectory,
    Map<String, String> environment,
435 436
    bool includeParentEnvironment = true,
    bool runInShell = false,
437
    ProcessStartMode mode = ProcessStartMode.normal,
438
  }) {
Josh Soref's avatar
Josh Soref committed
439
    lastPubEnvironment = environment['PUB_ENVIRONMENT'];
440
    lastPubCache = environment['PUB_CACHE'];
441 442
    return Future<Process>.value(mocks.createMockProcess(
      exitCode: fakeExitCode,
443
      stdout: stdout,
444 445
      stderr: stderr,
    ));
446 447 448 449 450 451
  }

  @override
  dynamic noSuchMethod(Invocation invocation) => null;
}

452
class MockFileSystem extends ForwardingFileSystem {
453
  MockFileSystem() : super(MemoryFileSystem.test());
454

455 456
  @override
  File file(dynamic path) {
457
    return MockFile();
458
  }
459 460 461

  @override
  Directory directory(dynamic path) {
462
    return MockDirectory(path as String);
463
  }
464 465 466 467
}

class MockFile implements File {
  @override
468
  Future<RandomAccessFile> open({ FileMode mode = FileMode.read }) async {
469
    return MockRandomAccessFile();
470 471 472 473 474 475
  }

  @override
  bool existsSync() => true;

  @override
476
  DateTime lastModifiedSync() => DateTime(0);
477 478 479 480 481

  @override
  dynamic noSuchMethod(Invocation invocation) => null;
}

482 483 484 485 486 487
class MockDirectory implements Directory {
  MockDirectory(this.path);

  @override
  final String path;

488 489
  static bool findCache = false;

490 491 492 493 494 495 496
  @override
  bool existsSync() => findCache && path.endsWith('.pub-cache');

  @override
  dynamic noSuchMethod(Invocation invocation) => null;
}

497
class MockRandomAccessFile extends Mock implements RandomAccessFile {}
498 499

class MockUsage extends Mock implements Usage {}