// Copyright 2014 The Flutter Authors. All rights reserved.
// 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';
import 'package:flutter_tools/src/base/bot_detector.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart' show ProcessException;
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';

import '../../src/common.dart';
import '../../src/fake_process_manager.dart';
import '../../src/fakes.dart';

void main() {
  setUpAll(() {
    Cache.flutterRoot = '';
  });

  testWithoutContext('Throws a tool exit if pub cannot be run', () async {
    final FakeProcessManager processManager = FakeProcessManager.empty();
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    processManager.excludedExecutables.add('bin/cache/dart-sdk/bin/dart');

    fileSystem.file('pubspec.yaml').createSync();

    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      usage: TestUsage(),
      platform: FakePlatform(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await expectLater(
        () => pub.get(
              project:
                  FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
              context: PubContext.pubGet,
              checkUpToDate: true,
            ),
        throwsToolExit(
            message:
                'Your Flutter SDK download may be corrupt or missing permissions to run'));
  });

  group('shouldSkipThirdPartyGenerator', () {
    testWithoutContext('does not skip pub get the parameter is false',
        () async {
      final FakeProcessManager processManager =
          FakeProcessManager.list(<FakeCommand>[
        const FakeCommand(command: <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ]),
      ]);
      final BufferLogger logger = BufferLogger.test();
      final MemoryFileSystem fileSystem = MemoryFileSystem.test();

      fileSystem.file('pubspec.yaml').createSync();
      fileSystem.file('pubspec.lock').createSync();
      fileSystem.file('version').writeAsStringSync('b');
      fileSystem.file('.dart_tool/package_config.json')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
  {
    "configVersion": 2,
    "packages": [],
    "generated": "2021-07-08T10:02:49.155589Z",
    "generator": "third-party",
    "generatorVersion": "2.14.0-276.0.dev"
  }
  ''');

      final Pub pub = Pub.test(
        fileSystem: fileSystem,
        logger: logger,
        processManager: processManager,
        usage: TestUsage(),
        platform: FakePlatform(),
        botDetector: const BotDetectorAlwaysNo(),
        stdio: FakeStdio(),
      );

      await pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.pubGet,
        checkUpToDate: true,
        shouldSkipThirdPartyGenerator: false,
      );

      expect(processManager, hasNoRemainingExpectations);
      expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b');
    });

    testWithoutContext(
        'does not skip pub get if package_config.json has "generator": "pub"',
        () async {
      final FakeProcessManager processManager =
          FakeProcessManager.list(<FakeCommand>[
        const FakeCommand(command: <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ]),
      ]);
      final BufferLogger logger = BufferLogger.test();
      final MemoryFileSystem fileSystem = MemoryFileSystem.test();

      fileSystem.file('pubspec.yaml').createSync();
      fileSystem.file('pubspec.lock').createSync();
      fileSystem.file('.dart_tool/package_config.json')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
  {
    "configVersion": 2,
    "packages": [],
    "generated": "2021-07-08T10:02:49.155589Z",
    "generator": "pub",
    "generatorVersion": "2.14.0-276.0.dev"
  }
  ''');
      fileSystem.file('.dart_tool/version').writeAsStringSync('a');
      fileSystem.file('version').writeAsStringSync('b');

      final Pub pub = Pub.test(
        fileSystem: fileSystem,
        logger: logger,
        processManager: processManager,
        usage: TestUsage(),
        platform: FakePlatform(),
        botDetector: const BotDetectorAlwaysNo(),
        stdio: FakeStdio(),
      );

      await pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.pubGet,
        checkUpToDate: true,
      );

      expect(processManager, hasNoRemainingExpectations);
      expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b');
    });

    testWithoutContext(
        'does not skip pub get if package_config.json has "generator": "pub"',
        () async {
      final FakeProcessManager processManager =
          FakeProcessManager.list(<FakeCommand>[
        const FakeCommand(command: <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ]),
      ]);
      final BufferLogger logger = BufferLogger.test();
      final MemoryFileSystem fileSystem = MemoryFileSystem.test();

      fileSystem.file('pubspec.yaml').createSync();
      fileSystem.file('pubspec.lock').createSync();
      fileSystem.file('.dart_tool/package_config.json')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
  {
    "configVersion": 2,
    "packages": [],
    "generated": "2021-07-08T10:02:49.155589Z",
    "generator": "pub",
    "generatorVersion": "2.14.0-276.0.dev"
  }
  ''');
      fileSystem.file('.dart_tool/version').writeAsStringSync('a');
      fileSystem.file('version').writeAsStringSync('b');

      final Pub pub = Pub.test(
        fileSystem: fileSystem,
        logger: logger,
        processManager: processManager,
        usage: TestUsage(),
        platform: FakePlatform(),
        botDetector: const BotDetectorAlwaysNo(),
        stdio: FakeStdio(),
      );

      await pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.pubGet,
        checkUpToDate: true,
      );

      expect(processManager, hasNoRemainingExpectations);
      expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b');
    });

    testWithoutContext(
        'skips pub get if the package config "generator" is '
        'different than "pub"', () async {
      final FakeProcessManager processManager = FakeProcessManager.empty();
      final BufferLogger logger = BufferLogger.test();
      final MemoryFileSystem fileSystem = MemoryFileSystem.test();

      fileSystem.file('pubspec.yaml').createSync();
      fileSystem.file('pubspec.lock').createSync();
      fileSystem.file('.dart_tool/package_config.json')
        ..createSync(recursive: true)
        ..writeAsStringSync('{"generator": "third-party"}');

      final Pub pub = Pub.test(
        fileSystem: fileSystem,
        logger: logger,
        processManager: processManager,
        usage: TestUsage(),
        platform: FakePlatform(),
        botDetector: const BotDetectorAlwaysNo(),
        stdio: FakeStdio(),
      );

      await pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.pubGet,
        checkUpToDate: true,
      );

      expect(
        logger.traceText,
        contains('Skipping pub get: generated by third-party.'),
      );
    });
  });

  testWithoutContext(
      'checkUpToDate skips pub get if the package config is newer than the pubspec '
      'and the current framework version is the same as the last version',
      () async {
    final FakeProcessManager processManager = FakeProcessManager.empty();
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();

    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('pubspec.lock').createSync();
    fileSystem
        .file('.dart_tool/package_config.json')
        .createSync(recursive: true);
    fileSystem.file('.dart_tool/version').writeAsStringSync('a');
    fileSystem.file('version').writeAsStringSync('a');

    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      usage: TestUsage(),
      platform: FakePlatform(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.pubGet,
      checkUpToDate: true,
    );

    expect(logger.traceText, contains('Skipping pub get: version match.'));
  });

  testWithoutContext(
      'checkUpToDate does not skip pub get if the package config is newer than the pubspec '
      'but the current framework version is not the same as the last version',
      () async {
    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(command: <String>[
        'bin/cache/dart-sdk/bin/dart',
        'pub',
        '--suppress-analytics',
        '--directory',
        '.',
        'get',
        '--example',
      ]),
    ]);
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();

    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('pubspec.lock').createSync();
    fileSystem
        .file('.dart_tool/package_config.json')
        .createSync(recursive: true);
    fileSystem.file('.dart_tool/version').writeAsStringSync('a');
    fileSystem.file('version').writeAsStringSync('b');

    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      usage: TestUsage(),
      platform: FakePlatform(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.pubGet,
      checkUpToDate: true,
    );

    expect(processManager, hasNoRemainingExpectations);
    expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b');
  });

  testWithoutContext(
      'checkUpToDate does not skip pub get if the package config is newer than the pubspec '
      'but the current framework version does not exist yet', () async {
    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(command: <String>[
        'bin/cache/dart-sdk/bin/dart',
        'pub',
        '--suppress-analytics',
        '--directory',
        '.',
        'get',
        '--example',
      ]),
    ]);
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();

    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('pubspec.lock').createSync();
    fileSystem
        .file('.dart_tool/package_config.json')
        .createSync(recursive: true);
    fileSystem.file('version').writeAsStringSync('b');

    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      usage: TestUsage(),
      platform: FakePlatform(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.pubGet,
      checkUpToDate: true,
    );

    expect(processManager, hasNoRemainingExpectations);
    expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b');
  });

  testWithoutContext(
      'checkUpToDate does not skip pub get if the package config does not exist',
      () async {
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      FakeCommand(
          command: const <String>[
            'bin/cache/dart-sdk/bin/dart',
            'pub',
            '--suppress-analytics',
            '--directory',
            '.',
            'get',
            '--example',
          ],
          onRun: () {
            fileSystem
                .file('.dart_tool/package_config.json')
                .createSync(recursive: true);
          }),
    ]);
    final BufferLogger logger = BufferLogger.test();

    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('pubspec.lock').createSync();
    fileSystem.file('version').writeAsStringSync('b');

    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      usage: TestUsage(),
      platform: FakePlatform(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.pubGet,
      checkUpToDate: true,
    );

    expect(processManager, hasNoRemainingExpectations);
    expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b');
  });

  testWithoutContext(
      'checkUpToDate does not skip pub get if the pubspec.lock does not exist',
      () async {
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(command: <String>[
        'bin/cache/dart-sdk/bin/dart',
        'pub',
        '--suppress-analytics',
        '--directory',
        '.',
        'get',
        '--example',
      ]),
    ]);
    final BufferLogger logger = BufferLogger.test();

    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('version').writeAsStringSync('b');
    fileSystem
        .file('.dart_tool/package_config.json')
        .createSync(recursive: true);
    fileSystem.file('.dart_tool/version').writeAsStringSync('b');

    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      usage: TestUsage(),
      platform: FakePlatform(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.pubGet,
      checkUpToDate: true,
    );

    expect(processManager, hasNoRemainingExpectations);
    expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b');
  });

  testWithoutContext(
      'checkUpToDate does not skip pub get if the package config is older that the pubspec',
      () async {
    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(command: <String>[
        'bin/cache/dart-sdk/bin/dart',
        'pub',
        '--suppress-analytics',
        '--directory',
        '.',
        'get',
        '--example',
      ]),
    ]);
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();

    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('pubspec.lock').createSync();
    fileSystem.file('.dart_tool/package_config.json')
      ..createSync(recursive: true)
      ..setLastModifiedSync(DateTime(1991));
    fileSystem.file('version').writeAsStringSync('b');

    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      usage: TestUsage(),
      platform: FakePlatform(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.pubGet,
      checkUpToDate: true,
    );

    expect(processManager, hasNoRemainingExpectations);
    expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b');
  });

  testWithoutContext(
      'checkUpToDate does not skip pub get if the pubspec.lock is older that the pubspec',
      () async {
    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(command: <String>[
        'bin/cache/dart-sdk/bin/dart',
        'pub',
        '--suppress-analytics',
        '--directory',
        '.',
        'get',
        '--example',
      ]),
    ]);
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();

    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('pubspec.lock')
      ..createSync()
      ..setLastModifiedSync(DateTime(1991));
    fileSystem
        .file('.dart_tool/package_config.json')
        .createSync(recursive: true);
    fileSystem.file('version').writeAsStringSync('b');
    fileSystem.file('.dart_tool/version').writeAsStringSync('b');

    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      usage: TestUsage(),
      platform: FakePlatform(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.pubGet,
      checkUpToDate: true,
    );

    expect(processManager, hasNoRemainingExpectations);
    expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b');
  });

  testWithoutContext('pub get 66 shows message from pub', () async {
    final BufferLogger logger = BufferLogger.test();
    final FileSystem fileSystem = MemoryFileSystem.test();

    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(
        command: <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ],
        exitCode: 66,
        stderr: 'err1\nerr2\nerr3\n',
        stdout: 'out1\nout2\nout3\n',
        environment: <String, String>{
          'FLUTTER_ROOT': '',
          'PUB_ENVIRONMENT': 'flutter_cli:flutter_tests'
        },
      ),
    ]);
    final FakeStdio mockStdio = FakeStdio();
    final Pub pub = Pub.test(
      platform: FakePlatform(),
      fileSystem: fileSystem,
      logger: logger,
      usage: TestUsage(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: mockStdio,
      processManager: processManager,
    );
    const String toolExitMessage = '''
pub get failed
command: "bin/cache/dart-sdk/bin/dart pub --suppress-analytics --directory . get --example"
pub env: {
  "FLUTTER_ROOT": "",
  "PUB_ENVIRONMENT": "flutter_cli:flutter_tests",
}
exit code: 66
''';
    await expectLater(
      () => pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.flutterTests,
      ),
      throwsA(isA<ToolExit>()
          .having((ToolExit error) => error.message, 'message', null)),
    );
    expect(logger.statusText, isEmpty);
    expect(logger.traceText, contains(toolExitMessage));
    expect(mockStdio.stdout.writes.map(utf8.decode), <String>[
      'out1\nout2\nout3\n',
    ]);
    expect(mockStdio.stderr.writes.map(utf8.decode), <String>[
      'err1\nerr2\nerr3\n',
    ]);
    expect(processManager, hasNoRemainingExpectations);
  });

  testWithoutContext('pub get shows working directory on process exception',
      () async {
    final BufferLogger logger = BufferLogger.test();
    final FileSystem fileSystem = MemoryFileSystem.test();

    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      FakeCommand(
        command: const <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ],
        onRun: () {
          throw const ProcessException(
            'bin/cache/dart-sdk/bin/dart',
            <String>[
              'pub',
              '--suppress-analytics',
              '--directory',
              '.',
              'get',
              '--example',
            ],
            'message',
            1,
          );
        },
        exitCode: 66,
        stderr: 'err1\nerr2\nerr3\n',
        stdout: 'out1\nout2\nout3\n',
        environment: const <String, String>{
          'FLUTTER_ROOT': '',
          'PUB_ENVIRONMENT': 'flutter_cli:flutter_tests'
        },
      ),
    ]);

    final Pub pub = Pub.test(
      platform: FakePlatform(),
      fileSystem: fileSystem,
      logger: logger,
      usage: TestUsage(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
      processManager: processManager,
    );
    await expectLater(
      () => pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.flutterTests,
      ),
      throwsA(
        isA<ProcessException>()
            .having(
              (ProcessException error) => error.message,
              'message',
              contains('Working directory: "/" (exists)'),
            )
            .having(
              (ProcessException error) => error.message,
              'message',
              contains('"PUB_ENVIRONMENT": "flutter_cli:flutter_tests"'),
            ),
      ),
    );
    expect(logger.statusText, isEmpty);
    expect(logger.errorText, isEmpty);
    expect(processManager, hasNoRemainingExpectations);
  });

  // Regression test for https://github.com/flutter/flutter/issues/116627
  testWithoutContext('pub get suppresses progress output', () async {
    final BufferLogger logger = BufferLogger.test();
    final FileSystem fileSystem = MemoryFileSystem.test();

    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(
        command: <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ],
        stderr: 'err1\nerr2\nerr3\n',
        stdout: 'out1\nout2\nout3\n',
        environment: <String, String>{
          'FLUTTER_ROOT': '',
          'PUB_ENVIRONMENT': 'flutter_cli:flutter_tests'
        },
      ),
    ]);

    final FakeStdio mockStdio = FakeStdio();
    final Pub pub = Pub.test(
      platform: FakePlatform(),
      usage: TestUsage(),
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      botDetector: const BotDetectorAlwaysNo(),
      stdio: mockStdio,
    );

    try {
      await pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.flutterTests,
        outputMode: PubOutputMode.none,
      );
    } on ToolExit {
      // Ignore.
    }

    expect(
        mockStdio.stdout.writes.map(utf8.decode),
        isNot(<String>[
          'out1\nout2\nout3\n',
        ]));
    expect(processManager, hasNoRemainingExpectations);
  });

  testWithoutContext('pub cache in flutter root is ignored', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(
          command: <String>[
            'bin/cache/dart-sdk/bin/dart',
            'pub',
            '--suppress-analytics',
            '--directory',
            '.',
            'get',
            '--example',
          ],
          exitCode: 69,
          environment: <String, String>{
            'FLUTTER_ROOT': '',
            'PUB_ENVIRONMENT': 'flutter_cli:flutter_tests',
          },
          stdout: "FakeCommand's env successfully matched"),
    ]);

    final FakeStdio mockStdio = FakeStdio();
    final Pub pub = Pub.test(
      platform: FakePlatform(),
      usage: TestUsage(),
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      processManager: processManager,
      botDetector: const BotDetectorAlwaysNo(),
      stdio: mockStdio,
    );

    try {
      await pub.get(
          project:
              FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          context: PubContext.flutterTests);
    } on ToolExit {
      // Ignore.
    }

    expect(mockStdio.stdout.writes.map(utf8.decode), <String>[
      "FakeCommand's env successfully matched",
    ]);
    expect(processManager, hasNoRemainingExpectations);
  });

  testWithoutContext('Preloaded packages are added to the pub cache', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Directory preloadCache =
        fileSystem.currentDirectory.childDirectory('.pub-preload-cache');
    preloadCache.childFile('a.tar.gz').createSync(recursive: true);
    preloadCache.childFile('b.tar.gz').createSync();
    fileSystem.currentDirectory.childFile('version').createSync();

    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(
        command: <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          'cache',
          'preload',
          '.pub-preload-cache/a.tar.gz',
          '.pub-preload-cache/b.tar.gz',
        ],
      ),
      FakeCommand(
          command: const <String>[
            'bin/cache/dart-sdk/bin/dart',
            'pub',
            '--suppress-analytics',
            '--directory',
            '.',
            'get',
            '--example',
          ],
          environment: const <String, String>{
            'FLUTTER_ROOT': '',
            'PUB_ENVIRONMENT': 'flutter_cli:flutter_tests',
          },
          onRun: () {
            fileSystem.currentDirectory
                .childDirectory('.dart_tool')
                .childFile('package_config.json')
                .createSync(recursive: true);
          }),
    ]);

    final Platform platform =
        FakePlatform(environment: <String, String>{'HOME': '/global'});
    final BufferLogger logger = BufferLogger.test();
    final Pub pub = Pub.test(
      platform: platform,
      usage: TestUsage(),
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.flutterTests);
    expect(logger.statusText, isNot(contains('Found an existing Pub cache')));
    expect(logger.statusText,
        isNot(contains('Found an existing Dart Analysis Server cache')));

    expect(processManager, hasNoRemainingExpectations);
    expect(preloadCache.existsSync(), false);
  });

  testWithoutContext('Notifies about existing caches, on first run only',
      () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final Directory preloadCache =
        fileSystem.currentDirectory.childDirectory('.pub-preload-cache');
    preloadCache.childFile('a.tar.gz').createSync(recursive: true);
    fileSystem.currentDirectory.childFile('version').createSync();
    fileSystem.directory('/global/.pub-cache').createSync(recursive: true);
    fileSystem.directory('/global/.dartServer').createSync(recursive: true);

    const FakeCommand dartPreloadCommand = FakeCommand(
      command: <String>[
        'bin/cache/dart-sdk/bin/dart',
        'pub',
        '--suppress-analytics',
        'cache',
        'preload',
        '.pub-preload-cache/a.tar.gz',
      ],
    );
    final FakeCommand dartPubGetCommand = FakeCommand(
        command: const <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ],
        environment: const <String, String>{
          'FLUTTER_ROOT': '',
          'PUB_ENVIRONMENT': 'flutter_cli:flutter_tests',
        },
        onRun: () {
          fileSystem.currentDirectory
              .childDirectory('.dart_tool')
              .childFile('package_config.json')
              .createSync(recursive: true);
        });

    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      dartPreloadCommand,
      dartPubGetCommand,
      dartPubGetCommand,
    ]);

    final Platform platform =
        FakePlatform(environment: <String, String>{'HOME': '/global'});
    final BufferLogger logger = BufferLogger.test();
    final Pub pub = Pub.test(
      platform: platform,
      usage: TestUsage(),
      fileSystem: fileSystem,
      logger: logger,
      processManager: processManager,
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
    );

    await pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.flutterTests);
    expect(logger.statusText,
        contains('Found an existing Pub cache at /global/.pub-cache'));
    expect(logger.statusText,
        contains('It can be reset by running `dart pub cache clean`'));
    expect(
      logger.statusText,
      contains(
          'Found an existing Dart Analysis Server cache at /global/.dartServer'),
    );
    expect(preloadCache.existsSync(), false);
    logger.clear();
    await pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.flutterTests);
    expect(logger.statusText, isNot(contains('Found an existing Pub cache')));
    expect(logger.statusText,
        isNot(contains('Found an existing Dart Analysis Server cache')));
    expect(processManager, hasNoRemainingExpectations);
  });

  testWithoutContext('pub cache in environment is used', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    fileSystem.directory('custom/pub-cache/path').createSync(recursive: true);
    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(
          command: <String>[
            'bin/cache/dart-sdk/bin/dart',
            'pub',
            '--suppress-analytics',
            '--directory',
            '.',
            'get',
            '--example',
          ],
          exitCode: 69,
          environment: <String, String>{
            'FLUTTER_ROOT': '',
            'PUB_ENVIRONMENT': 'flutter_cli:flutter_tests',
            'PUB_CACHE': 'custom/pub-cache/path',
          },
          stdout: "FakeCommand's env successfully matched"),
    ]);

    final FakeStdio mockStdio = FakeStdio();
    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      processManager: processManager,
      usage: TestUsage(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: mockStdio,
      platform: FakePlatform(
        environment: const <String, String>{
          'PUB_CACHE': 'custom/pub-cache/path',
        },
      ),
    );

    try {
      await pub.get(
          project:
              FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          context: PubContext.flutterTests);
    } on ToolExit {
      // Ignore.
    }

    expect(mockStdio.stdout.writes.map(utf8.decode), <String>[
      "FakeCommand's env successfully matched",
    ]);
    expect(processManager, hasNoRemainingExpectations);
  });

  testWithoutContext('analytics sent on success', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final TestUsage usage = TestUsage();
    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      processManager: FakeProcessManager.any(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
      usage: usage,
      platform: FakePlatform(environment: const <String, String>{
        'PUB_CACHE': 'custom/pub-cache/path',
      }),
    );
    fileSystem.file('version').createSync();
    fileSystem.file('pubspec.yaml').createSync();
    fileSystem.file('.dart_tool/package_config.json')
      ..createSync(recursive: true)
      ..writeAsStringSync('{"configVersion": 2,"packages": []}');

    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.flutterTests,
    );
    expect(
        usage.events,
        contains(
          const TestUsageEvent('pub-result', 'flutter-tests', label: 'success'),
        ));
  });

  testWithoutContext(
      'package_config_subset file is generated from packages and not timestamp',
      () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    final TestUsage usage = TestUsage();
    final Pub pub = Pub.test(
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      processManager: FakeProcessManager.any(),
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
      usage: usage,
      platform: FakePlatform(environment: const <String, String>{
        'PUB_CACHE': 'custom/pub-cache/path',
      }),
    );
    fileSystem.file('version').createSync();
    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(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.flutterTests,
    );

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

  testWithoutContext('analytics sent on failure', () async {
    final FileSystem fileSystem = MemoryFileSystem.test();
    fileSystem.directory('custom/pub-cache/path').createSync(recursive: true);
    final TestUsage usage = TestUsage();

    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      const FakeCommand(
        command: <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ],
        exitCode: 1,
      ),
    ]);

    final Pub pub = Pub.test(
      usage: usage,
      fileSystem: fileSystem,
      logger: BufferLogger.test(),
      processManager: processManager,
      botDetector: const BotDetectorAlwaysNo(),
      stdio: FakeStdio(),
      platform: FakePlatform(
        environment: const <String, String>{
          'PUB_CACHE': 'custom/pub-cache/path',
        },
      ),
    );
    try {
      await pub.get(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        context: PubContext.flutterTests,
      );
    } on ToolExit {
      // Ignore.
    }

    expect(
        usage.events,
        contains(
          const TestUsageEvent('pub-result', 'flutter-tests', label: 'failure'),
        ));
    expect(processManager, hasNoRemainingExpectations);
  });

  testWithoutContext('Pub error handling', () async {
    final BufferLogger logger = BufferLogger.test();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final FakeProcessManager processManager =
        FakeProcessManager.list(<FakeCommand>[
      FakeCommand(
          command: const <String>[
            'bin/cache/dart-sdk/bin/dart',
            'pub',
            '--suppress-analytics',
            '--directory',
            '.',
            'get',
            '--example',
          ],
          onRun: () {
            fileSystem
                .file('.dart_tool/package_config.json')
                .setLastModifiedSync(DateTime(2002));
          }),
      const FakeCommand(
        command: <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ],
      ),
      FakeCommand(
          command: const <String>[
            'bin/cache/dart-sdk/bin/dart',
            'pub',
            '--suppress-analytics',
            '--directory',
            '.',
            'get',
            '--example',
          ],
          onRun: () {
            fileSystem.file('pubspec.yaml').setLastModifiedSync(DateTime(2002));
          }),
      const FakeCommand(
        command: <String>[
          'bin/cache/dart-sdk/bin/dart',
          'pub',
          '--suppress-analytics',
          '--directory',
          '.',
          'get',
          '--example',
        ],
      ),
    ]);
    final Pub pub = Pub.test(
        usage: TestUsage(),
        fileSystem: fileSystem,
        logger: logger,
        processManager: processManager,
        platform: FakePlatform(
          environment: <String, String>{},
        ),
        botDetector: const BotDetectorAlwaysNo(),
        stdio: FakeStdio());

    fileSystem.file('version').createSync();
    // 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));
    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.flutterTests,
    ); // pub sets date of .packages to 2002

    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));
    await pub.get(
      project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
      context: PubContext.flutterTests,
    ); // pub does nothing

    expect(logger.statusText, isEmpty);
    expect(logger.errorText, isEmpty);
    expect(fileSystem.file('pubspec.yaml').lastModifiedSync(),
        DateTime(2001)); // because nothing should touch it
    logger.clear();
  });
}

class BotDetectorAlwaysNo implements BotDetector {
  const BotDetectorAlwaysNo();

  @override
  Future<bool> get isRunningOnBot async => false;
}