packages_test.dart 21.5 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 7 8 9 10
// TODO(gspencergoog): Remove this tag once this test's state leaks/test
// dependencies have been fixed.
// https://github.com/flutter/flutter/issues/85160
// Fails with "flutter test --test-randomize-ordering-seed=1000"
@Tags(<String>['no-shuffle'])

11
import 'dart:async';
12
import 'dart:convert';
13 14

import 'package:args/command_runner.dart';
15
import 'package:file/memory.dart';
16
import 'package:flutter_tools/src/base/bot_detector.dart';
17
import 'package:flutter_tools/src/base/error_handling_io.dart';
18
import 'package:flutter_tools/src/base/file_system.dart' hide IOSink;
19
import 'package:flutter_tools/src/base/io.dart';
20
import 'package:flutter_tools/src/base/platform.dart';
21
import 'package:flutter_tools/src/cache.dart';
22
import 'package:flutter_tools/src/commands/packages.dart';
23
import 'package:flutter_tools/src/dart/pub.dart';
24
import 'package:flutter_tools/src/globals.dart' as globals;
25

26 27
import '../../src/common.dart';
import '../../src/context.dart';
28
import '../../src/fake_process_manager.dart';
29
import '../../src/fakes.dart';
30
import '../../src/test_flutter_command_runner.dart';
31 32

