spec-controller 0.1.0-alpha.3 → 0.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-args.d.ts +1 -1
- package/dist/cli-args.js +1 -1
- package/dist/cli-balance/cli.d.ts +18 -1
- package/dist/cli-balance/cli.d.ts.map +1 -1
- package/dist/cli-balance/cli.js +159 -149
- package/dist/cli-balance/cli.js.map +1 -1
- package/dist/cli-registry.d.ts.map +1 -1
- package/dist/cli-registry.js +0 -5
- package/dist/cli-registry.js.map +1 -1
- package/dist/corpus/cli.d.ts.map +1 -1
- package/dist/corpus/cli.js +1 -4
- package/dist/corpus/cli.js.map +1 -1
- package/dist/deferralTags.d.ts +2 -2
- package/dist/deferralTags.js +2 -2
- package/dist/ingest/gherkinValidation.d.ts +6 -5
- package/dist/ingest/gherkinValidation.d.ts.map +1 -1
- package/dist/ingest/gherkinValidation.js +6 -5
- package/dist/ingest/gherkinValidation.js.map +1 -1
- package/dist/ingest/ingestQualityChecks.d.ts +0 -25
- package/dist/ingest/ingestQualityChecks.d.ts.map +1 -1
- package/dist/ingest/ingestQualityChecks.js +7 -79
- package/dist/ingest/ingestQualityChecks.js.map +1 -1
- package/package.json +2 -7
- package/dist/mutation-ratchet/index.d.ts +0 -46
- package/dist/mutation-ratchet/index.d.ts.map +0 -1
- package/dist/mutation-ratchet/index.js +0 -46
- package/dist/mutation-ratchet/index.js.map +0 -1
- package/dist/mutation-ratchet/record.d.ts +0 -178
- package/dist/mutation-ratchet/record.d.ts.map +0 -1
- package/dist/mutation-ratchet/record.js +0 -314
- package/dist/mutation-ratchet/record.js.map +0 -1
- package/dist/mutation-ratchet/report.d.ts +0 -109
- package/dist/mutation-ratchet/report.d.ts.map +0 -1
- package/dist/mutation-ratchet/report.js +0 -156
- package/dist/mutation-ratchet/report.js.map +0 -1
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A GATE'S RECORD — the pair of counts it last banked, and the allowance stated against them
|
|
3
|
-
* (@SCN-MUT-012, 3F-2790, 3F-3337).
|
|
4
|
-
*
|
|
5
|
-
* THE RATCHET THAT ONCE RECONCILED AGAINST THIS IS GONE; THE READER IS NOT (3F-3337). What reaches
|
|
6
|
-
* this module now is the engine's `mutationGate` bar kind, through `ingestQualityChecks.ts`: a
|
|
7
|
-
* target may author a fitness row whose bar names a gate rather than a figure, and the kill floor
|
|
8
|
-
* that gate's record implies is the bar the ledger then holds it to. That is a published
|
|
9
|
-
* capability, decided over a TARGET'S record, and this repository no longer keeps one of its own.
|
|
10
|
-
* @SCN-MUT-012 and @SCN-MUT-013 are what specify it — the second carrying, as its own Examples,
|
|
11
|
-
* the record that holds no gate of that name, the record that is not there to be read and the
|
|
12
|
-
* record holding text nothing can read as a record.
|
|
13
|
-
*
|
|
14
|
-
* THE RECORD IS TWO COUNTS, NEVER A BARE SCORE, and that is the load-bearing choice this whole
|
|
15
|
-
* module exists to make possible. A mutation score is detected over total, so it falls for two
|
|
16
|
-
* entirely unrelated reasons: a mutant this suite used to kill now survives, which is the fault
|
|
17
|
-
* the gate exists for; or new mutable code arrived carrying survivors with it, which is no fault
|
|
18
|
-
* at all. One number reds on both, and the only cheap way out of a red for the second is to widen
|
|
19
|
-
* the bar — which is how a floor loses its teeth. Keeping `detected` and `total` apart is what
|
|
20
|
-
* lets the verdict tell them apart: kills lost is stated directly, and a growing population moves
|
|
21
|
-
* `total` while leaving `detected` alone.
|
|
22
|
-
*
|
|
23
|
-
* NOTHING HERE NAMES A PROJECT, A CHECK, A RUNNER, A WORKFLOW OR A REPOSITORY. The record's text
|
|
24
|
-
* arrives as a string and the name it is filed under arrives beside it, because which file holds a
|
|
25
|
-
* project's bars, and what its gates are called, are that project's own facts. This module has an
|
|
26
|
-
* opinion about the SHAPE of a record and none about whose it is.
|
|
27
|
-
*
|
|
28
|
-
* MALFORMED MEANS NO BAR, NEVER A DEFAULTED ONE. Every refusal below is a record a lenient reader
|
|
29
|
-
* answers with a bar nobody wrote — an absent section read as "no gates to enforce", a gate that
|
|
30
|
-
* measured nothing read as scoring zero, an allowance nobody justified read as justified. The bar
|
|
31
|
-
* is then enforced as though somebody had written it, and the only symptom is a build that has
|
|
32
|
-
* quietly stopped being able to red. So each one is refused by name, and the refusal says which
|
|
33
|
-
* record it read and where in that record the fault is (@SCN-MUT-013, 3F-2792).
|
|
34
|
-
*
|
|
35
|
-
* IT IS NOT A STATIC CHECK, and takes no `static-check:` prefix. A static check is a pure function
|
|
36
|
-
* of the checked-out tree; the record this reads feeds a reconciler that judges a MEASUREMENT,
|
|
37
|
-
* which by construction is not in the tree — the standing ruling every scheduled-sweep reader in
|
|
38
|
-
* this portfolio already carries.
|
|
39
|
-
*/
|
|
40
|
-
import { parse } from "yaml";
|
|
41
|
-
/**
|
|
42
|
-
* The name a record is filed under when its caller does not choose one.
|
|
43
|
-
*
|
|
44
|
-
* A DEFAULT, NEVER A CONSTANT THE READER GOES LOOKING FOR. Which file holds a project's bars is
|
|
45
|
-
* that project's own fact, and a caller naming its own is answered by that name — this is only
|
|
46
|
-
* what stands in when none is given, and it is exported so a caller happy with the convention does
|
|
47
|
-
* not have to respell it and get one character of it wrong.
|
|
48
|
-
*
|
|
49
|
-
* IT MOVED HERE FROM `reconcile.ts` WHEN THE RATCHET LEFT (3F-3337), unchanged. The entry that used
|
|
50
|
-
* to sit beside it reconciled a gate against this record; what is left reading a record is this
|
|
51
|
-
* module, so the default name sits with the reader that defaults to it.
|
|
52
|
-
*/
|
|
53
|
-
export const RECORD_FILE = "quality-thresholds.yml";
|
|
54
|
-
/**
|
|
55
|
-
* The section of a record file that holds every gate's counts.
|
|
56
|
-
*
|
|
57
|
-
* EXPORTED SO THE RECORDER CANNOT HOLD A SECOND COPY OF IT. `ratchet.ts` finds the same section in
|
|
58
|
-
* the same text in order to rewrite two of its lines, and two spellings of one name is the
|
|
59
|
-
* agreeing-until-somebody-edits-one shape this whole library exists to remove.
|
|
60
|
-
*
|
|
61
|
-
* The SECTION name is this module's, and the gate names inside it are the project's. A record file
|
|
62
|
-
* is a project's own document and may hold whatever else that project keeps in it; what this module
|
|
63
|
-
* asks of it is one section, under one name, so a project can file its mutation bars beside the
|
|
64
|
-
* rest of its policy rather than in a file of their own.
|
|
65
|
-
*/
|
|
66
|
-
const MUTATION_GATES = "mutation-gates";
|
|
67
|
-
/**
|
|
68
|
-
* The document itself, when the fault is above any one gate — a record whose top level is not a
|
|
69
|
-
* mapping of sections, or one the YAML parser could not read at all.
|
|
70
|
-
*
|
|
71
|
-
* A PLACEHOLDER RATHER THAN A GATE NAME, and it is spelled here so the two readers that use it
|
|
72
|
-
* cannot drift apart. A refusal always names a section and a key so that a reader who has one
|
|
73
|
-
* message in front of them never has to work out which shape of message they are holding.
|
|
74
|
-
*/
|
|
75
|
-
const WHOLE_DOCUMENT = "<document>";
|
|
76
|
-
/** The key a document-level refusal names, there being no gate to name. */
|
|
77
|
-
const DOCUMENT_ROOT = "root";
|
|
78
|
-
/** The key a section-level refusal names, the fault being the section itself rather than a gate. */
|
|
79
|
-
const WHOLE_SECTION = "<section>";
|
|
80
|
-
/**
|
|
81
|
-
* The field a refusal names when the fault is the ASK rather than a field of a gate's block — a
|
|
82
|
-
* gate the record does not hold at all.
|
|
83
|
-
*
|
|
84
|
-
* EXPORTED FOR THE SAME REASON AS THE SECTION NAME: `ratchet.ts` refuses the same way when it is
|
|
85
|
-
* asked to raise a gate no block is written for, and a refusal a reader has to recognise in two
|
|
86
|
-
* spellings is one they will eventually parse wrongly.
|
|
87
|
-
*/
|
|
88
|
-
const WHOLE_GATE = "<gate>";
|
|
89
|
-
/**
|
|
90
|
-
* Raised when a record cannot be read as a bar.
|
|
91
|
-
*
|
|
92
|
-
* IT NAMES THE SECTION, THE GATE AND THE FIELD, because a record file is a wall of near-identical
|
|
93
|
-
* gate blocks and a bare "invalid config" over one of them leaves a reader opening the file and
|
|
94
|
-
* reading every block to find out which. The four together are enough to put a cursor on the line.
|
|
95
|
-
*
|
|
96
|
-
* ONE TYPE FOR EVERY FAULT, INCLUDING THE ONES THIS MODULE DOES NOT DETECT ITSELF. A caller is
|
|
97
|
-
* told to catch this and nothing else, so anything it does not cover is something that caller
|
|
98
|
-
* sails past — which is why the YAML parser's own errors are normalised into it rather than left
|
|
99
|
-
* to travel under their own name. That is what makes "a malformed record fails loudly" a property
|
|
100
|
-
* of the reader rather than a property of the field checks alone.
|
|
101
|
-
*/
|
|
102
|
-
export class MalformedRecordError extends Error {
|
|
103
|
-
recordFile;
|
|
104
|
-
section;
|
|
105
|
-
key;
|
|
106
|
-
field;
|
|
107
|
-
constructor(
|
|
108
|
-
/** The name the record was filed under, as the caller handed it over. */
|
|
109
|
-
recordFile,
|
|
110
|
-
/** The section the fault sits in, or `<document>` when it sits above every section. */
|
|
111
|
-
section,
|
|
112
|
-
/** The gate the fault sits under, or a placeholder when the fault is the section itself. */
|
|
113
|
-
key,
|
|
114
|
-
/** The field the fault sits at. */
|
|
115
|
-
field, detail) {
|
|
116
|
-
super(`${recordFile}: ${section} "${key}" is malformed at ${field}: ${detail}`);
|
|
117
|
-
this.recordFile = recordFile;
|
|
118
|
-
this.section = section;
|
|
119
|
-
this.key = key;
|
|
120
|
-
this.field = field;
|
|
121
|
-
this.name = "MalformedRecordError";
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/** A mutation score, as a percentage, computed from counts rather than read off a report. */
|
|
125
|
-
function scoreOf(detected, total) {
|
|
126
|
-
return (detected / total) * 100;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* The failing floor: the record's counts with its allowance spent out of the DETECTED count.
|
|
130
|
-
*
|
|
131
|
-
* SPENT FROM THE NUMERATOR, not subtracted from the score, because the allowance is counted in
|
|
132
|
-
* mutants and a mutant is worth a different number of points in every gate — the same allowance
|
|
133
|
-
* taken off two scores would mean two different things in two gates of different sizes.
|
|
134
|
-
*
|
|
135
|
-
* A REPORTED FIGURE, NOT THE DECISION VARIABLE. The verdict decides on COUNTS — fewer kills than
|
|
136
|
-
* the record less its allowance — and prints this floor beside it so a reader has the bar in the
|
|
137
|
-
* units the sweep speaks. The two agree exactly while the mutant population is unchanged, and part
|
|
138
|
-
* company the moment it moves: a grown population drags the measured score below this floor with
|
|
139
|
-
* no kill lost at all, and that case is green. Reading this floor AS the gate would put the
|
|
140
|
-
* judgement back on the score, which is the confusion the counts model exists to prevent.
|
|
141
|
-
*/
|
|
142
|
-
export function floorOf(record) {
|
|
143
|
-
return scoreOf(record.detected - record.allowance.mutants, record.total);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* The failing floor in MUTANTS: the record's kills with its allowance spent out of them.
|
|
147
|
-
*
|
|
148
|
-
* `floorOf`'s TWIN WITHOUT THE DIVISION, and the division is the whole difference. A percentage is
|
|
149
|
-
* two facts folded into one, and folding them is what makes a floor fall when new mutable code
|
|
150
|
-
* arrives carrying survivors — no kill lost, and the bar missed anyway. Subtracting the allowance
|
|
151
|
-
* and stopping leaves a whole number of mutants that moves only when a mutant this suite used to
|
|
152
|
-
* kill stops dying, which is the one fact a bar on this axis is about.
|
|
153
|
-
*
|
|
154
|
-
* `total` NEVER ENTERS IT, and that is the population-independence rather than a simplification.
|
|
155
|
-
* A reader holding this number can be handed a run over any population at all and still be asking
|
|
156
|
-
* the only question worth asking of it.
|
|
157
|
-
*
|
|
158
|
-
* HERE RATHER THAN AT ITS CALLER, for the reason `floorOf` is here: how an allowance is spent is a
|
|
159
|
-
* fact about the record, and a caller computing `detected - slack` itself would be a second place
|
|
160
|
-
* holding it — agreeing until the day one of them is edited.
|
|
161
|
-
*/
|
|
162
|
-
export function killFloorOf(record) {
|
|
163
|
-
return record.detected - record.allowance.mutants;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Read the record's text into a document mapping.
|
|
167
|
-
*
|
|
168
|
-
* THE YAML PARSER'S OWN DETECTIONS ARE REAL, AND THEY ARRIVE UNDER SOMEONE ELSE'S NAME. A gate
|
|
169
|
-
* recorded twice raises `YAMLParseError` before this module sees a mapping at all — a genuine
|
|
170
|
-
* catch, and one a caller that was told to catch `MalformedRecordError` walks straight past. So it
|
|
171
|
-
* is caught here and re-raised as this module's own refusal, carrying the parser's message as the
|
|
172
|
-
* detail so nothing about WHAT was wrong is lost on the way across.
|
|
173
|
-
*/
|
|
174
|
-
function documentOf(text, recordFile) {
|
|
175
|
-
let read;
|
|
176
|
-
try {
|
|
177
|
-
read = parse(text);
|
|
178
|
-
}
|
|
179
|
-
catch (cause) {
|
|
180
|
-
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
181
|
-
throw new MalformedRecordError(recordFile, WHOLE_DOCUMENT, DOCUMENT_ROOT, "yaml", detail);
|
|
182
|
-
}
|
|
183
|
-
if (!isMapping(read)) {
|
|
184
|
-
throw new MalformedRecordError(recordFile, WHOLE_DOCUMENT, DOCUMENT_ROOT, DOCUMENT_ROOT, "the top level must be a mapping of sections.");
|
|
185
|
-
}
|
|
186
|
-
return read;
|
|
187
|
-
}
|
|
188
|
-
/** Whether a value read out of the record is a block of named fields, rather than a list or scalar. */
|
|
189
|
-
function isMapping(value) {
|
|
190
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* The section holding every gate, refused when it is absent or holds no gate.
|
|
194
|
-
*
|
|
195
|
-
* REQUIRED, NEVER DEFAULTED, and the two refusals are one fault at two depths. A record with no
|
|
196
|
-
* section reads as "no gates to enforce"; a section with no gate reads as a bar that enforces
|
|
197
|
-
* nothing. Both hand back a green that was never earned, which is the vacuous pass this whole
|
|
198
|
-
* apparatus exists to close, so neither is allowed to be the answer.
|
|
199
|
-
*/
|
|
200
|
-
function gatesSectionOf(text, recordFile) {
|
|
201
|
-
const section = documentOf(text, recordFile)[MUTATION_GATES];
|
|
202
|
-
if (!isMapping(section)) {
|
|
203
|
-
throw new MalformedRecordError(recordFile, MUTATION_GATES, WHOLE_SECTION, MUTATION_GATES, "the section is missing, or is not a mapping of gate name to record.");
|
|
204
|
-
}
|
|
205
|
-
const entries = Object.entries(section);
|
|
206
|
-
if (entries.length === 0) {
|
|
207
|
-
throw new MalformedRecordError(recordFile, MUTATION_GATES, WHOLE_SECTION, MUTATION_GATES, "at least one gate must be recorded — an empty section is a bar that enforces nothing.");
|
|
208
|
-
}
|
|
209
|
-
return entries;
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* One field, read as a whole, non-negative count of mutants.
|
|
213
|
-
*
|
|
214
|
-
* THE UNIT IS THE MUTANT, so a fraction is not a smaller count — it is a number that came from
|
|
215
|
-
* somewhere other than counting, and the arithmetic downstream is exact precisely because nothing
|
|
216
|
-
* here ever rounds.
|
|
217
|
-
*/
|
|
218
|
-
function countAt(gate, entry, field, recordFile) {
|
|
219
|
-
const raw = entry[field];
|
|
220
|
-
if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 0) {
|
|
221
|
-
throw new MalformedRecordError(recordFile, MUTATION_GATES, gate, field, "expected a whole number of mutants.");
|
|
222
|
-
}
|
|
223
|
-
return raw;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* The allowance the record states, refused when it cannot be spent or cannot say why it exists.
|
|
227
|
-
*
|
|
228
|
-
* AN ALLOWANCE NOBODY CAN JUSTIFY IS HEAD-ROOM, NOT TOLERANCE, and head-room admitted at read time
|
|
229
|
-
* is a bar nobody wrote being enforced as though somebody had. It is refused HERE rather than left
|
|
230
|
-
* to the reconciler, because by the time a verdict is being computed the unjustified allowance has
|
|
231
|
-
* already become part of the bar. A ZERO allowance needs no reason: there is nothing to justify.
|
|
232
|
-
*
|
|
233
|
-
* WIDER THAN THE KILLS IT IS SPENT FROM IS THE OTHER WAY THE SAME BAR VANISHES. The allowance comes
|
|
234
|
-
* out of the recorded kills, so one wider than them puts the floor below zero, and a gate whose
|
|
235
|
-
* floor is below zero cannot red for any measurement at all.
|
|
236
|
-
*/
|
|
237
|
-
function allowanceOf(gate, entry, detected, recordFile) {
|
|
238
|
-
const mutants = countAt(gate, entry, "slack-mutants", recordFile);
|
|
239
|
-
if (mutants > detected) {
|
|
240
|
-
throw new MalformedRecordError(recordFile, MUTATION_GATES, gate, "slack-mutants", `${mutants} is wider than the ${detected} kills it is spent from.`);
|
|
241
|
-
}
|
|
242
|
-
const stated = entry["slack-reason"];
|
|
243
|
-
const reason = typeof stated === "string" ? stated.trim() : "";
|
|
244
|
-
if (mutants > 0 && reason === "") {
|
|
245
|
-
throw new MalformedRecordError(recordFile, MUTATION_GATES, gate, "slack-reason", "a non-zero allowance must say why it exists — one nobody can justify is head-room.");
|
|
246
|
-
}
|
|
247
|
-
return { mutants, reason };
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* One gate's entry, read into its record.
|
|
251
|
-
*
|
|
252
|
-
* `total` IS READ AND REFUSED BEFORE `detected`, WHICH IS THE ORDER AND NOT AN ACCIDENT. A gate
|
|
253
|
-
* that measured no mutants has no score — the division is undefined — so there is nothing for a
|
|
254
|
-
* kill count to be compared against, and refusing at `detected` first would name the wrong field
|
|
255
|
-
* in the message a reader is trying to act on.
|
|
256
|
-
*/
|
|
257
|
-
function recordOf(gate, raw, recordFile) {
|
|
258
|
-
if (!isMapping(raw)) {
|
|
259
|
-
throw new MalformedRecordError(recordFile, MUTATION_GATES, gate, "<entry>", "expected a block of fields.");
|
|
260
|
-
}
|
|
261
|
-
const total = countAt(gate, raw, "total", recordFile);
|
|
262
|
-
if (total === 0) {
|
|
263
|
-
throw new MalformedRecordError(recordFile, MUTATION_GATES, gate, "total", "a gate that measured no mutants has no score.");
|
|
264
|
-
}
|
|
265
|
-
const detected = countAt(gate, raw, "detected", recordFile);
|
|
266
|
-
if (detected > total) {
|
|
267
|
-
throw new MalformedRecordError(recordFile, MUTATION_GATES, gate, "detected", `${detected} kills over ${total} mutants — a gate cannot detect more than it measured.`);
|
|
268
|
-
}
|
|
269
|
-
return { gate, detected, total, allowance: allowanceOf(gate, raw, detected, recordFile) };
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Read every gate's record out of a record file's text.
|
|
273
|
-
*
|
|
274
|
-
* @param text the record file's own bytes, as authored
|
|
275
|
-
* @param recordFile the name the record is filed under, so a refusal can say which file it read
|
|
276
|
-
* @throws MalformedRecordError when the text cannot be read as a bar — one type for every fault,
|
|
277
|
-
* the YAML parser's own included, so a caller told to catch this catches all of them.
|
|
278
|
-
*/
|
|
279
|
-
export function parseGateRecords(text, recordFile) {
|
|
280
|
-
return new Map(gatesSectionOf(text, recordFile).map(([gate, raw]) => [gate, recordOf(gate, raw, recordFile)]));
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* The bar the record holds for one named gate, refused when the record holds no such gate.
|
|
284
|
-
*
|
|
285
|
-
* A GATE WITH NO RECORD IS THE ONE FAULT IN THIS MODULE THAT LOOKS LIKE NOTHING AT ALL. Every
|
|
286
|
-
* refusal above is a record somebody wrote badly, sitting in the file a reader would go and open.
|
|
287
|
-
* This one is a record that reads perfectly, asked for a gate it never held — a gate name mistyped
|
|
288
|
-
* where the caller states it. The lenient answer is not a wrong bar but NO bar, which reads
|
|
289
|
-
* downstream as a gate with nothing to enforce, and it greens for as long as the typo survives.
|
|
290
|
-
* Nothing about the record is wrong, so nobody is ever sent to look at it (@SCN-MUT-013, 3F-2793).
|
|
291
|
-
*
|
|
292
|
-
* AND THE REFUSAL LISTS THE GATES THE RECORD DOES HOLD. A record's gates are near-neighbours by
|
|
293
|
-
* construction — a project names them after the scopes it is quarantining from each other — so a
|
|
294
|
-
* refusal naming only the gate that was asked for sends its reader to open the file and compare
|
|
295
|
-
* spellings by eye. Listing what is held turns the typo into a one-line diagnosis.
|
|
296
|
-
*
|
|
297
|
-
* UNDER THE ONE REFUSAL TYPE, like every fault above it. A caller is told to catch
|
|
298
|
-
* `MalformedRecordError` and nothing else, so an unrecorded gate raised under a second type is a
|
|
299
|
-
* fault that caller sails straight past — the very hole the YAML parser's own errors are
|
|
300
|
-
* normalised to close.
|
|
301
|
-
*
|
|
302
|
-
* @param gate the gate to resolve, as the caller names it — never a name this module knows
|
|
303
|
-
* @param records every gate the record holds, as `parseGateRecords` read them
|
|
304
|
-
* @param recordFile the name the record is filed under, so a refusal can say which file it read
|
|
305
|
-
* @throws MalformedRecordError when the record holds no such gate, listing the gates it does hold
|
|
306
|
-
*/
|
|
307
|
-
export function gateRecordFor(gate, records, recordFile) {
|
|
308
|
-
const record = records.get(gate);
|
|
309
|
-
if (record === undefined) {
|
|
310
|
-
throw new MalformedRecordError(recordFile, MUTATION_GATES, gate, WHOLE_GATE, `no bar is recorded for this gate (recorded: ${[...records.keys()].join(", ")}).`);
|
|
311
|
-
}
|
|
312
|
-
return record;
|
|
313
|
-
}
|
|
314
|
-
//# sourceMappingURL=record.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/mutation-ratchet/record.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THE MEASUREMENT — a gate's run, counted out of its mutation report's own mutants
|
|
3
|
-
* (@SCN-MUT-007, 3F-2799, 3F-3337).
|
|
4
|
-
*
|
|
5
|
-
* THE RATCHET THAT RECONCILED THIS IS GONE; THE READER IS NOT (3F-3337). Its one remaining caller
|
|
6
|
-
* is `tests/checks/mutationScoreCli.ts` — `pnpm mutation:score`, the producer that posts
|
|
7
|
-
* SCN-FIT-002's number — which takes the two counts out of this and divides them. @SCN-MUT-007 and
|
|
8
|
-
* @SCN-MUT-008 are what specify it now, and they state the same two things this file states: the
|
|
9
|
-
* valid population is the killed and the timed-out over those plus the survived and the
|
|
10
|
-
* never-covered, and a run with no readable report posts nothing rather than a count of zero.
|
|
11
|
-
*
|
|
12
|
-
* STATED, NEVER INFERRED, AND THAT IS THE WHOLE MODULE. A mutation report groups its mutants by
|
|
13
|
-
* the file they were made in and gives each one a status. The metric scores two of those statuses
|
|
14
|
-
* over four: the kills are the killed and the timed-out, and the population is those plus the
|
|
15
|
-
* survived and the never-covered. Both sets are written down below, member by member, and the four
|
|
16
|
-
* statuses left over — a mutant that would not compile, one that faulted at run time, one the run
|
|
17
|
-
* was told to ignore, and one that never got as far as being tried — are in neither.
|
|
18
|
-
*
|
|
19
|
-
* THE LENIENT READING IS ONE SENTENCE LONG, AND IT IS WRONG. "Everything not detected is
|
|
20
|
-
* undetected" reaches a population without anybody having to write a second set down, and it folds
|
|
21
|
-
* all four of those statuses into the denominator. A mutant that would not compile is not a
|
|
22
|
-
* survivor: nobody's assertion failed to catch it, because there was never anything there to catch.
|
|
23
|
-
* So that reading answers with a score which disagrees with the one the run itself printed —
|
|
24
|
-
* quietly, and in the direction of looking worse — and a reconciler holding a second opinion about
|
|
25
|
-
* the very number it is reconciling has stopped being one. Naming the two sets is what makes that
|
|
26
|
-
* disagreement impossible rather than merely unlikely.
|
|
27
|
-
*
|
|
28
|
-
* AND THE REPORT'S OWN THRESHOLDS ARE NOT READ, WHICH IS THE SAME RULING ONE ARTEFACT FURTHER OUT.
|
|
29
|
-
* A report carries the thresholds its own run was configured against, and reading them would be
|
|
30
|
-
* taking the runner's verdict on its own homework as the measurement. The bar lives in the record
|
|
31
|
-
* (SCN-FIT-002's own row, which `stryker.config.mjs` reads back), and the report is only ever
|
|
32
|
-
* asked what happened. Every figure produced here is
|
|
33
|
-
* counted from the mutants; every other number in the document is left where it lies, however
|
|
34
|
-
* conveniently it is shaped.
|
|
35
|
-
*
|
|
36
|
-
* THE COUNTS ARE TAKEN ACROSS THE WHOLE REPORT RATHER THAN A FILE OF IT. The grouping is
|
|
37
|
-
* presentation and nothing more — a gate's population is every mutant under it, wherever the report
|
|
38
|
-
* chose to file it. A counter that stopped at the first file would be green against every report
|
|
39
|
-
* holding exactly one, which is the shape a small fixture takes by default.
|
|
40
|
-
*
|
|
41
|
-
* AND A REPORT THAT CANNOT BE READ, OR THAT HOLDS NO SCORED MUTANT AT ALL, IS REFUSED RATHER THAN
|
|
42
|
-
* SCORED ZERO (@SCN-MUT-008). Zero is a measurement — a run that mutated the scope and killed none
|
|
43
|
-
* of it — and the two must not be spelled the same, because reconciling a zero reaches a regression
|
|
44
|
-
* whose message sends its reader hunting kills that in this case never happened. The refusal comes
|
|
45
|
-
* back as a value rather than a throw, so a caller cannot drop it by forgetting to catch.
|
|
46
|
-
*
|
|
47
|
-
* NOTHING HERE NAMES A PROJECT, A CHECK, A RUNNER, A WORKFLOW OR A REPOSITORY. The shape read below
|
|
48
|
-
* is the report FORMAT's, which is nobody's installation, and the gate's name arrives from the
|
|
49
|
-
* caller rather than out of the document.
|
|
50
|
-
*/
|
|
51
|
-
/**
|
|
52
|
-
* One gate's measured run, in the two counts the metric is defined over.
|
|
53
|
-
*
|
|
54
|
-
* IT MOVED HERE FROM `verdict.ts` WHEN THE RATCHET LEFT (3F-3337), unchanged. It sat beside the
|
|
55
|
-
* comparison while there was one; what produces it now is this module alone, so it sits where it
|
|
56
|
-
* is made.
|
|
57
|
-
*
|
|
58
|
-
* TWO COUNTS AND NOT A SCORE. A measurement that arrived as a score would already have folded the
|
|
59
|
-
* two events a reader has to tell apart — a kill lost, and a population that grew — into one
|
|
60
|
-
* number, before any caller could look at either.
|
|
61
|
-
*/
|
|
62
|
-
export interface MutationMeasurement {
|
|
63
|
-
/** The gate this run measured, as the caller names it. */
|
|
64
|
-
readonly gate: string;
|
|
65
|
-
/** Mutants the suite detected. */
|
|
66
|
-
readonly detected: number;
|
|
67
|
-
/** Every mutant the gate measured, detected or not. */
|
|
68
|
-
readonly total: number;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* A report read: the measurement, or the reason there is not one (@SCN-MUT-008, 3F-2800).
|
|
72
|
-
*
|
|
73
|
-
* A REFUSAL IS A RESULT, NEVER AN EXCEPTION THE CALLER MAY FORGET TO CATCH. Reading a report is the
|
|
74
|
-
* one step of this library that reaches for something outside its own arguments, so it is the one
|
|
75
|
-
* step that can fail for reasons nothing in the record explains. A throw would be a fault every
|
|
76
|
-
* caller has to remember to catch, and the caller who forgets reports whatever their surroundings
|
|
77
|
-
* do with an uncaught one — on a good day a red nobody can read, on a bad one a step never reached.
|
|
78
|
-
* Handed back as a value, the refusal is impossible to drop by omission: there is no measurement to
|
|
79
|
-
* read out of it until its `ok` has been asked about.
|
|
80
|
-
*/
|
|
81
|
-
export type MutationReportRead = {
|
|
82
|
-
readonly ok: true;
|
|
83
|
-
readonly measurement: MutationMeasurement;
|
|
84
|
-
} | {
|
|
85
|
-
readonly ok: false;
|
|
86
|
-
readonly reason: string;
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* Count a gate's measurement out of its report's text, or say why there is not one.
|
|
90
|
-
*
|
|
91
|
-
* THE GATE IS THE CALLER'S WORD FOR IT. A report says which files were mutated and nothing about
|
|
92
|
-
* which gate was being run, so a name taken from the document would be a name this module invented.
|
|
93
|
-
*
|
|
94
|
-
* AND SO IS THE PATH, WHICH IS CARRIED PURELY SO A REFUSAL CAN NAME IT — the same reason
|
|
95
|
-
* `parseGateRecords` carries the record file's name. Text has no path of its own, and "no
|
|
96
|
-
* measurement" over a path nothing writes to and "no measurement" over a sweep that never ran are
|
|
97
|
-
* the same sentence with entirely different fixes.
|
|
98
|
-
*/
|
|
99
|
-
export declare function measurementFromReport(gate: string, text: string, reportPath: string): MutationReportRead;
|
|
100
|
-
/**
|
|
101
|
-
* Read a gate's measurement off the tree, refusing an absent report by the path it was sought at.
|
|
102
|
-
*
|
|
103
|
-
* THE PATH ARRIVES RESOLVED, AND THAT IS THE WHOLE OF THIS MODULE'S OPINION ABOUT WHERE A TREE IS.
|
|
104
|
-
* A reader that joined a root of its own — its own installed location, or wherever the process
|
|
105
|
-
* happened to start — is correct only while those coincide with the caller's, which they stop
|
|
106
|
-
* doing the moment this ships as something another repository installs.
|
|
107
|
-
*/
|
|
108
|
-
export declare function readMutationReport(gate: string, reportPath: string): MutationReportRead;
|
|
109
|
-
//# sourceMappingURL=report.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/mutation-ratchet/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAIH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AA+DpD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAoBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAWvF"}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THE MEASUREMENT — a gate's run, counted out of its mutation report's own mutants
|
|
3
|
-
* (@SCN-MUT-007, 3F-2799, 3F-3337).
|
|
4
|
-
*
|
|
5
|
-
* THE RATCHET THAT RECONCILED THIS IS GONE; THE READER IS NOT (3F-3337). Its one remaining caller
|
|
6
|
-
* is `tests/checks/mutationScoreCli.ts` — `pnpm mutation:score`, the producer that posts
|
|
7
|
-
* SCN-FIT-002's number — which takes the two counts out of this and divides them. @SCN-MUT-007 and
|
|
8
|
-
* @SCN-MUT-008 are what specify it now, and they state the same two things this file states: the
|
|
9
|
-
* valid population is the killed and the timed-out over those plus the survived and the
|
|
10
|
-
* never-covered, and a run with no readable report posts nothing rather than a count of zero.
|
|
11
|
-
*
|
|
12
|
-
* STATED, NEVER INFERRED, AND THAT IS THE WHOLE MODULE. A mutation report groups its mutants by
|
|
13
|
-
* the file they were made in and gives each one a status. The metric scores two of those statuses
|
|
14
|
-
* over four: the kills are the killed and the timed-out, and the population is those plus the
|
|
15
|
-
* survived and the never-covered. Both sets are written down below, member by member, and the four
|
|
16
|
-
* statuses left over — a mutant that would not compile, one that faulted at run time, one the run
|
|
17
|
-
* was told to ignore, and one that never got as far as being tried — are in neither.
|
|
18
|
-
*
|
|
19
|
-
* THE LENIENT READING IS ONE SENTENCE LONG, AND IT IS WRONG. "Everything not detected is
|
|
20
|
-
* undetected" reaches a population without anybody having to write a second set down, and it folds
|
|
21
|
-
* all four of those statuses into the denominator. A mutant that would not compile is not a
|
|
22
|
-
* survivor: nobody's assertion failed to catch it, because there was never anything there to catch.
|
|
23
|
-
* So that reading answers with a score which disagrees with the one the run itself printed —
|
|
24
|
-
* quietly, and in the direction of looking worse — and a reconciler holding a second opinion about
|
|
25
|
-
* the very number it is reconciling has stopped being one. Naming the two sets is what makes that
|
|
26
|
-
* disagreement impossible rather than merely unlikely.
|
|
27
|
-
*
|
|
28
|
-
* AND THE REPORT'S OWN THRESHOLDS ARE NOT READ, WHICH IS THE SAME RULING ONE ARTEFACT FURTHER OUT.
|
|
29
|
-
* A report carries the thresholds its own run was configured against, and reading them would be
|
|
30
|
-
* taking the runner's verdict on its own homework as the measurement. The bar lives in the record
|
|
31
|
-
* (SCN-FIT-002's own row, which `stryker.config.mjs` reads back), and the report is only ever
|
|
32
|
-
* asked what happened. Every figure produced here is
|
|
33
|
-
* counted from the mutants; every other number in the document is left where it lies, however
|
|
34
|
-
* conveniently it is shaped.
|
|
35
|
-
*
|
|
36
|
-
* THE COUNTS ARE TAKEN ACROSS THE WHOLE REPORT RATHER THAN A FILE OF IT. The grouping is
|
|
37
|
-
* presentation and nothing more — a gate's population is every mutant under it, wherever the report
|
|
38
|
-
* chose to file it. A counter that stopped at the first file would be green against every report
|
|
39
|
-
* holding exactly one, which is the shape a small fixture takes by default.
|
|
40
|
-
*
|
|
41
|
-
* AND A REPORT THAT CANNOT BE READ, OR THAT HOLDS NO SCORED MUTANT AT ALL, IS REFUSED RATHER THAN
|
|
42
|
-
* SCORED ZERO (@SCN-MUT-008). Zero is a measurement — a run that mutated the scope and killed none
|
|
43
|
-
* of it — and the two must not be spelled the same, because reconciling a zero reaches a regression
|
|
44
|
-
* whose message sends its reader hunting kills that in this case never happened. The refusal comes
|
|
45
|
-
* back as a value rather than a throw, so a caller cannot drop it by forgetting to catch.
|
|
46
|
-
*
|
|
47
|
-
* NOTHING HERE NAMES A PROJECT, A CHECK, A RUNNER, A WORKFLOW OR A REPOSITORY. The shape read below
|
|
48
|
-
* is the report FORMAT's, which is nobody's installation, and the gate's name arrives from the
|
|
49
|
-
* caller rather than out of the document.
|
|
50
|
-
*/
|
|
51
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
52
|
-
/**
|
|
53
|
-
* The statuses that are a kill.
|
|
54
|
-
*
|
|
55
|
-
* A TIMEOUT IS A KILL AND NOT A NEAR-MISS. The mutant changed the code enough that the suite never
|
|
56
|
-
* finished, which is the suite noticing — the same event as an assertion firing, reached by a
|
|
57
|
-
* slower road.
|
|
58
|
-
*/
|
|
59
|
-
const DETECTED = ["Killed", "Timeout"];
|
|
60
|
-
/**
|
|
61
|
-
* The statuses that are in the population and are not a kill.
|
|
62
|
-
*
|
|
63
|
-
* NEVER-COVERED IS COUNTED, AND THAT IS THE HALF A LENIENT READER LOSES BY LEAVING IT OUT. A mutant
|
|
64
|
-
* no test reached is one this suite would not have noticed, which is precisely what the metric
|
|
65
|
-
* measures; dropping it from the denominator would score untested code as though it were not there.
|
|
66
|
-
*/
|
|
67
|
-
const UNDETECTED = ["Survived", "NoCoverage"];
|
|
68
|
-
/** A refusal, as the value it is. */
|
|
69
|
-
function refused(reason) {
|
|
70
|
-
return { ok: false, reason };
|
|
71
|
-
}
|
|
72
|
-
/** Whatever an error carries by way of a sentence, whatever kind of thing was thrown. */
|
|
73
|
-
function raisedBy(cause) {
|
|
74
|
-
return cause instanceof Error ? cause.message : String(cause);
|
|
75
|
-
}
|
|
76
|
-
/** A JSON object — not a list, and not the null a bare `typeof` reads as one. */
|
|
77
|
-
function isMapping(value) {
|
|
78
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Every mutant in the report, flattened out of the per-file grouping it arrived in — or `null`
|
|
82
|
-
* where the document holds no grouping to flatten.
|
|
83
|
-
*
|
|
84
|
-
* THE TWO EMPTINESSES ARE NOT THE SAME AND ARE NOT ANSWERED THE SAME. `null` is a document that
|
|
85
|
-
* never had a files section; an empty list is a section that holds no mutant. Told apart, one
|
|
86
|
-
* reader is sent to the shape of the artefact and the other to the sweep that filled it; folded
|
|
87
|
-
* together, whichever sentence was chosen sends half of them somewhere useless.
|
|
88
|
-
*/
|
|
89
|
-
function mutantsIn(document) {
|
|
90
|
-
const files = document["files"];
|
|
91
|
-
if (!isMapping(files))
|
|
92
|
-
return null;
|
|
93
|
-
return Object.values(files).flatMap((file) => {
|
|
94
|
-
const mutants = isMapping(file) ? file["mutants"] : undefined;
|
|
95
|
-
return Array.isArray(mutants) ? mutants : [];
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
/** How many of these mutants carry a status the given set names. */
|
|
99
|
-
function countOf(mutants, statuses) {
|
|
100
|
-
return mutants.filter((mutant) => statuses.includes(mutant.status)).length;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Count a gate's measurement out of its report's text, or say why there is not one.
|
|
104
|
-
*
|
|
105
|
-
* THE GATE IS THE CALLER'S WORD FOR IT. A report says which files were mutated and nothing about
|
|
106
|
-
* which gate was being run, so a name taken from the document would be a name this module invented.
|
|
107
|
-
*
|
|
108
|
-
* AND SO IS THE PATH, WHICH IS CARRIED PURELY SO A REFUSAL CAN NAME IT — the same reason
|
|
109
|
-
* `parseGateRecords` carries the record file's name. Text has no path of its own, and "no
|
|
110
|
-
* measurement" over a path nothing writes to and "no measurement" over a sweep that never ran are
|
|
111
|
-
* the same sentence with entirely different fixes.
|
|
112
|
-
*/
|
|
113
|
-
export function measurementFromReport(gate, text, reportPath) {
|
|
114
|
-
let document;
|
|
115
|
-
try {
|
|
116
|
-
document = JSON.parse(text);
|
|
117
|
-
}
|
|
118
|
-
catch (cause) {
|
|
119
|
-
return refused(`the report at ${reportPath} is not readable JSON: ${raisedBy(cause)}`);
|
|
120
|
-
}
|
|
121
|
-
if (!isMapping(document)) {
|
|
122
|
-
return refused(`the report at ${reportPath} is not a mutation-report document.`);
|
|
123
|
-
}
|
|
124
|
-
const mutants = mutantsIn(document);
|
|
125
|
-
if (mutants === null) {
|
|
126
|
-
return refused(`the report at ${reportPath} holds no files section.`);
|
|
127
|
-
}
|
|
128
|
-
const detected = countOf(mutants, DETECTED);
|
|
129
|
-
const undetected = countOf(mutants, UNDETECTED);
|
|
130
|
-
if (detected + undetected === 0) {
|
|
131
|
-
return refused(`the report at ${reportPath} holds no scored mutant — the gate measured nothing.`);
|
|
132
|
-
}
|
|
133
|
-
return { ok: true, measurement: { gate, detected, total: detected + undetected } };
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Read a gate's measurement off the tree, refusing an absent report by the path it was sought at.
|
|
137
|
-
*
|
|
138
|
-
* THE PATH ARRIVES RESOLVED, AND THAT IS THE WHOLE OF THIS MODULE'S OPINION ABOUT WHERE A TREE IS.
|
|
139
|
-
* A reader that joined a root of its own — its own installed location, or wherever the process
|
|
140
|
-
* happened to start — is correct only while those coincide with the caller's, which they stop
|
|
141
|
-
* doing the moment this ships as something another repository installs.
|
|
142
|
-
*/
|
|
143
|
-
export function readMutationReport(gate, reportPath) {
|
|
144
|
-
if (!existsSync(reportPath)) {
|
|
145
|
-
return refused(`no report at ${reportPath} — the gate produced no score, or never ran.`);
|
|
146
|
-
}
|
|
147
|
-
let text;
|
|
148
|
-
try {
|
|
149
|
-
text = readFileSync(reportPath, "utf8");
|
|
150
|
-
}
|
|
151
|
-
catch (cause) {
|
|
152
|
-
return refused(`the report at ${reportPath} could not be read: ${raisedBy(cause)}`);
|
|
153
|
-
}
|
|
154
|
-
return measurementFromReport(gate, text, reportPath);
|
|
155
|
-
}
|
|
156
|
-
//# sourceMappingURL=report.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/mutation-ratchet/report.ts"],"names":[],"mappings":"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"}
|