runMatrix function

Future<int> runMatrix(
  1. List<SmfModule> modules, {
  2. required String directory,
  3. Map<String, String?> roleOptions = const {},
  4. List<MatrixAppTest> appTests = const [],
  5. Set<String>? only,
  6. void log(
    1. String line
    )?,
  7. MatrixCommands commands = const MatrixCommands(),
})

Generates every app of the matrixOf of modules with roleOptions in directory, with the options of CI, analyzes each with flutter analyze, and, in an app that some of the appTests apply to, adds them and runs every test of the app with flutter test. Returns the exit code: 0 if every app was generated with every module and every step that the options of CI do not leave for later, has no issue and passes its tests, and each of the appTests applies to some app; 1 otherwise.

With only, it checks only the apps of the matrix with those names, such as every module (bloc), and runs the appTests that apply to them; a name that no app of the matrix has is a problem too.

log gets what happens, by default the standard output; the apps stay in directory, with the tests. commands run for each app.

Implementation

Future<int> runMatrix(
  List<SmfModule> modules, {
  required String directory,
  Map<String, String?> roleOptions = const {},
  List<MatrixAppTest> appTests = const [],
  Set<String>? only,
  void Function(String line)? log,
  MatrixCommands commands = const MatrixCommands(),
}) async {
  final run = _MatrixRun(
    directory: directory,
    appTests: appTests,
    // coverage:ignore-start
    // The defaults print to the terminal, create the apps with smf and
    // analyze them with Flutter, as the runs of the matrix in CI do; the
    // tests give their own.
    say: log ?? (String line) => stdout.writeln(line),
    create: commands.create ??
        (arguments, onCreated) => runCli(
              arguments,
              modules: modules,
              banner: false,
              onCreated: onCreated,
            ),
    analyze:
        commands.analyze ?? (directory) => _flutter(['analyze'], directory),
    // coverage:ignore-end
    test: commands.test ?? runAppTests,
  );
  final (:apps, :failed) = await matrixOf(modules, roleOptions: roleOptions);
  final problems = [
    for (final result in failed)
      '${result.contractCase}: ${result.errors.join('; ')}',
    for (final name in only ?? const <String>{})
      if (!apps.any((app) => app.name == name))
        'No app of the matrix is $name.',
  ];
  final checked = <MatrixApp>[];
  for (final (index, app) in apps.indexed) {
    if (only != null && !only.contains(app.name)) continue;
    checked.add(app);
    // An app keeps its number in the matrix when only some are checked.
    problems.addAll(await run.check(app, 'app_${index + 1}'));
  }
  // Tests that apply to no app would leave CI without saying so. Those of
  // the apps that are not checked run where the whole matrix is.
  for (final test in appTests) {
    if (!apps.any(test.appliesTo)) {
      problems.add('The tests of ${test.directory} apply to no app.');
    }
  }
  run.say('\n${checked.length} apps generated in $directory.');
  if (problems.isEmpty) return 0;
  run.say('Problems:');
  problems.forEach(run.say);
  return 1;
}