@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/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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 { extractFeatures, correlation } from './features.js';
|
|
11
|
+
export { classifyFlake, shouldRetry, FLAKE_CLASSES } from './classify.js';
|
|
12
|
+
export { analyzeTest, analyzeAll, groupByTestAndProject, DEFAULT_ANALYZE_CONFIG } from './analyze.js';
|
|
13
|
+
export { evaluateQuarantinePolicy, effectiveBudget, daysUntilExpiry, expiryFor, renderQuarantineComment, buildQuarantineEntry, DEFAULT_QUARANTINE_POLICY, DEFAULT_QUARANTINE_REASON, UNCLASSIFIED_CAUSE, } from './quarantine.js';
|
|
14
|
+
export { quarantineCodemod, releaseCodemod, QUARANTINE_TAG } from './codemod.js';
|
|
15
|
+
export { bisect, interpret, DEFAULT_BISECT_OPTIONS } from './bisect.js';
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","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;AAGpB,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG1E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAGtG,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,SAAS,EACT,uBAAuB,EACvB,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGjF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quarantine policy.
|
|
3
|
+
*
|
|
4
|
+
* A quarantine without an expiry is a deletion with extra steps, and a
|
|
5
|
+
* quarantine list without a budget becomes the default response to any red
|
|
6
|
+
* build. Both limits are enforced here so the convention is mechanical rather
|
|
7
|
+
* than aspirational — "fix or delete promptly" is not a thing a policy
|
|
8
|
+
* document can make true.
|
|
9
|
+
*/
|
|
10
|
+
export interface QuarantineEntry {
|
|
11
|
+
readonly testId: string;
|
|
12
|
+
/** Null means every project. */
|
|
13
|
+
readonly project: string | null;
|
|
14
|
+
readonly title: string;
|
|
15
|
+
readonly reason: string;
|
|
16
|
+
readonly flakeScore: number;
|
|
17
|
+
readonly rootCause: string;
|
|
18
|
+
readonly issueUrl: string | null;
|
|
19
|
+
readonly createdAt: string;
|
|
20
|
+
readonly expiresAt: string;
|
|
21
|
+
/** Recorded when an expiry is extended, so extensions are auditable. */
|
|
22
|
+
readonly justification: string | null;
|
|
23
|
+
}
|
|
24
|
+
export interface QuarantinePolicy {
|
|
25
|
+
readonly expiryDays: number;
|
|
26
|
+
readonly maxTests: number;
|
|
27
|
+
/** Cap as a share of the suite; the effective cap is the larger of the two. */
|
|
28
|
+
readonly maxRatio: number;
|
|
29
|
+
}
|
|
30
|
+
export declare const DEFAULT_QUARANTINE_POLICY: QuarantinePolicy;
|
|
31
|
+
export declare const DEFAULT_QUARANTINE_REASON = "flaky";
|
|
32
|
+
export declare const UNCLASSIFIED_CAUSE = "unclassified";
|
|
33
|
+
export interface BuildQuarantineEntryInput {
|
|
34
|
+
readonly title: string;
|
|
35
|
+
readonly testId?: string | undefined;
|
|
36
|
+
readonly project?: string | null | undefined;
|
|
37
|
+
readonly reason?: string | undefined;
|
|
38
|
+
readonly flakeScore?: number | undefined;
|
|
39
|
+
readonly rootCause?: string | undefined;
|
|
40
|
+
readonly issueUrl?: string | null | undefined;
|
|
41
|
+
readonly policy?: QuarantinePolicy | undefined;
|
|
42
|
+
readonly now?: number | undefined;
|
|
43
|
+
}
|
|
44
|
+
export declare function buildQuarantineEntry(input: BuildQuarantineEntryInput): QuarantineEntry;
|
|
45
|
+
export interface PolicyViolation {
|
|
46
|
+
readonly kind: 'expired' | 'budget-exceeded';
|
|
47
|
+
readonly message: string;
|
|
48
|
+
readonly testIds: readonly string[];
|
|
49
|
+
}
|
|
50
|
+
export interface PolicyResult {
|
|
51
|
+
readonly ok: boolean;
|
|
52
|
+
readonly violations: readonly PolicyViolation[];
|
|
53
|
+
readonly active: number;
|
|
54
|
+
readonly budget: number;
|
|
55
|
+
readonly expired: readonly QuarantineEntry[];
|
|
56
|
+
/** Sorted soonest-first, for the "expiring shortly" warning. */
|
|
57
|
+
readonly expiringSoon: readonly QuarantineEntry[];
|
|
58
|
+
}
|
|
59
|
+
export declare function effectiveBudget(suiteSize: number, policy: QuarantinePolicy): number;
|
|
60
|
+
export declare function daysUntilExpiry(entry: QuarantineEntry, now?: number): number;
|
|
61
|
+
export declare function expiryFor(policy: QuarantinePolicy, now?: number): string;
|
|
62
|
+
/**
|
|
63
|
+
* Evaluate the whole quarantine list. Returns violations rather than throwing,
|
|
64
|
+
* so a caller can print them all at once instead of failing on the first —
|
|
65
|
+
* being told about one problem at a time is how a CI gate becomes hated.
|
|
66
|
+
*/
|
|
67
|
+
export declare function evaluateQuarantinePolicy(entries: readonly QuarantineEntry[], suiteSize: number, policy?: QuarantinePolicy, now?: number): PolicyResult;
|
|
68
|
+
/**
|
|
69
|
+
* The tag comment written above a quarantined test.
|
|
70
|
+
*
|
|
71
|
+
* Self-documenting and greppable on purpose: whoever finds this in six months
|
|
72
|
+
* should not have to open a dashboard to learn why it is here or when it
|
|
73
|
+
* should have been dealt with.
|
|
74
|
+
*/
|
|
75
|
+
export declare function renderQuarantineComment(entry: QuarantineEntry, atestVersion: string): string[];
|
|
76
|
+
//# sourceMappingURL=quarantine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quarantine.d.ts","sourceRoot":"","sources":["../src/quarantine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quarantine policy.
|
|
3
|
+
*
|
|
4
|
+
* A quarantine without an expiry is a deletion with extra steps, and a
|
|
5
|
+
* quarantine list without a budget becomes the default response to any red
|
|
6
|
+
* build. Both limits are enforced here so the convention is mechanical rather
|
|
7
|
+
* than aspirational — "fix or delete promptly" is not a thing a policy
|
|
8
|
+
* document can make true.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_QUARANTINE_POLICY = {
|
|
11
|
+
expiryDays: 14,
|
|
12
|
+
maxTests: 5,
|
|
13
|
+
maxRatio: 0.02,
|
|
14
|
+
};
|
|
15
|
+
export const DEFAULT_QUARANTINE_REASON = 'flaky';
|
|
16
|
+
export const UNCLASSIFIED_CAUSE = 'unclassified';
|
|
17
|
+
export function buildQuarantineEntry(input) {
|
|
18
|
+
const now = input.now ?? Date.now();
|
|
19
|
+
const policy = input.policy ?? DEFAULT_QUARANTINE_POLICY;
|
|
20
|
+
return {
|
|
21
|
+
testId: input.testId ?? input.title,
|
|
22
|
+
project: input.project ?? null,
|
|
23
|
+
title: input.title,
|
|
24
|
+
reason: input.reason ?? DEFAULT_QUARANTINE_REASON,
|
|
25
|
+
flakeScore: input.flakeScore ?? 0,
|
|
26
|
+
rootCause: input.rootCause ?? UNCLASSIFIED_CAUSE,
|
|
27
|
+
issueUrl: input.issueUrl ?? null,
|
|
28
|
+
createdAt: new Date(now).toISOString(),
|
|
29
|
+
expiresAt: expiryFor(policy, now),
|
|
30
|
+
justification: null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const MS_PER_DAY = 86_400_000;
|
|
34
|
+
const EXPIRING_SOON_DAYS = 3;
|
|
35
|
+
export function effectiveBudget(suiteSize, policy) {
|
|
36
|
+
return Math.max(policy.maxTests, Math.floor(suiteSize * policy.maxRatio));
|
|
37
|
+
}
|
|
38
|
+
export function daysUntilExpiry(entry, now = Date.now()) {
|
|
39
|
+
return (Date.parse(entry.expiresAt) - now) / MS_PER_DAY;
|
|
40
|
+
}
|
|
41
|
+
export function expiryFor(policy, now = Date.now()) {
|
|
42
|
+
return new Date(now + policy.expiryDays * MS_PER_DAY).toISOString();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Evaluate the whole quarantine list. Returns violations rather than throwing,
|
|
46
|
+
* so a caller can print them all at once instead of failing on the first —
|
|
47
|
+
* being told about one problem at a time is how a CI gate becomes hated.
|
|
48
|
+
*/
|
|
49
|
+
export function evaluateQuarantinePolicy(entries, suiteSize, policy = DEFAULT_QUARANTINE_POLICY, now = Date.now()) {
|
|
50
|
+
const violations = [];
|
|
51
|
+
const expired = entries.filter(e => daysUntilExpiry(e, now) < 0);
|
|
52
|
+
if (expired.length > 0) {
|
|
53
|
+
violations.push({
|
|
54
|
+
kind: 'expired',
|
|
55
|
+
message: `${expired.length} quarantine${expired.length === 1 ? '' : 's'} past expiry. ` +
|
|
56
|
+
'Quarantines expire so they get fixed: release the test, extend with a written ' +
|
|
57
|
+
'justification, or delete it.',
|
|
58
|
+
testIds: expired.map(e => e.testId),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const budget = effectiveBudget(suiteSize, policy);
|
|
62
|
+
if (entries.length > budget) {
|
|
63
|
+
violations.push({
|
|
64
|
+
kind: 'budget-exceeded',
|
|
65
|
+
message: `${entries.length} quarantined tests exceeds the budget of ${budget} ` +
|
|
66
|
+
`(max(${policy.maxTests}, ${(policy.maxRatio * 100).toFixed(0)}% of ${suiteSize}). ` +
|
|
67
|
+
'Fix one before quarantining another.',
|
|
68
|
+
testIds: entries.map(e => e.testId),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const expiringSoon = entries
|
|
72
|
+
.filter(e => {
|
|
73
|
+
const days = daysUntilExpiry(e, now);
|
|
74
|
+
return days >= 0 && days <= EXPIRING_SOON_DAYS;
|
|
75
|
+
})
|
|
76
|
+
.sort((a, b) => Date.parse(a.expiresAt) - Date.parse(b.expiresAt));
|
|
77
|
+
return {
|
|
78
|
+
ok: violations.length === 0,
|
|
79
|
+
violations,
|
|
80
|
+
active: entries.length,
|
|
81
|
+
budget,
|
|
82
|
+
expired,
|
|
83
|
+
expiringSoon,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The tag comment written above a quarantined test.
|
|
88
|
+
*
|
|
89
|
+
* Self-documenting and greppable on purpose: whoever finds this in six months
|
|
90
|
+
* should not have to open a dashboard to learn why it is here or when it
|
|
91
|
+
* should have been dealt with.
|
|
92
|
+
*/
|
|
93
|
+
export function renderQuarantineComment(entry, atestVersion) {
|
|
94
|
+
return [
|
|
95
|
+
`@quarantine ${entry.reason}`,
|
|
96
|
+
`flakeScore ${entry.flakeScore.toFixed(2)} · class ${entry.rootCause}`,
|
|
97
|
+
`expires ${entry.expiresAt.slice(0, 10)} · ${entry.issueUrl ?? 'no issue linked'}`,
|
|
98
|
+
`added by atest ${atestVersion}`,
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=quarantine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quarantine.js","sourceRoot":"","sources":["../src/quarantine.ts"],"names":[],"mappings":"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"}
|
package/dist/score.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flakiness scoring.
|
|
3
|
+
*
|
|
4
|
+
* A test is flaky when, holding commit, environment and project fixed, its
|
|
5
|
+
* outcome is non-deterministic. Anything else is a bug, an environment
|
|
6
|
+
* problem, or a real regression — and calling those "flaky" is how suites rot.
|
|
7
|
+
*
|
|
8
|
+
* Raw failure rate is a bad metric on both axes. It is unstable at low `n`,
|
|
9
|
+
* and it treats a six-month-old failure the same as this morning's. Two terms
|
|
10
|
+
* fix that:
|
|
11
|
+
*
|
|
12
|
+
* Wilson lower bound — stays conservative while observations are few, so a
|
|
13
|
+
* single failure in twelve runs does not brand a test.
|
|
14
|
+
* Transition density — distinguishes "was broken, then fixed" (a run of
|
|
15
|
+
* failures, low density) from genuine non-determinism
|
|
16
|
+
* (alternating outcomes, high density).
|
|
17
|
+
*
|
|
18
|
+
* Neither term alone is sufficient, which the tests demonstrate with worked
|
|
19
|
+
* patterns. No model is involved anywhere in this file.
|
|
20
|
+
*/
|
|
21
|
+
import { type HistoricalAttempt } from '@aplaytest/core';
|
|
22
|
+
export type ScoreConfidence = 'low' | 'medium' | 'high';
|
|
23
|
+
export interface FlakeScore {
|
|
24
|
+
/** 0..1. Flag above `threshold` once `rawN >= minRuns`. */
|
|
25
|
+
readonly score: number;
|
|
26
|
+
/**
|
|
27
|
+
* Wilson lower bound on the rate of the MINORITY outcome — how confident we
|
|
28
|
+
* are that both outcomes genuinely occur.
|
|
29
|
+
*
|
|
30
|
+
* Not the failure rate. A test that fails 12 times out of 12 has a very high
|
|
31
|
+
* failure rate and zero instability: it is deterministically broken, not
|
|
32
|
+
* flaky. Scoring on the failure rate labelled exactly that case FLAKY at
|
|
33
|
+
* 0.45, which sends a real breakage to the quarantine list instead of to
|
|
34
|
+
* whoever broke it.
|
|
35
|
+
*/
|
|
36
|
+
readonly instability: number;
|
|
37
|
+
readonly transitionDensity: number;
|
|
38
|
+
/** Sum of recency weights — the effective sample size. */
|
|
39
|
+
readonly weightedN: number;
|
|
40
|
+
readonly rawN: number;
|
|
41
|
+
readonly failures: number;
|
|
42
|
+
readonly confidence: ScoreConfidence;
|
|
43
|
+
/** True when there were too few conclusive attempts to say anything. */
|
|
44
|
+
readonly insufficientData: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface ScoreConfig {
|
|
47
|
+
readonly minRuns: number;
|
|
48
|
+
readonly halfLifeDays: number;
|
|
49
|
+
}
|
|
50
|
+
export declare const DEFAULT_SCORE_CONFIG: ScoreConfig;
|
|
51
|
+
/**
|
|
52
|
+
* Attempts that can testify about a test's determinism.
|
|
53
|
+
*
|
|
54
|
+
* `infra` failures (a crashed browser, a dropped port-forward) and skips are
|
|
55
|
+
* excluded: they are facts about the environment, not about the test, and
|
|
56
|
+
* counting them would manufacture flakiness during an outage.
|
|
57
|
+
*/
|
|
58
|
+
export declare function usableAttempts(attempts: readonly HistoricalAttempt[]): HistoricalAttempt[];
|
|
59
|
+
/** Wilson score interval, lower bound. Conservative at small n by design. */
|
|
60
|
+
export declare function wilsonLowerBound(successes: number, total: number, z?: number): number;
|
|
61
|
+
/**
|
|
62
|
+
* Score a test's flakiness from its recorded attempts.
|
|
63
|
+
*
|
|
64
|
+
* Combines a Wilson lower bound on the **minority** outcome with transition
|
|
65
|
+
* density. A test that fails 12 of 12 is deterministically broken, not flaky
|
|
66
|
+
* — scoring on raw failure rate labelled that case FLAKY, which sends a real
|
|
67
|
+
* breakage to the quarantine list.
|
|
68
|
+
*
|
|
69
|
+
* @param attempts - History for one (test, project), oldest or newest first.
|
|
70
|
+
* @param config - Thresholds, half-life, and minimum sample size.
|
|
71
|
+
* @param now - Clock used for recency weighting. Injected so tests are stable.
|
|
72
|
+
* @returns A score in 0..1, plus the measurements that produced it. Check
|
|
73
|
+
* `insufficientData` before acting on `score`.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const score = scoreTest(attempts);
|
|
78
|
+
* if (!score.insufficientData && score.score > 0.15) {
|
|
79
|
+
* // candidate for classification, not yet for quarantine
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function scoreTest(attempts: readonly HistoricalAttempt[], config?: ScoreConfig, now?: number): FlakeScore;
|
|
84
|
+
export declare function isFlaky(score: FlakeScore, threshold: number): boolean;
|
|
85
|
+
//# sourceMappingURL=score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../src/score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAqC,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE5F,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExD,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;;;;OASG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,oBAAoB,EAAE,WAGlC,CAAC;AAiBF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAO1F;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,SAAI,GAAG,MAAM,CAOhF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,SAAS,iBAAiB,EAAE,EACtC,MAAM,GAAE,WAAkC,EAC1C,GAAG,GAAE,MAAmB,GACvB,UAAU,CAuDZ;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAErE"}
|
package/dist/score.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flakiness scoring.
|
|
3
|
+
*
|
|
4
|
+
* A test is flaky when, holding commit, environment and project fixed, its
|
|
5
|
+
* outcome is non-deterministic. Anything else is a bug, an environment
|
|
6
|
+
* problem, or a real regression — and calling those "flaky" is how suites rot.
|
|
7
|
+
*
|
|
8
|
+
* Raw failure rate is a bad metric on both axes. It is unstable at low `n`,
|
|
9
|
+
* and it treats a six-month-old failure the same as this morning's. Two terms
|
|
10
|
+
* fix that:
|
|
11
|
+
*
|
|
12
|
+
* Wilson lower bound — stays conservative while observations are few, so a
|
|
13
|
+
* single failure in twelve runs does not brand a test.
|
|
14
|
+
* Transition density — distinguishes "was broken, then fixed" (a run of
|
|
15
|
+
* failures, low density) from genuine non-determinism
|
|
16
|
+
* (alternating outcomes, high density).
|
|
17
|
+
*
|
|
18
|
+
* Neither term alone is sufficient, which the tests demonstrate with worked
|
|
19
|
+
* patterns. No model is involved anywhere in this file.
|
|
20
|
+
*/
|
|
21
|
+
import { countsTowardFlakeStats, isFailure } from '@aplaytest/core';
|
|
22
|
+
export const DEFAULT_SCORE_CONFIG = {
|
|
23
|
+
minRuns: 10,
|
|
24
|
+
halfLifeDays: 7,
|
|
25
|
+
};
|
|
26
|
+
/** 95% confidence. */
|
|
27
|
+
const Z = 1.96;
|
|
28
|
+
const MS_PER_DAY = 86_400_000;
|
|
29
|
+
const EMPTY = {
|
|
30
|
+
score: 0,
|
|
31
|
+
instability: 0,
|
|
32
|
+
transitionDensity: 0,
|
|
33
|
+
weightedN: 0,
|
|
34
|
+
rawN: 0,
|
|
35
|
+
failures: 0,
|
|
36
|
+
confidence: 'low',
|
|
37
|
+
insufficientData: true,
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Attempts that can testify about a test's determinism.
|
|
41
|
+
*
|
|
42
|
+
* `infra` failures (a crashed browser, a dropped port-forward) and skips are
|
|
43
|
+
* excluded: they are facts about the environment, not about the test, and
|
|
44
|
+
* counting them would manufacture flakiness during an outage.
|
|
45
|
+
*/
|
|
46
|
+
export function usableAttempts(attempts) {
|
|
47
|
+
return attempts.filter(a => a.outcome !== 'skipped' &&
|
|
48
|
+
a.outcome !== 'interrupted' &&
|
|
49
|
+
(a.failureKind === null || countsTowardFlakeStats(a.failureKind)));
|
|
50
|
+
}
|
|
51
|
+
/** Wilson score interval, lower bound. Conservative at small n by design. */
|
|
52
|
+
export function wilsonLowerBound(successes, total, z = Z) {
|
|
53
|
+
if (total <= 0)
|
|
54
|
+
return 0;
|
|
55
|
+
const p = successes / total;
|
|
56
|
+
const denominator = 1 + (z * z) / total;
|
|
57
|
+
const centre = p + (z * z) / (2 * total);
|
|
58
|
+
const margin = z * Math.sqrt((p * (1 - p) + (z * z) / (4 * total)) / total);
|
|
59
|
+
return Math.max(0, (centre - margin) / denominator);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Score a test's flakiness from its recorded attempts.
|
|
63
|
+
*
|
|
64
|
+
* Combines a Wilson lower bound on the **minority** outcome with transition
|
|
65
|
+
* density. A test that fails 12 of 12 is deterministically broken, not flaky
|
|
66
|
+
* — scoring on raw failure rate labelled that case FLAKY, which sends a real
|
|
67
|
+
* breakage to the quarantine list.
|
|
68
|
+
*
|
|
69
|
+
* @param attempts - History for one (test, project), oldest or newest first.
|
|
70
|
+
* @param config - Thresholds, half-life, and minimum sample size.
|
|
71
|
+
* @param now - Clock used for recency weighting. Injected so tests are stable.
|
|
72
|
+
* @returns A score in 0..1, plus the measurements that produced it. Check
|
|
73
|
+
* `insufficientData` before acting on `score`.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const score = scoreTest(attempts);
|
|
78
|
+
* if (!score.insufficientData && score.score > 0.15) {
|
|
79
|
+
* // candidate for classification, not yet for quarantine
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function scoreTest(attempts, config = DEFAULT_SCORE_CONFIG, now = Date.now()) {
|
|
84
|
+
const usable = usableAttempts(attempts);
|
|
85
|
+
const rawN = usable.length;
|
|
86
|
+
const failures = usable.filter(a => isFailure(a.outcome)).length;
|
|
87
|
+
if (rawN < config.minRuns) {
|
|
88
|
+
return { ...EMPTY, rawN, failures };
|
|
89
|
+
}
|
|
90
|
+
// Exponential recency decay: a failure one half-life ago counts half as much
|
|
91
|
+
// as one today, so a fixed test stops being penalised for its history.
|
|
92
|
+
const halfLifeMs = config.halfLifeDays * MS_PER_DAY;
|
|
93
|
+
const weightOf = (attempt) => {
|
|
94
|
+
const age = now - Date.parse(attempt.startedAt);
|
|
95
|
+
if (!Number.isFinite(age))
|
|
96
|
+
return 1;
|
|
97
|
+
return Math.pow(0.5, Math.max(0, age) / halfLifeMs);
|
|
98
|
+
};
|
|
99
|
+
let weightedN = 0;
|
|
100
|
+
let weightedFailures = 0;
|
|
101
|
+
for (const attempt of usable) {
|
|
102
|
+
const weight = weightOf(attempt);
|
|
103
|
+
weightedN += weight;
|
|
104
|
+
if (isFailure(attempt.outcome))
|
|
105
|
+
weightedFailures += weight;
|
|
106
|
+
}
|
|
107
|
+
const weightedPasses = weightedN - weightedFailures;
|
|
108
|
+
// The MINORITY outcome is what makes a test flaky. All-fail and all-pass are
|
|
109
|
+
// both perfectly deterministic; instability peaks when the two are balanced
|
|
110
|
+
// and is zero at either extreme, regardless of which one dominates.
|
|
111
|
+
const minorityOutcome = Math.min(weightedFailures, weightedPasses);
|
|
112
|
+
const instability = weightedN > 0 ? wilsonLowerBound(minorityOutcome, weightedN) : 0;
|
|
113
|
+
// Ordered oldest-first so a "flip" means a genuine change of outcome over
|
|
114
|
+
// time rather than an artefact of query ordering.
|
|
115
|
+
const ordered = [...usable].sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt));
|
|
116
|
+
let flips = 0;
|
|
117
|
+
for (let i = 1; i < ordered.length; i++) {
|
|
118
|
+
const previous = ordered[i - 1];
|
|
119
|
+
const current = ordered[i];
|
|
120
|
+
if (previous === undefined || current === undefined)
|
|
121
|
+
continue;
|
|
122
|
+
if (isFailure(previous.outcome) !== isFailure(current.outcome))
|
|
123
|
+
flips += 1;
|
|
124
|
+
}
|
|
125
|
+
const transitionDensity = ordered.length > 1 ? flips / (ordered.length - 1) : 0;
|
|
126
|
+
return {
|
|
127
|
+
score: 0.6 * instability + 0.4 * transitionDensity,
|
|
128
|
+
instability,
|
|
129
|
+
transitionDensity,
|
|
130
|
+
weightedN,
|
|
131
|
+
rawN,
|
|
132
|
+
failures,
|
|
133
|
+
confidence: rawN >= 30 ? 'high' : rawN >= 15 ? 'medium' : 'low',
|
|
134
|
+
insufficientData: false,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function isFlaky(score, threshold) {
|
|
138
|
+
return !score.insufficientData && score.score > threshold;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.js","sourceRoot":"","sources":["../src/score.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aplaytest/flaky",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Statistical flake detection, root-cause classification, and quarantine policy",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc --build",
|
|
14
|
+
"test": "vitest run"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@aplaytest/core": "^0.1.0",
|
|
18
|
+
"ts-morph": "^28.0.0",
|
|
19
|
+
"@aplaytest/runner-playwright": "^0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/ianoflynnautomation/aplaytest.git",
|
|
33
|
+
"directory": "packages/flaky"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/ianoflynnautomation/aplaytest#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/ianoflynnautomation/aplaytest/issues"
|
|
38
|
+
}
|
|
39
|
+
}
|