run method

  1. @override
Future<ExitCode> run([
  1. List<String>? args
])
override

Runs this command.

The return value is wrapped in a Future if necessary and returned by CommandRunner.runCommand.

Implementation

@override
Future<ExitCode> run([List<String>? args]) async {
  final argResults = args != null ? argParser.parse(args) : super.argResults!;

  final isRecursive = argResults['recursive'] as bool;
  final isDartOnly =
      argResults.wasParsed('dart-only') && argResults['dart-only'] as bool;
  final isFlutterOnly = argResults.wasParsed('flutter-only') &&
      argResults['flutter-only'] as bool;
  final concurrent = argResults['concurrent'] as bool;
  final clean = argResults['clean'] as bool;

  final optimize = argResults['optimize'] as bool;
  final runTestType =
      TestScope.options[argResults['scope'] as String] ?? TestScope.active;

  warnDartOrFlutterTests(
    isFlutterOnly: isFlutterOnly,
    isDartOnly: isDartOnly,
  );

  final pubspecs = await pubspecYaml.all(recursive: isRecursive);

  final testDirsResult = getTestDirs(
    pubspecs,
    isFlutterOnly: isFlutterOnly,
    isDartOnly: isDartOnly,
  );

  if (testDirsResult.$2 case final ExitCode exitCode) {
    return exitCode;
  }

  final (testDirs, dirTools) = testDirsResult.$1!;

  final testsResult = getPackagesToTest(
    testDirs,
    dirTools,
    optimize: optimize,
  );

  // exit code is not null
  if (testsResult.$2 case final ExitCode exitCode) {
    return exitCode;
  }

  final tests = testsResult.$1!;

  final libDirs = testDirs.map((e) => e.replaceAll(RegExp(r'test$'), 'lib'));

  final (:both, :dart, :flutter) = getArgs(this);

  final flutterArgs = [...flutter, ...both];
  final dartArgs = [...dart, ...both];

  var printMessage = true;

  logger.detail(
    'Watching directories: ${[
      ...testDirs,
      ...libDirs,
    ].map(path.relative).join(', ')}',
  );

  var runType = runTestType;
  var runConcurrently = concurrent;

  Iterable<PackageToTest>? lastTests;

  // This setup up will not include any new packages created,
  // only the ones that exist when the command is run
  while (true) {
    if (printMessage) {
      writeWaitingMessage(
        runType,
        runConcurrently: runConcurrently,
      );
    }

    printMessage = false;

    final (type: event, :file) = await waitForChange(
      testDirs: testDirs,
      libDirs: libDirs,
      printMessage: () => writeWaitingMessage(
        runType,
        runConcurrently: runConcurrently,
      ),
    );

    if (event.isExit) {
      break;
    }

    if (event.isToggleModified) {
      runType = TestScope.toggle(runType);
      printMessage = true;
      lastTests = null;
      continue;
    }

    if (event.isToggleConcurrency) {
      runConcurrently = !runConcurrently;
      printMessage = true;
      continue;
    }

    final testsToRun = <PackageToTest>{};

    if (!event.isRun) {
      if (file == null) {
        logger.detail('No file changed, waiting for changes...');
        continue;
      }

      if (file.endsWith(TesterMixin.optimizedTestBasename)) {
        logger.detail('Optimized test file changed, waiting for changes...');
        continue;
      }
    }

    if (runType.isAll) {
      logger.info('Running all tests');

      testsToRun.addAll(tests);
    } else if (file == null) {
      logger.info('Checking for last tests run...');
      await Future<void>.delayed(const Duration(milliseconds: 100));
      if (lastTests == null) {
        logger.info(
          red.wrap('No previous tests found, modify a file to run tests'),
        );

        printMessage = true;
        continue;
      }

      testsToRun.addAll(lastTests);
    } else {
      final packageToTest = await findTest(
        tests,
        file,
        returnTestFile: runType.isModified,
      );

      if (packageToTest == null) {
        logger.detail('No test found for $file, waiting for changes...');
        continue;
      }

      logger.info('Running tests for ${path.relative(file)}');

      testsToRun.add(packageToTest);
    }

    lastTests = testsToRun;

    printMessage = true;

    final commandsToRun = getCommandsToRun(
      testsToRun,
      flutterArgs: flutterArgs,
      dartArgs: dartArgs,
    );

    final exitCode = await runCommands(
      commandsToRun,
      runConcurrently: runConcurrently,
      bail: false,
    );

    if (exitCode != ExitCode.success) {
      logger.err('${red.wrap('✗')} Some tests failed');
    } else {
      logger.write('${green.wrap('✔')} Tests passed');
    }
  }

  if (optimize && clean) {
    final done = logger.progress('Cleaning up optimized test files');

    cleanUpOptimizedFiles(tests.map((e) => e.optimizedPath));

    done.complete('Optimized test files cleaned!');
  }

  return ExitCode.success;
}