void main() {
33 34
  Cache.disableLocking();
  group('packages get/upgrade', () {
35
    late Directory tempDir;
36 37

    setUp(() {
38
      tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
39 40 41
    });

    tearDown(() {
42
      tryToDelete(tempDir);
43 44
    });

45
    Future<String> createProjectWithPlugin(String plugin, { List<String>? arguments }) async {
46
      final String projectPath = await createProject(tempDir, arguments: arguments);
47
      final File pubspec = globals.fs.file(globals.fs.path.join(projectPath, 'pubspec.yaml'));
48
      String content = await pubspec.readAsString();
49 50 51 52 53 54 55 56
      final List<String> contentLines = LineSplitter.split(content).toList();
      final int depsIndex = contentLines.indexOf('dependencies:');
      expect(depsIndex, isNot(-1));
      contentLines.replaceRange(depsIndex, depsIndex + 1, <String>[
        'dependencies:',
        '  $plugin:',
      ]);
      content = contentLines.join('\n');
57 58 59
      await pubspec.writeAsString(content, flush: true);
      return projectPath;
    }
60

61
    Future<PackagesCommand> runCommandIn(String projectPath, String verb, { List<String>? args }) async {
62
      final PackagesCommand command = PackagesCommand();
63
      final CommandRunner<void> runner = createTestCommandRunner(command);
64 65 66 67 68 69
      await runner.run(<String>[
        'packages',
        verb,
        ...?args,
        projectPath,
      ]);
70
      return command;
71 72
    }

73
    void expectExists(String projectPath, String relPath) {
74
      expect(
75
        globals.fs.isFileSync(globals.fs.path.join(projectPath, relPath)),
76 77 78 79 80 81 82 83
        true,
        reason: '$projectPath/$relPath should exist, but does not',
      );
    }

    void expectContains(String projectPath, String relPath, String substring) {
      expectExists(projectPath, relPath);
      expect(
84
        globals.fs.file(globals.fs.path.join(projectPath, relPath)).readAsStringSync(),
85
        contains(substring),
86
        reason: '$projectPath/$relPath has unexpected content',
87 88 89 90 91
      );
    }

    void expectNotExists(String projectPath, String relPath) {
      expect(
92
        globals.fs.isFileSync(globals.fs.path.join(projectPath, relPath)),
93 94 95 96 97 98 99 100
        false,
        reason: '$projectPath/$relPath should not exist, but does',
      );
    }

    void expectNotContains(String projectPath, String relPath, String substring) {
      expectExists(projectPath, relPath);
      expect(
101
        globals.fs.file(globals.fs.path.join(projectPath, relPath)).readAsStringSync(),
102 103 104 105 106
        isNot(contains(substring)),
        reason: '$projectPath/$relPath has unexpected content',
      );
    }

107 108
    final List<String> pubOutput = <String>[
      globals.fs.path.join('.dart_tool', 'package_config.json'),
109 110 111
      'pubspec.lock',
    ];

112
    const List<String> pluginRegistrants = <String>[
113 114 115 116 117
      'ios/Runner/GeneratedPluginRegistrant.h',
      'ios/Runner/GeneratedPluginRegistrant.m',
      'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
    ];

118
    const List<String> modulePluginRegistrants = <String>[
119 120 121 122 123
      '.ios/Flutter/FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.h',
      '.ios/Flutter/FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.m',
      '.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
    ];

124
    const List<String> pluginWitnesses = <String>[
125 126 127 128
      '.flutter-plugins',
      'ios/Podfile',
    ];

129
    const List<String> modulePluginWitnesses = <String>[
130 131 132 133
      '.flutter-plugins',
      '.ios/Podfile',
    ];

134
    const Map<String, String> pluginContentWitnesses = <String, String>{
135 136
      'ios/Flutter/Debug.xcconfig': '#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
      'ios/Flutter/Release.xcconfig': '#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
137 138
    };

139
    const Map<String, String> modulePluginContentWitnesses = <String, String>{
140 141
      '.ios/Config/Debug.xcconfig': '#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
      '.ios/Config/Release.xcconfig': '#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
142 143
    };

144
    void expectDependenciesResolved(String projectPath) {
145
      for (final String output in pubOutput) {
146 147 148 149 150
        expectExists(projectPath, output);
      }
    }

    void expectZeroPluginsInjected(String projectPath) {
151
      for (final String registrant in modulePluginRegistrants) {
152 153 154 155 156
        expectExists(projectPath, registrant);
      }
      for (final String witness in pluginWitnesses) {
        expectNotExists(projectPath, witness);
      }
157
      modulePluginContentWitnesses.forEach((String witness, String content) {
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
        expectNotContains(projectPath, witness, content);
      });
    }

    void expectPluginInjected(String projectPath) {
      for (final String registrant in pluginRegistrants) {
        expectExists(projectPath, registrant);
      }
      for (final String witness in pluginWitnesses) {
        expectExists(projectPath, witness);
      }
      pluginContentWitnesses.forEach((String witness, String content) {
        expectContains(projectPath, witness, content);
      });
    }

174 175
    void expectModulePluginInjected(String projectPath) {
      for (final String registrant in modulePluginRegistrants) {
176 177
        expectExists(projectPath, registrant);
      }
178
      for (final String witness in modulePluginWitnesses) {
179 180
        expectExists(projectPath, witness);
      }
181
      modulePluginContentWitnesses.forEach((String witness, String content) {
182 183 184 185
        expectContains(projectPath, witness, content);
      });
    }

186 187 188
    void removeGeneratedFiles(String projectPath) {
      final Iterable<String> allFiles = <List<String>>[
        pubOutput,
189
        modulePluginRegistrants,
190
        pluginWitnesses,
191
      ].expand<String>((List<String> list) => list);
192
      for (final String path in allFiles) {
193
        final File file = globals.fs.file(globals.fs.path.join(projectPath, path));
194
        ErrorHandlingFileSystem.deleteIfExists(file);
195
      }
196 197
    }

198
    testUsingContext('get fetches packages', () async {
199 200
      final String projectPath = await createProject(tempDir,
        arguments: <String>['--no-pub', '--template=module']);
201 202 203 204 205 206
      removeGeneratedFiles(projectPath);

      await runCommandIn(projectPath, 'get');

      expectDependenciesResolved(projectPath);
      expectZeroPluginsInjected(projectPath);
207
    }, overrides: <Type, Generator>{
208 209 210 211 212 213 214 215
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
216
    });
217

218
    testUsingContext('get --offline fetches packages', () async {
219 220
      final String projectPath = await createProject(tempDir,
        arguments: <String>['--no-pub', '--template=module']);
221 222 223 224 225 226
      removeGeneratedFiles(projectPath);

      await runCommandIn(projectPath, 'get', args: <String>['--offline']);

      expectDependenciesResolved(projectPath);
      expectZeroPluginsInjected(projectPath);
227
    }, overrides: <Type, Generator>{
228 229 230 231 232 233 234 235
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
236
    });
237

238
    testUsingContext('set no plugins as usage value', () async {
239 240 241 242 243
      final String projectPath = await createProject(tempDir,
        arguments: <String>['--no-pub', '--template=module']);
      removeGeneratedFiles(projectPath);

      final PackagesCommand command = await runCommandIn(projectPath, 'get');
244
      final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
245

246
      expect((await getCommand.usageValues).commandPackagesNumberPlugins, 0);
247
    }, overrides: <Type, Generator>{
248 249 250 251 252 253 254 255
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
256
    });
257

258 259 260 261 262 263 264 265
    testUsingContext('set the number of plugins as usage value', () async {
      final String projectPath = await createProject(
        tempDir,
        arguments: <String>['--template=plugin', '--no-pub', '--platforms=ios,android,macos,windows'],
      );
      final String exampleProjectPath = globals.fs.path.join(projectPath, 'example');

      final PackagesCommand command = await runCommandIn(exampleProjectPath, 'get');
266
      final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
267

268
      expect((await getCommand.usageValues).commandPackagesNumberPlugins, 1);
269 270 271 272 273 274 275 276 277 278 279
    }, overrides: <Type, Generator>{
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
    });

280 281 282 283 284 285
    testUsingContext('indicate that the project is not a module in usage value', () async {
      final String projectPath = await createProject(tempDir,
        arguments: <String>['--no-pub']);
      removeGeneratedFiles(projectPath);

      final PackagesCommand command = await runCommandIn(projectPath, 'get');
286
      final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
287

288
      expect((await getCommand.usageValues).commandPackagesProjectModule, false);
289
    }, overrides: <Type, Generator>{
290 291 292 293 294 295 296 297
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
298
    });
299 300 301 302 303 304 305

    testUsingContext('indicate that the project is a module in usage value', () async {
      final String projectPath = await createProject(tempDir,
        arguments: <String>['--no-pub', '--template=module']);
      removeGeneratedFiles(projectPath);

      final PackagesCommand command = await runCommandIn(projectPath, 'get');
306
      final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
307

308
      expect((await getCommand.usageValues).commandPackagesProjectModule, true);
309
    }, overrides: <Type, Generator>{
310 311 312 313 314 315 316 317
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
318
    });
319

320 321 322 323 324
    testUsingContext('indicate that Android project reports v1 in usage value', () async {
      final String projectPath = await createProject(tempDir,
        arguments: <String>['--no-pub']);
      removeGeneratedFiles(projectPath);

325 326 327 328 329 330 331 332 333
      final File androidManifest = globals.fs.file(globals.fs.path.join(
        projectPath,
        'android/app/src/main/AndroidManifest.xml',
      ));
      final String updatedAndroidManifestString =
          androidManifest.readAsStringSync().replaceAll('android:value="2"', 'android:value="1"');

      androidManifest.writeAsStringSync(updatedAndroidManifestString);

334
      final PackagesCommand command = await runCommandIn(projectPath, 'get');
335
      final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
336

337
      expect((await getCommand.usageValues).commandPackagesAndroidEmbeddingVersion, 'v1');
338
    }, overrides: <Type, Generator>{
339 340 341 342 343 344 345 346
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
347 348 349 350 351 352 353 354
    });

    testUsingContext('indicate that Android project reports v2 in usage value', () async {
      final String projectPath = await createProject(tempDir,
        arguments: <String>['--no-pub']);
      removeGeneratedFiles(projectPath);

      final PackagesCommand command = await runCommandIn(projectPath, 'get');
355
      final PackagesGetCommand getCommand = command.subcommands['get']! as PackagesGetCommand;
356

357
      expect((await getCommand.usageValues).commandPackagesAndroidEmbeddingVersion, 'v2');
358
    }, overrides: <Type, Generator>{
359 360 361 362 363 364 365 366
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
367 368
    });

369
    testUsingContext('upgrade fetches packages', () async {
370 371
      final String projectPath = await createProject(tempDir,
        arguments: <String>['--no-pub', '--template=module']);
372 373 374 375 376 377
      removeGeneratedFiles(projectPath);

      await runCommandIn(projectPath, 'upgrade');

      expectDependenciesResolved(projectPath);
      expectZeroPluginsInjected(projectPath);
378
    }, overrides: <Type, Generator>{
379
      Stdio: () => FakeStdio()..stdout.terminalColumns = 80,
380 381 382 383 384 385 386 387
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
388
    });
389 390

    testUsingContext('get fetches packages and injects plugin', () async {
391 392
      final String projectPath = await createProjectWithPlugin('path_provider',
        arguments: <String>['--no-pub', '--template=module']);
393 394 395
      removeGeneratedFiles(projectPath);

      await runCommandIn(projectPath, 'get');
396

397
      expectDependenciesResolved(projectPath);
398
      expectModulePluginInjected(projectPath);
399
    }, overrides: <Type, Generator>{
400 401 402 403 404 405 406 407
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
408
    });
409

410 411
    testUsingContext('get fetches packages and injects plugin in plugin project', () async {
      final String projectPath = await createProject(
412
        tempDir,
413
        arguments: <String>['--template=plugin', '--no-pub', '--platforms=ios,android'],
414
      );
415
      final String exampleProjectPath = globals.fs.path.join(projectPath, 'example');
416 417 418 419 420 421 422 423 424 425 426
      removeGeneratedFiles(projectPath);
      removeGeneratedFiles(exampleProjectPath);

      await runCommandIn(projectPath, 'get');

      expectDependenciesResolved(projectPath);

      await runCommandIn(exampleProjectPath, 'get');

      expectDependenciesResolved(exampleProjectPath);
      expectPluginInjected(exampleProjectPath);
427
    }, overrides: <Type, Generator>{
428 429 430 431 432 433 434 435
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
436
    });
437
  });
438 439

  group('packages test/pub', () {
440 441
    late FakeProcessManager processManager;
    late FakeStdio mockStdio;
442 443

    setUp(() {
444
      processManager = FakeProcessManager.empty();
445
      mockStdio = FakeStdio()..stdout.terminalColumns = 80;
446 447
    });

448
    testUsingContext('test without bot', () async {
449
      Cache.flutterRoot = '';
450
      globals.fs.directory('/packages/flutter_tools').createSync(recursive: true);
451 452
      globals.fs.file('pubspec.yaml').createSync();
      processManager.addCommand(
453
        const FakeCommand(command: <String>['/bin/cache/dart-sdk/bin/dart', '__deprecated_pub', 'run', 'test']),
454
      );
455
      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'test']);
456

457
      expect(processManager, hasNoRemainingExpectations);
458
    }, overrides: <Type, Generator>{
459
      FileSystem: () => MemoryFileSystem.test(),
460
      Platform: () => FakePlatform(environment: <String, String>{}),
461
      ProcessManager: () => processManager,
462
      Stdio: () => mockStdio,
463
      BotDetector: () => const FakeBotDetector(false),
464 465 466 467 468 469 470 471
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
472 473 474
    });

    testUsingContext('test with bot', () async {
475 476 477
      Cache.flutterRoot = '';
      globals.fs.file('pubspec.yaml').createSync();
      processManager.addCommand(
478
        const FakeCommand(command: <String>['/bin/cache/dart-sdk/bin/dart', '__deprecated_pub', '--trace', 'run', 'test']),
479
      );
480
      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', 'test']);
481

482
      expect(processManager, hasNoRemainingExpectations);
483
    }, overrides: <Type, Generator>{
484
      FileSystem: () => MemoryFileSystem.test(),
485
      Platform: () => FakePlatform(environment: <String, String>{}),
486
      ProcessManager: () => processManager,
487
      Stdio: () => mockStdio,
488
      BotDetector: () => const FakeBotDetector(true),
489 490 491 492 493 494 495 496
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
497 498
    });

499 500 501 502 503
    testUsingContext('run pass arguments through to pub', () async {
      Cache.flutterRoot = '';
      globals.fs.file('pubspec.yaml').createSync();
      final IOSink stdin = IOSink(StreamController<List<int>>().sink);
      processManager.addCommand(
504 505 506 507
        FakeCommand(
          command: const <String>[
            '/bin/cache/dart-sdk/bin/dart', '__deprecated_pub', 'run', '--foo', 'bar',
          ],
508 509 510 511
          stdin: stdin,
        ),
      );
      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', '--verbose', 'pub', 'run', '--foo', 'bar']);
512

513
      expect(processManager, hasNoRemainingExpectations);
514
    }, overrides: <Type, Generator>{
515
      FileSystem: () => MemoryFileSystem.test(),
516
      Platform: () => FakePlatform(environment: <String, String>{}),
517
      ProcessManager: () => processManager,
518
      Stdio: () => mockStdio,
519 520 521 522 523 524 525 526
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
527
    });
528

529 530 531 532 533
    testUsingContext('token pass arguments through to pub', () async {
      Cache.flutterRoot = '';
      globals.fs.file('pubspec.yaml').createSync();
      final IOSink stdin = IOSink(StreamController<List<int>>().sink);
      processManager.addCommand(
534 535 536 537
        FakeCommand(
          command: const <String>[
            '/bin/cache/dart-sdk/bin/dart', '__deprecated_pub', 'token', 'list',
          ],
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
          stdin: stdin,
        ),
      );
      await createTestCommandRunner(PackagesCommand()).run(<String>['packages', '--verbose', 'pub', 'token', 'list']);

      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      FileSystem: () => MemoryFileSystem.test(),
      Platform: () => FakePlatform(environment: <String, String>{}),
      ProcessManager: () => processManager,
      Stdio: () => mockStdio,
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
    });

559 560 561
    testUsingContext('upgrade does not check for pubspec.yaml if -h/--help is passed', () async {
      Cache.flutterRoot = '';
      processManager.addCommand(
562 563 564 565
        FakeCommand(
          command: const <String>[
            '/bin/cache/dart-sdk/bin/dart', '__deprecated_pub', 'upgrade', '-h',
          ],
566 567 568 569 570 571 572 573
          stdin:  IOSink(StreamController<List<int>>().sink),
        ),
      );
      await createTestCommandRunner(PackagesCommand()).run(<String>['pub', 'upgrade', '-h']);

      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      FileSystem: () => MemoryFileSystem.test(),
574
      Platform: () => FakePlatform(environment: <String, String>{}),
575 576 577 578 579 580 581 582 583 584 585
      ProcessManager: () => processManager,
      Stdio: () => mockStdio,
      Pub: () => Pub(
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
      ),
    });
586 587
  });
}