@aplaytest/flaky 0.1.0
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/.tsbuildinfo +1 -0
- package/dist/analyze.d.ts +46 -0
- package/dist/analyze.d.ts.map +1 -0
- package/dist/analyze.js +68 -0
- package/dist/analyze.js.map +1 -0
- package/dist/bisect.d.ts +69 -0
- package/dist/bisect.d.ts.map +1 -0
- package/dist/bisect.js +177 -0
- package/dist/bisect.js.map +1 -0
- package/dist/classify.d.ts +57 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/classify.js +254 -0
- package/dist/classify.js.map +1 -0
- package/dist/codemod.d.ts +45 -0
- package/dist/codemod.d.ts.map +1 -0
- package/dist/codemod.js +322 -0
- package/dist/codemod.js.map +1 -0
- package/dist/features.d.ts +55 -0
- package/dist/features.d.ts.map +1 -0
- package/dist/features.js +203 -0
- package/dist/features.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/quarantine.d.ts +76 -0
- package/dist/quarantine.d.ts.map +1 -0
- package/dist/quarantine.js +101 -0
- package/dist/quarantine.js.map +1 -0
- package/dist/score.d.ts +85 -0
- package/dist/score.d.ts.map +1 -0
- package/dist/score.js +140 -0
- package/dist/score.js.map +1 -0
- package/package.json +39 -0
package/dist/codemod.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The quarantine codemod.
|
|
3
|
+
*
|
|
4
|
+
* Writes `@quarantine` into a test's own tag array rather than maintaining a
|
|
5
|
+
* side-list of quarantined tests. A suite that already excludes that tag needs
|
|
6
|
+
* no new runtime machinery, the state is greppable, it travels with the code
|
|
7
|
+
* in review, and deleting the test deletes the quarantine.
|
|
8
|
+
*
|
|
9
|
+
* WHY THIS LIVES IN @aplaytest/flaky AND NOT @aplaytest/core: ts-morph bundles the
|
|
10
|
+
* TypeScript compiler. `core` is imported by the reporter, which runs inside
|
|
11
|
+
* every test worker — loading a compiler there would be exactly the overhead
|
|
12
|
+
* that makes people switch the tool off. `flaky` is never on that path. When
|
|
13
|
+
* the heal engine needs the same plumbing, extract it to its own package
|
|
14
|
+
* rather than pushing it down into core.
|
|
15
|
+
*/
|
|
16
|
+
import { Node, Project, SyntaxKind } from 'ts-morph';
|
|
17
|
+
export const QUARANTINE_TAG = '@quarantine';
|
|
18
|
+
/** `test(...)`, `test.only(...)`, `test.fail(...)` — but never `test.describe(...)`. */
|
|
19
|
+
function isTestCall(call) {
|
|
20
|
+
const expression = call.getExpression();
|
|
21
|
+
if (Node.isIdentifier(expression))
|
|
22
|
+
return expression.getText() === 'test';
|
|
23
|
+
if (Node.isPropertyAccessExpression(expression)) {
|
|
24
|
+
const object = expression.getExpression().getText();
|
|
25
|
+
const property = expression.getName();
|
|
26
|
+
// describe/beforeEach/afterEach take a title too; tagging those would
|
|
27
|
+
// quarantine an entire suite from a request to quarantine one test.
|
|
28
|
+
const NON_TEST = new Set(['describe', 'beforeEach', 'afterEach', 'beforeAll', 'afterAll', 'step']);
|
|
29
|
+
return object === 'test' && !NON_TEST.has(property);
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
function titleOf(call) {
|
|
34
|
+
const first = call.getArguments()[0];
|
|
35
|
+
if (first === undefined)
|
|
36
|
+
return null;
|
|
37
|
+
if (Node.isStringLiteral(first) || Node.isNoSubstitutionTemplateLiteral(first)) {
|
|
38
|
+
return first.getLiteralText();
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function findTestCalls(source, title) {
|
|
43
|
+
return source
|
|
44
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
45
|
+
.filter(call => isTestCall(call) && titleOf(call) === title);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The static fragments of a template-literal title, in order.
|
|
49
|
+
*
|
|
50
|
+
* `` `…selects "${name}", then ${path} is opened` `` yields
|
|
51
|
+
* `['…selects "', '", then ', ' is opened']`.
|
|
52
|
+
*/
|
|
53
|
+
function templateQuasis(call) {
|
|
54
|
+
const first = call.getArguments()[0];
|
|
55
|
+
if (first === undefined || !Node.isTemplateExpression(first))
|
|
56
|
+
return null;
|
|
57
|
+
const parts = [first.getHead().getLiteralText()];
|
|
58
|
+
for (const span of first.getTemplateSpans()) {
|
|
59
|
+
parts.push(span.getLiteral().getLiteralText());
|
|
60
|
+
}
|
|
61
|
+
return parts;
|
|
62
|
+
}
|
|
63
|
+
/** Do the template's static fragments appear, in order, in this concrete title? */
|
|
64
|
+
function templateMatches(quasis, title) {
|
|
65
|
+
let cursor = 0;
|
|
66
|
+
for (const part of quasis) {
|
|
67
|
+
if (part === '')
|
|
68
|
+
continue;
|
|
69
|
+
const index = title.indexOf(part, cursor);
|
|
70
|
+
if (index === -1)
|
|
71
|
+
return false;
|
|
72
|
+
cursor = index + part.length;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Find a loop-generated test whose runtime title would match.
|
|
78
|
+
*
|
|
79
|
+
* Real suites parameterise: one `test()` call inside a `for` produces N tests
|
|
80
|
+
* with computed titles. Reporting a plain "not found" for those reads as "your
|
|
81
|
+
* title is wrong" and sends someone hunting a typo that is not there — so this
|
|
82
|
+
* case gets its own status and its own explanation.
|
|
83
|
+
*/
|
|
84
|
+
function findParameterisedTest(source, title) {
|
|
85
|
+
for (const call of source.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
86
|
+
if (!isTestCall(call))
|
|
87
|
+
continue;
|
|
88
|
+
const quasis = templateQuasis(call);
|
|
89
|
+
if (quasis !== null && templateMatches(quasis, title))
|
|
90
|
+
return call;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Add the tag to the test's options object, creating one if absent.
|
|
96
|
+
*
|
|
97
|
+
* Three shapes exist in the wild and all three must round-trip:
|
|
98
|
+
* test('…', async () => {}) → insert `{ tag: [...] }`
|
|
99
|
+
* test('…', { tag: '@acceptance' }, async () => {}) → widen the string to an array
|
|
100
|
+
* test('…', { tag: ['@smoke'] }, async () => {}) → push onto the array
|
|
101
|
+
*/
|
|
102
|
+
function addTagToCall(call, tag) {
|
|
103
|
+
const args = call.getArguments();
|
|
104
|
+
const options = args[1];
|
|
105
|
+
if (options !== undefined && Node.isObjectLiteralExpression(options)) {
|
|
106
|
+
const tagProperty = options.getProperty('tag');
|
|
107
|
+
if (tagProperty === undefined) {
|
|
108
|
+
options.addPropertyAssignment({ name: 'tag', initializer: `['${tag}']` });
|
|
109
|
+
return 'applied';
|
|
110
|
+
}
|
|
111
|
+
if (Node.isPropertyAssignment(tagProperty)) {
|
|
112
|
+
const initializer = tagProperty.getInitializer();
|
|
113
|
+
if (initializer !== undefined && Node.isArrayLiteralExpression(initializer)) {
|
|
114
|
+
const existing = initializer.getElements().map(e => e.getText().replace(/['"`]/g, ''));
|
|
115
|
+
if (existing.includes(tag))
|
|
116
|
+
return 'already-tagged';
|
|
117
|
+
initializer.addElement(`'${tag}'`);
|
|
118
|
+
return 'applied';
|
|
119
|
+
}
|
|
120
|
+
if (initializer !== undefined &&
|
|
121
|
+
(Node.isStringLiteral(initializer) || Node.isNoSubstitutionTemplateLiteral(initializer))) {
|
|
122
|
+
const existing = initializer.getLiteralText();
|
|
123
|
+
if (existing === tag)
|
|
124
|
+
return 'already-tagged';
|
|
125
|
+
tagProperty.setInitializer(`['${existing}', '${tag}']`);
|
|
126
|
+
return 'applied';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// A computed or spread tag value: append rather than rewrite, so we never
|
|
130
|
+
// discard an expression we do not understand.
|
|
131
|
+
options.addPropertyAssignment({ name: `/* atest */ tag`, initializer: `['${tag}']` });
|
|
132
|
+
return 'applied';
|
|
133
|
+
}
|
|
134
|
+
// No options object — insert one between the title and the body.
|
|
135
|
+
call.insertArgument(1, `{ tag: ['${tag}'] }`);
|
|
136
|
+
return 'applied';
|
|
137
|
+
}
|
|
138
|
+
function statementOf(call) {
|
|
139
|
+
return call.getFirstAncestorByKind(SyntaxKind.ExpressionStatement) ?? call;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Build the comment for insertion at `statement.getStart()`.
|
|
143
|
+
*
|
|
144
|
+
* That position sits AFTER the statement's existing leading whitespace, which
|
|
145
|
+
* drives both details here: the first line takes no indent prefix (the
|
|
146
|
+
* whitespace already in the file provides it), and the text ends with the
|
|
147
|
+
* indent so the statement it precedes keeps its own indentation instead of
|
|
148
|
+
* being left at column zero.
|
|
149
|
+
*/
|
|
150
|
+
function renderComment(lines, indent) {
|
|
151
|
+
const body = lines.map(line => `${indent} * ${line}`).join('\n');
|
|
152
|
+
return `/**\n${body}\n${indent} */\n${indent}`;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Apply the quarantine tag. Pure with respect to disk — the caller decides
|
|
156
|
+
* whether to write `result.after`, so a dry run and a real run take exactly
|
|
157
|
+
* the same code path.
|
|
158
|
+
*/
|
|
159
|
+
export function quarantineCodemod(sourceText, input) {
|
|
160
|
+
const tag = input.tag ?? QUARANTINE_TAG;
|
|
161
|
+
const project = new Project({ useInMemoryFileSystem: true });
|
|
162
|
+
const source = project.createSourceFile(input.file, sourceText, { overwrite: true });
|
|
163
|
+
let matches = findTestCalls(source, input.testTitle);
|
|
164
|
+
if (matches.length === 0) {
|
|
165
|
+
const parameterised = findParameterisedTest(source, input.testTitle);
|
|
166
|
+
if (parameterised !== null) {
|
|
167
|
+
const line = source.getLineAndColumnAtPos(parameterised.getStart()).line;
|
|
168
|
+
return {
|
|
169
|
+
status: 'parameterised',
|
|
170
|
+
file: input.file,
|
|
171
|
+
before: sourceText,
|
|
172
|
+
after: null,
|
|
173
|
+
line,
|
|
174
|
+
message: `"${input.testTitle}" is generated at runtime by the parameterised test at ` +
|
|
175
|
+
`${input.file}:${line}. Its title does not exist as a literal in the source, and the ` +
|
|
176
|
+
`single test() call there produces every case in the loop — tagging it would ` +
|
|
177
|
+
`quarantine all of them, not just this one.\n` +
|
|
178
|
+
`Narrow the loop's data, extract this case into its own test(), or fix it directly; ` +
|
|
179
|
+
`quarantine is not the right lever here.`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
status: 'not-found',
|
|
184
|
+
file: input.file,
|
|
185
|
+
before: sourceText,
|
|
186
|
+
after: null,
|
|
187
|
+
line: null,
|
|
188
|
+
message: `No test titled "${input.testTitle}" in ${input.file}.`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (matches.length > 1 && input.line !== undefined) {
|
|
192
|
+
const onLine = matches.filter(call => source.getLineAndColumnAtPos(call.getStart()).line === input.line);
|
|
193
|
+
if (onLine.length === 1)
|
|
194
|
+
matches = onLine;
|
|
195
|
+
}
|
|
196
|
+
if (matches.length > 1) {
|
|
197
|
+
return {
|
|
198
|
+
status: 'ambiguous',
|
|
199
|
+
file: input.file,
|
|
200
|
+
before: sourceText,
|
|
201
|
+
after: null,
|
|
202
|
+
line: null,
|
|
203
|
+
message: `${matches.length} tests in ${input.file} share the title "${input.testTitle}". ` +
|
|
204
|
+
'Pass the line number to disambiguate.',
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const call = matches[0];
|
|
208
|
+
if (call === undefined) {
|
|
209
|
+
return {
|
|
210
|
+
status: 'not-found',
|
|
211
|
+
file: input.file,
|
|
212
|
+
before: sourceText,
|
|
213
|
+
after: null,
|
|
214
|
+
line: null,
|
|
215
|
+
message: `No test titled "${input.testTitle}" in ${input.file}.`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const line = source.getLineAndColumnAtPos(call.getStart()).line;
|
|
219
|
+
const tagResult = addTagToCall(call, tag);
|
|
220
|
+
if (tagResult === 'already-tagged') {
|
|
221
|
+
return {
|
|
222
|
+
status: 'already-tagged',
|
|
223
|
+
file: input.file,
|
|
224
|
+
before: sourceText,
|
|
225
|
+
after: null,
|
|
226
|
+
line,
|
|
227
|
+
message: `"${input.testTitle}" already carries ${tag}.`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// The comment is inserted LAST and by text position, because insertText
|
|
231
|
+
// invalidates every node handle obtained before it.
|
|
232
|
+
if (input.comment !== undefined && input.comment.length > 0) {
|
|
233
|
+
const statement = statementOf(call);
|
|
234
|
+
const start = statement.getStart();
|
|
235
|
+
const column = source.getLineAndColumnAtPos(start).column;
|
|
236
|
+
const indent = ' '.repeat(Math.max(0, column - 1));
|
|
237
|
+
source.insertText(start, renderComment(input.comment, indent));
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
status: 'applied',
|
|
241
|
+
file: input.file,
|
|
242
|
+
before: sourceText,
|
|
243
|
+
after: source.getFullText(),
|
|
244
|
+
line,
|
|
245
|
+
message: `Tagged "${input.testTitle}" with ${tag}.`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/** Remove the tag again — the release path. */
|
|
249
|
+
export function releaseCodemod(sourceText, input) {
|
|
250
|
+
const tag = input.tag ?? QUARANTINE_TAG;
|
|
251
|
+
const project = new Project({ useInMemoryFileSystem: true });
|
|
252
|
+
const source = project.createSourceFile(input.file, sourceText, { overwrite: true });
|
|
253
|
+
const matches = findTestCalls(source, input.testTitle);
|
|
254
|
+
const call = matches[0];
|
|
255
|
+
if (call === undefined) {
|
|
256
|
+
return {
|
|
257
|
+
status: 'not-found',
|
|
258
|
+
file: input.file,
|
|
259
|
+
before: sourceText,
|
|
260
|
+
after: null,
|
|
261
|
+
line: null,
|
|
262
|
+
message: `No test titled "${input.testTitle}" in ${input.file}.`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const line = source.getLineAndColumnAtPos(call.getStart()).line;
|
|
266
|
+
const options = call.getArguments()[1];
|
|
267
|
+
if (options === undefined || !Node.isObjectLiteralExpression(options)) {
|
|
268
|
+
return {
|
|
269
|
+
status: 'not-found',
|
|
270
|
+
file: input.file,
|
|
271
|
+
before: sourceText,
|
|
272
|
+
after: null,
|
|
273
|
+
line,
|
|
274
|
+
message: `"${input.testTitle}" has no tags to remove.`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const tagProperty = options.getProperty('tag');
|
|
278
|
+
if (tagProperty === undefined || !Node.isPropertyAssignment(tagProperty)) {
|
|
279
|
+
return {
|
|
280
|
+
status: 'not-found',
|
|
281
|
+
file: input.file,
|
|
282
|
+
before: sourceText,
|
|
283
|
+
after: null,
|
|
284
|
+
line,
|
|
285
|
+
message: `"${input.testTitle}" has no tags to remove.`,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const initializer = tagProperty.getInitializer();
|
|
289
|
+
if (initializer === undefined || !Node.isArrayLiteralExpression(initializer)) {
|
|
290
|
+
return {
|
|
291
|
+
status: 'not-found',
|
|
292
|
+
file: input.file,
|
|
293
|
+
before: sourceText,
|
|
294
|
+
after: null,
|
|
295
|
+
line,
|
|
296
|
+
message: `"${input.testTitle}" does not carry ${tag}.`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const index = initializer
|
|
300
|
+
.getElements()
|
|
301
|
+
.findIndex(e => e.getText().replace(/['"`]/g, '') === tag);
|
|
302
|
+
if (index === -1) {
|
|
303
|
+
return {
|
|
304
|
+
status: 'not-found',
|
|
305
|
+
file: input.file,
|
|
306
|
+
before: sourceText,
|
|
307
|
+
after: null,
|
|
308
|
+
line,
|
|
309
|
+
message: `"${input.testTitle}" does not carry ${tag}.`,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
initializer.removeElement(index);
|
|
313
|
+
return {
|
|
314
|
+
status: 'applied',
|
|
315
|
+
file: input.file,
|
|
316
|
+
before: sourceText,
|
|
317
|
+
after: source.getFullText(),
|
|
318
|
+
line,
|
|
319
|
+
message: `Removed ${tag} from "${input.testTitle}".`,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=codemod.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codemod.js","sourceRoot":"","sources":["../src/codemod.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic feature extraction.
|
|
3
|
+
*
|
|
4
|
+
* Every value here is measured from recorded attempts. Classification consumes
|
|
5
|
+
* these features; a model is invited only to write prose *after* the class has
|
|
6
|
+
* already been decided. That ordering is what makes a flake verdict
|
|
7
|
+
* reproducible and arguable rather than an opinion.
|
|
8
|
+
*/
|
|
9
|
+
import { type HistoricalAttempt } from '@aplaytest/core';
|
|
10
|
+
export interface FlakeFeatures {
|
|
11
|
+
/** Share of this test's failures landing in its single worst project. */
|
|
12
|
+
readonly projectConcentration: number;
|
|
13
|
+
/**
|
|
14
|
+
* Point-biserial correlation between worker count and failing. −1..1.
|
|
15
|
+
*
|
|
16
|
+
* Reported for context, but NOT used as the load test: with a rare outcome
|
|
17
|
+
* its ceiling collapses. A test that never fails at one worker and fails a
|
|
18
|
+
* third of the time at eight — an unmistakable load dependency — scores only
|
|
19
|
+
* ~0.41, because a 20% base rate caps how high r can reach. Use
|
|
20
|
+
* `workerLoadDelta` to decide; use this to describe.
|
|
21
|
+
*/
|
|
22
|
+
readonly workerCorrelation: number;
|
|
23
|
+
/**
|
|
24
|
+
* failureRate(above median workers) − failureRate(at or below).
|
|
25
|
+
*
|
|
26
|
+
* Directly measures "does load make this fail?", is bounded 0..1, and is
|
|
27
|
+
* interpretable without knowing what point-biserial means.
|
|
28
|
+
*/
|
|
29
|
+
readonly workerLoadDelta: number;
|
|
30
|
+
/** False when every attempt ran at the same worker count — nothing to compare. */
|
|
31
|
+
readonly workerCountVaried: boolean;
|
|
32
|
+
/** max P(fail | X co-scheduled) / P(fail). >1 means co-scheduling hurts. */
|
|
33
|
+
readonly coScheduleLift: number;
|
|
34
|
+
readonly coScheduleSuspects: readonly string[];
|
|
35
|
+
/** Failing duration ÷ median passing duration. High implies waiting, then giving up. */
|
|
36
|
+
readonly durationRatio: number;
|
|
37
|
+
readonly failureKinds: Readonly<Record<string, number>>;
|
|
38
|
+
/** Failures begin sharply at one commit and persist — a regression, not flake. */
|
|
39
|
+
readonly commitBoundary: boolean;
|
|
40
|
+
/** Distinct commits observed; below 2 the boundary test cannot fire. */
|
|
41
|
+
readonly commitsObserved: number;
|
|
42
|
+
/** Failed then passed inside a single run at one commit. The strongest signal. */
|
|
43
|
+
readonly retryFlips: number;
|
|
44
|
+
readonly totalFailures: number;
|
|
45
|
+
readonly totalAttempts: number;
|
|
46
|
+
}
|
|
47
|
+
/** Pearson correlation; with a 0/1 second variable this is point-biserial. */
|
|
48
|
+
export declare function correlation(xs: readonly number[], ys: readonly number[]): number;
|
|
49
|
+
/**
|
|
50
|
+
* @param scoped attempts for the (test, project) under analysis
|
|
51
|
+
* @param allProjects attempts for the same test across every project, used
|
|
52
|
+
* only for the concentration signal
|
|
53
|
+
*/
|
|
54
|
+
export declare function extractFeatures(scoped: readonly HistoricalAttempt[], allProjects?: readonly HistoricalAttempt[]): FlakeFeatures;
|
|
55
|
+
//# sourceMappingURL=features.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../src/features.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAA+B,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,wFAAwF;IACxF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,wEAAwE;IACxE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AA0BD,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAsBhF;AAkHD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,WAAW,GAAE,SAAS,iBAAiB,EAAW,GACjD,aAAa,CAiDf"}
|
package/dist/features.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic feature extraction.
|
|
3
|
+
*
|
|
4
|
+
* Every value here is measured from recorded attempts. Classification consumes
|
|
5
|
+
* these features; a model is invited only to write prose *after* the class has
|
|
6
|
+
* already been decided. That ordering is what makes a flake verdict
|
|
7
|
+
* reproducible and arguable rather than an opinion.
|
|
8
|
+
*/
|
|
9
|
+
import { isFailure } from '@aplaytest/core';
|
|
10
|
+
const EMPTY_FEATURES = {
|
|
11
|
+
projectConcentration: 0,
|
|
12
|
+
workerCorrelation: 0,
|
|
13
|
+
workerLoadDelta: 0,
|
|
14
|
+
workerCountVaried: false,
|
|
15
|
+
coScheduleLift: 1,
|
|
16
|
+
coScheduleSuspects: [],
|
|
17
|
+
durationRatio: 1,
|
|
18
|
+
failureKinds: {},
|
|
19
|
+
commitBoundary: false,
|
|
20
|
+
commitsObserved: 0,
|
|
21
|
+
retryFlips: 0,
|
|
22
|
+
totalFailures: 0,
|
|
23
|
+
totalAttempts: 0,
|
|
24
|
+
};
|
|
25
|
+
function median(values) {
|
|
26
|
+
if (values.length === 0)
|
|
27
|
+
return 0;
|
|
28
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
29
|
+
const mid = Math.floor(sorted.length / 2);
|
|
30
|
+
if (sorted.length % 2 === 1)
|
|
31
|
+
return sorted[mid] ?? 0;
|
|
32
|
+
return ((sorted[mid - 1] ?? 0) + (sorted[mid] ?? 0)) / 2;
|
|
33
|
+
}
|
|
34
|
+
/** Pearson correlation; with a 0/1 second variable this is point-biserial. */
|
|
35
|
+
export function correlation(xs, ys) {
|
|
36
|
+
const n = Math.min(xs.length, ys.length);
|
|
37
|
+
if (n < 2)
|
|
38
|
+
return 0;
|
|
39
|
+
const meanX = xs.reduce((a, b) => a + b, 0) / n;
|
|
40
|
+
const meanY = ys.reduce((a, b) => a + b, 0) / n;
|
|
41
|
+
let covariance = 0;
|
|
42
|
+
let varianceX = 0;
|
|
43
|
+
let varianceY = 0;
|
|
44
|
+
for (let i = 0; i < n; i++) {
|
|
45
|
+
const dx = (xs[i] ?? 0) - meanX;
|
|
46
|
+
const dy = (ys[i] ?? 0) - meanY;
|
|
47
|
+
covariance += dx * dy;
|
|
48
|
+
varianceX += dx * dx;
|
|
49
|
+
varianceY += dy * dy;
|
|
50
|
+
}
|
|
51
|
+
// Zero variance means the variable never moved — no relationship is
|
|
52
|
+
// observable, which is honestly reported as 0 rather than NaN.
|
|
53
|
+
if (varianceX === 0 || varianceY === 0)
|
|
54
|
+
return 0;
|
|
55
|
+
return covariance / Math.sqrt(varianceX * varianceY);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Did failures start at a commit and stay?
|
|
59
|
+
*
|
|
60
|
+
* This is the guard that stops a genuine regression being filed as flake. It
|
|
61
|
+
* requires more than one commit in the window: with a single commit there is
|
|
62
|
+
* no boundary to detect, and treating that as a regression would silence every
|
|
63
|
+
* flake on a quiet branch.
|
|
64
|
+
*/
|
|
65
|
+
function detectCommitBoundary(ordered) {
|
|
66
|
+
const withCommit = ordered.filter(a => a.commit !== null && a.commit !== '');
|
|
67
|
+
const commits = new Set(withCommit.map(a => a.commit));
|
|
68
|
+
if (withCommit.length < 4 || commits.size < 2)
|
|
69
|
+
return false;
|
|
70
|
+
const firstFailureIndex = withCommit.findIndex(a => isFailure(a.outcome));
|
|
71
|
+
if (firstFailureIndex <= 0)
|
|
72
|
+
return false;
|
|
73
|
+
const before = withCommit.slice(0, firstFailureIndex);
|
|
74
|
+
const after = withCommit.slice(firstFailureIndex);
|
|
75
|
+
if (before.length === 0 || after.length < 2)
|
|
76
|
+
return false;
|
|
77
|
+
const beforeFailureRate = before.filter(a => isFailure(a.outcome)).length / before.length;
|
|
78
|
+
const afterFailureRate = after.filter(a => isFailure(a.outcome)).length / after.length;
|
|
79
|
+
return beforeFailureRate === 0 && afterFailureRate >= 0.7;
|
|
80
|
+
}
|
|
81
|
+
/** Failed then passed within the same run — non-determinism at one commit. */
|
|
82
|
+
function countRetryFlips(attempts) {
|
|
83
|
+
const byRun = new Map();
|
|
84
|
+
for (const attempt of attempts) {
|
|
85
|
+
const existing = byRun.get(attempt.runId) ?? [];
|
|
86
|
+
existing.push(attempt);
|
|
87
|
+
byRun.set(attempt.runId, existing);
|
|
88
|
+
}
|
|
89
|
+
let flips = 0;
|
|
90
|
+
for (const group of byRun.values()) {
|
|
91
|
+
if (group.length < 2)
|
|
92
|
+
continue;
|
|
93
|
+
const sorted = [...group].sort((a, b) => a.retry - b.retry);
|
|
94
|
+
const failedEarly = sorted.slice(0, -1).some(a => isFailure(a.outcome));
|
|
95
|
+
const passedLast = sorted[sorted.length - 1]?.outcome === 'passed';
|
|
96
|
+
if (failedEarly && passedLast)
|
|
97
|
+
flips += 1;
|
|
98
|
+
}
|
|
99
|
+
return flips;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Compare the failure rate at the LOWEST observed worker count against the
|
|
103
|
+
* HIGHEST.
|
|
104
|
+
*
|
|
105
|
+
* Deliberately not a median split. Worker counts are heavily skewed in
|
|
106
|
+
* practice — a suite that mostly runs at full parallelism and occasionally in
|
|
107
|
+
* isolation puts the median at the top of the range, leaving the "high" group
|
|
108
|
+
* empty and the signal silently zero. Comparing the extremes answers the
|
|
109
|
+
* actual question ("does more parallelism make this fail?") and cannot be
|
|
110
|
+
* defeated by the shape of the distribution.
|
|
111
|
+
*/
|
|
112
|
+
function workerLoadSignal(attempts) {
|
|
113
|
+
const distinct = [...new Set(attempts.map(a => a.workers))].sort((a, b) => a - b);
|
|
114
|
+
if (distinct.length < 2)
|
|
115
|
+
return { delta: 0, varied: false };
|
|
116
|
+
const lowest = distinct[0];
|
|
117
|
+
const highest = distinct[distinct.length - 1];
|
|
118
|
+
if (lowest === undefined || highest === undefined)
|
|
119
|
+
return { delta: 0, varied: false };
|
|
120
|
+
const low = attempts.filter(a => a.workers === lowest);
|
|
121
|
+
const high = attempts.filter(a => a.workers === highest);
|
|
122
|
+
if (low.length === 0 || high.length === 0)
|
|
123
|
+
return { delta: 0, varied: false };
|
|
124
|
+
const rate = (group) => group.filter(a => isFailure(a.outcome)).length / group.length;
|
|
125
|
+
return { delta: rate(high) - rate(low), varied: true };
|
|
126
|
+
}
|
|
127
|
+
function coScheduleSignal(attempts) {
|
|
128
|
+
const total = attempts.length;
|
|
129
|
+
const failures = attempts.filter(a => isFailure(a.outcome)).length;
|
|
130
|
+
if (total === 0 || failures === 0)
|
|
131
|
+
return { lift: 1, suspects: [] };
|
|
132
|
+
const baseRate = failures / total;
|
|
133
|
+
const withPeer = new Map();
|
|
134
|
+
for (const attempt of attempts) {
|
|
135
|
+
for (const peer of attempt.coScheduled) {
|
|
136
|
+
const stat = withPeer.get(peer) ?? { runs: 0, failures: 0 };
|
|
137
|
+
stat.runs += 1;
|
|
138
|
+
if (isFailure(attempt.outcome))
|
|
139
|
+
stat.failures += 1;
|
|
140
|
+
withPeer.set(peer, stat);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
let bestLift = 1;
|
|
144
|
+
const suspects = [];
|
|
145
|
+
for (const [peer, stat] of withPeer) {
|
|
146
|
+
// Require a few co-occurrences before believing a lift; two runs can make
|
|
147
|
+
// anything look causal.
|
|
148
|
+
if (stat.runs < 3)
|
|
149
|
+
continue;
|
|
150
|
+
const lift = stat.failures / stat.runs / baseRate;
|
|
151
|
+
if (lift > 1.5)
|
|
152
|
+
suspects.push(peer);
|
|
153
|
+
if (lift > bestLift)
|
|
154
|
+
bestLift = lift;
|
|
155
|
+
}
|
|
156
|
+
return { lift: bestLift, suspects: suspects.slice(0, 5) };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* @param scoped attempts for the (test, project) under analysis
|
|
160
|
+
* @param allProjects attempts for the same test across every project, used
|
|
161
|
+
* only for the concentration signal
|
|
162
|
+
*/
|
|
163
|
+
export function extractFeatures(scoped, allProjects = scoped) {
|
|
164
|
+
if (scoped.length === 0)
|
|
165
|
+
return EMPTY_FEATURES;
|
|
166
|
+
const ordered = [...scoped].sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt));
|
|
167
|
+
const failures = ordered.filter(a => isFailure(a.outcome));
|
|
168
|
+
const passes = ordered.filter(a => a.outcome === 'passed');
|
|
169
|
+
const failuresByProject = new Map();
|
|
170
|
+
for (const attempt of allProjects.filter(a => isFailure(a.outcome))) {
|
|
171
|
+
failuresByProject.set(attempt.project, (failuresByProject.get(attempt.project) ?? 0) + 1);
|
|
172
|
+
}
|
|
173
|
+
const totalCrossProjectFailures = [...failuresByProject.values()].reduce((a, b) => a + b, 0);
|
|
174
|
+
const projectConcentration = totalCrossProjectFailures === 0
|
|
175
|
+
? 0
|
|
176
|
+
: Math.max(...failuresByProject.values()) / totalCrossProjectFailures;
|
|
177
|
+
const failureKinds = {};
|
|
178
|
+
for (const attempt of failures) {
|
|
179
|
+
const kind = attempt.failureKind ?? 'unclassified';
|
|
180
|
+
failureKinds[kind] = (failureKinds[kind] ?? 0) + 1;
|
|
181
|
+
}
|
|
182
|
+
const medianPassDuration = median(passes.map(a => a.durationMs));
|
|
183
|
+
const medianFailDuration = median(failures.map(a => a.durationMs));
|
|
184
|
+
const durationRatio = medianPassDuration > 0 ? medianFailDuration / medianPassDuration : failures.length > 0 ? 2 : 1;
|
|
185
|
+
const { lift, suspects } = coScheduleSignal(ordered);
|
|
186
|
+
const load = workerLoadSignal(ordered);
|
|
187
|
+
return {
|
|
188
|
+
projectConcentration,
|
|
189
|
+
workerCorrelation: correlation(ordered.map(a => a.workers), ordered.map(a => (isFailure(a.outcome) ? 1 : 0))),
|
|
190
|
+
workerLoadDelta: load.delta,
|
|
191
|
+
workerCountVaried: load.varied,
|
|
192
|
+
coScheduleLift: lift,
|
|
193
|
+
coScheduleSuspects: suspects,
|
|
194
|
+
durationRatio,
|
|
195
|
+
failureKinds,
|
|
196
|
+
commitBoundary: detectCommitBoundary(ordered),
|
|
197
|
+
commitsObserved: new Set(ordered.map(a => a.commit).filter(c => c !== null && c !== '')).size,
|
|
198
|
+
retryFlips: countRetryFlips(ordered),
|
|
199
|
+
totalFailures: failures.length,
|
|
200
|
+
totalAttempts: ordered.length,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=features.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features.js","sourceRoot":"","sources":["../src/features.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aplaytest/flaky — statistical flake detection and root-cause classification.
|
|
3
|
+
*
|
|
4
|
+
* Entirely deterministic. Every number here is measured from recorded
|
|
5
|
+
* attempts; no model is called anywhere in this package. A model may later be
|
|
6
|
+
* asked to phrase a verdict, never to reach one — a flake score that cannot be
|
|
7
|
+
* reproduced is a flake score nobody will act on.
|
|
8
|
+
*/
|
|
9
|
+
export { scoreTest, isFlaky, usableAttempts, wilsonLowerBound, DEFAULT_SCORE_CONFIG, } from './score.js';
|
|
10
|
+
export type { FlakeScore, ScoreConfig, ScoreConfidence } from './score.js';
|
|
11
|
+
export { extractFeatures, correlation } from './features.js';
|
|
12
|
+
export type { FlakeFeatures } from './features.js';
|
|
13
|
+
export { classifyFlake, shouldRetry, FLAKE_CLASSES } from './classify.js';
|
|
14
|
+
export type { Classification, FlakeClass, Prescription } from './classify.js';
|
|
15
|
+
export { analyzeTest, analyzeAll, groupByTestAndProject, DEFAULT_ANALYZE_CONFIG } from './analyze.js';
|
|
16
|
+
export type { AnalyzeConfig, FlakyVerdict, FlakyReport } from './analyze.js';
|
|
17
|
+
export { evaluateQuarantinePolicy, effectiveBudget, daysUntilExpiry, expiryFor, renderQuarantineComment, buildQuarantineEntry, DEFAULT_QUARANTINE_POLICY, DEFAULT_QUARANTINE_REASON, UNCLASSIFIED_CAUSE, } from './quarantine.js';
|
|
18
|
+
export type { QuarantineEntry, QuarantinePolicy, PolicyResult, PolicyViolation, BuildQuarantineEntryInput, } from './quarantine.js';
|
|
19
|
+
export { quarantineCodemod, releaseCodemod, QUARANTINE_TAG } from './codemod.js';
|
|
20
|
+
export type { CodemodResult, CodemodStatus, QuarantineCodemodInput } from './codemod.js';
|
|
21
|
+
export { bisect, interpret, DEFAULT_BISECT_OPTIONS } from './bisect.js';
|
|
22
|
+
export type { BisectOptions, BisectResult, BisectProbe, BisectVerdict, BisectDimension } from './bisect.js';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,SAAS,EACT,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE3E,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC1E,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtG,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE7E,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,SAAS,EACT,uBAAuB,EACvB,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAEzF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
|