matrixOf function

Future<({List<MatrixApp> apps, List<ContractResult> failed})> matrixOf(
  1. List<SmfModule> modules, {
  2. Map<String, String?> roleOptions = const {},
  3. EveryModuleSelection everyModuleApps = EveryModuleCombinations.all,
})

The apps of the matrix of modules: every app that the contract harness builds for them, which covers every module and every provider with each subset of the roles it uses, and the apps with every module, one for each combination of the providers of roles that take one (see everyModuleAppsOf).

Each app gets roleOptions, the values of role options for every app, the options of its case, and the answers of the harness to the questions of the roles that they leave open (see ContractResult.answers), such as --start with the first of several screens that can start the app: smf create then makes the same choices without a terminal. It gets no answer to the question of a mode option (see RoleOption.mode): the role chooses the first value of such an option without it, in a terminal and in a run without one, as the harness checks. So smf create generates an app with the first value without the option, as for a user who does not give it, and an app of another value with the option of its case. Each app gets the choices too, with the data and roles of its case (MatrixApp.hook). The harness renders each app in memory first, so a case that it finds errors in is among the failed ones, since its app could not be generated.

An app with every module that another case built already is that app, which then has the MatrixApp.everyModuleWith of the app with every module. The apps with every module are those of everyModuleApps, such as a pairwise covering of them; the contract harness checks all of them anyway, and those that fail are among the failed ones.

The last apps are the apps with every module for the other values of the mode options of the roles (modeAppsOf), those that everyModuleApps takes next to its apps with every module (see EveryModuleSelection.selectModes). They come after every other app, so that the others have the same positions, and with them the same numbers in a run, whichever of these apps a selection takes.

Implementation

Future<({List<MatrixApp> apps, List<ContractResult> failed})> matrixOf(
  List<SmfModule> modules, {
  Map<String, String?> roleOptions = const {},
  EveryModuleSelection everyModuleApps = EveryModuleCombinations.all,
}) async {
  final harness = ContractHarness(
    ModuleRegistry(modules),
    roleOptions: roleOptions,
  );
  final apps = <MatrixApp>[];
  final failed = <ContractResult>[];
  for (final result in await harness.checkAll()) {
    switch (_appOf(result, harness)) {
      case final app?:
        apps.add(app);
      case null:
        failed.add(result);
    }
  }
  final everyModule = await everyModuleAppsOf(
    modules,
    roleOptions: roleOptions,
  );
  final modes = await modeAppsOf(modules, roleOptions: roleOptions);
  failed
    ..addAll(everyModule.failed)
    ..addAll(modes.failed);
  final selected = everyModuleApps.select(everyModule.apps, modules);
  for (final app in [
    ...selected,
    ...everyModuleApps.selectModes(modes.apps, selected, modules),
  ]) {
    final index = apps.indexWhere((other) => _keyOf(other) == _keyOf(app));
    if (index < 0) {
      apps.add(app);
    } else {
      apps[index] = apps[index]._with(everyModuleWith: app.everyModuleWith);
    }
  }
  return (apps: apps, failed: failed);
}