modeProblems method
The problems of the tests that read what says how the matrix
generates an app of apps, the apps of a matrix, rather than what the
app is: its name, its MatrixApp.roleOptions or its
MatrixApp.modes. Each problem tells which of them a test read, what
the test would do otherwise without it, and in which apps: apply to
one of the two apps only, fill other values, generate other files or
fail. The other app has another name, no role options or no modes,
and the same modules and the same data and choices of its roles
(MatrixApp.hook).
The name and the options of an app are how the matrix generates it,
such as --clock-hours=12 for the app of another value of a mode
option (see RoleOption.mode). What the app is, its roles chose: a
role chooses the first value of a mode option for an app that gets no
value of it, and an option may be given to an app whose roles do not
read it. So a test takes the mode of an app from the choice of the
role, with a function of the role that reads it from the input of its
hooks (Role.hookInput), as it takes the route that the app starts on
with routerRole.startIn(routerRole.hookInput(app.hook!)).
The rule is about where a test reads the mode, not about the apps that it runs in: a test may apply only to the apps in which a role chose one value, which it reads from the hook.
Implementation
List<String> modeProblems(List<MatrixApp> apps) {
final problems = <String>[];
for (final test in tests) {
// The apps in which the test read each thing, with each effect, in
// the order found.
final seen = <(_CaseRead, _CaseEffect), List<String>>{};
for (final app in apps) {
for (final read in _readsOf(test, app)) {
(seen[read] ??= []).add(app.name);
}
}
for (final MapEntry(key: (read, effect), value: names) in seen.entries) {
problems.add(_byCase(test, read, effect, names));
}
}
return problems;
}