measureCoverage function
Measures blueprintYaml against the model sample sampleYaml and the model
metadata metaJson, with the row table rowsYaml and the reason rules
reasonsYaml; oldBlueprintYaml, when given, fills the "old" columns.
Implementation
CoverageReport measureCoverage({
required String sampleYaml,
required String metaJson,
required String blueprintYaml,
required String rowsYaml,
required String reasonsYaml,
String? oldBlueprintYaml,
}) {
// One-of case arms, from the model metadata.
final meta =
(jsonDecode(metaJson) as Map<String, dynamic>)['classes']
as Map<String, dynamic>;
final caseKeys = <String>{};
meta.forEach((_, c) {
final cls = c as Map<String, dynamic>;
final annotations = (cls['annotations'] as List?) ?? const [];
if (!annotations.any((a) => (a as Map)['name'] == 'OneOf')) return;
for (final f in (cls['fields'] as List)) {
final fm = f as Map<String, dynamic>;
if (!((fm['annotations'] as List?) ?? const []).any(
(a) => (a as Map)['name'] == 'Case',
)) {
continue;
}
final sid =
fm['sectionId'] ??
((meta[fm['type']] as Map<String, dynamic>?)?['sectionId']);
caseKeys.add('$sid\u0000${fm['name']}');
}
});
// The model's sections, from the sample.
final model = <String, _ModelSection>{};
var idless = 0;
void walkSample(
Object? node,
List<String> fpath,
List<String> ipath,
bool inList,
List<String> variant,
bool refexp,
) {
if (node is! YamlMap) return;
node.forEach((key, v) {
final k = '$key';
if (k == 'content' || k == 'headline') return;
if (!k.contains(' ')) {
if (inList) {
// An item is named by its class id: the sample numbers its items
// `<class>-<kind>-<n>`, and a reason rule names the class.
final classId = k.split('-').first;
walkSample(v, fpath, [...ipath, classId], false, variant, refexp);
} else if (k[0].toLowerCase() == k[0] && k[0].toUpperCase() != k[0]) {
idless++;
}
return;
}
final space = k.indexOf(' ');
final sid = k.substring(0, space);
final name = k.substring(space + 1);
final fp = [...fpath, name];
final ip = [...ipath, sid];
final fpKey = fp.join('/');
final variants = [
...variant,
if (caseKeys.contains('$sid\u0000$name')) fpKey,
];
if (_isBlock(v)) model[fpKey] = _ModelSection(ip, variants, refexp);
walkSample(
v,
fp,
ip,
sid.endsWith('-LST'),
variants,
refexp || sid.endsWith('-REF'),
);
});
}
walkSample(_document(sampleYaml), const [], const [], false, const [], false);
// The Blueprint's paths.
({Set<String> present, Set<String> filled, Set<String> block}) walkBlueprint(
String text,
) {
final present = <String>{}, filled = <String>{}, block = <String>{};
void w(Object? node, List<String> fpath, bool inList) {
if (node is! YamlMap) return;
node.forEach((key, v) {
final k = '$key';
if (k == 'content' || k == 'headline') return;
if (!k.contains(' ')) {
if (inList) w(v, fpath, false);
return;
}
final space = k.indexOf(' ');
final sid = k.substring(0, space);
final fp = [...fpath, k.substring(space + 1)];
final fpKey = fp.join('/');
present.add(fpKey);
if (_isBlock(v)) block.add(fpKey);
if (_nonEmpty(v)) filled.add(fpKey);
w(v, fp, sid.endsWith('-LST'));
});
}
w(_document(text), const [], false);
return (present: present, filled: filled, block: block);
}
final bp = walkBlueprint(blueprintYaml);
final old = oldBlueprintYaml == null ? null : walkBlueprint(oldBlueprintYaml);
// Rows and rules.
final report = CoverageReport();
final prefixes = <String, List<String>>{};
for (final row in (loadYaml(rowsYaml) as YamlMap)['rows'] as YamlList) {
final id = '${row['id']}';
report.rows.add(id);
report.labels[id] = '${row['label']}';
prefixes[id] = [for (final p in row['prefixes'] as YamlList) '$p'];
}
String owner(String path) {
String? best;
var bestLength = -1;
prefixes.forEach((g, list) {
for (final pre in list) {
if ((path == pre || path.startsWith('$pre/')) &&
pre.length > bestLength) {
best = g;
bestLength = pre.length;
}
}
});
return best!;
}
final rules = [
for (final r in (loadYaml(reasonsYaml) as YamlMap)['rules'] as YamlList)
(RegExp('${r['pattern']}'), '${r['class']}', '${r['reason']}'),
];
void inc(Map<String, int> m, String g) => m[g] = (m[g] ?? 0) + 1;
model.forEach((fp, m) {
final g = owner(fp);
inc(report.model, g);
if (bp.filled.contains(fp)) inc(report.filled, g);
if (bp.block.contains(fp)) inc(report.block, g);
if (old != null) {
if (old.block.contains(fp)) inc(report.oldPresent, g);
if (old.filled.contains(fp)) inc(report.oldFilled, g);
}
if (bp.filled.contains(fp)) return;
var kind = '';
if (m.referenceExpansion) {
inc(report.referenceExpansions, g);
kind = 'R';
} else if (m.variants.any((a) {
final parent = a.contains('/') ? a.substring(0, a.lastIndexOf('/')) : '';
return !bp.filled.contains(a) && bp.filled.contains(parent);
})) {
inc(report.variants, g);
kind = 'V';
}
report.unfilled.add(
UnfilledSection(
g,
m.ids.join(' > '),
fp,
kind,
bp.present.contains(fp) ? 'present-empty' : 'absent',
),
);
});
int cmp(UnfilledSection a, UnfilledSection b) {
for (final pair in [
(a.row, b.row),
(a.idPath, b.idPath),
(a.fieldPath, b.fieldPath),
(a.kind, b.kind),
(a.state, b.state),
]) {
final c = pair.$1.compareTo(pair.$2);
if (c != 0) return c;
}
return 0;
}
report.unfilled.sort(cmp);
for (final u in report.unfilled) {
for (final (pattern, cls, why) in rules) {
if (pattern.hasMatch(u.idPath)) {
u.cls = cls;
u.reason = why;
break;
}
}
if (u.kind == 'R') {
u.cls = 'R';
u.reason = 'sample-only reference expansion';
}
if (u.kind == 'V') {
u.cls = 'V';
u.reason = 'variant arm (OneOf case) unused by any item; ${u.reason}';
}
final counts = report.classes.putIfAbsent(u.row, () => {});
counts[u.cls] = (counts[u.cls] ?? 0) + 1;
}
report.idlessSampleSections = idless;
return report;
}