flakeradar 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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +309 -0
  3. package/bin/flakeradar.js +11 -0
  4. package/dist/analysis/classify.d.ts +10 -0
  5. package/dist/analysis/classify.d.ts.map +1 -0
  6. package/dist/analysis/classify.js +163 -0
  7. package/dist/analysis/classify.js.map +1 -0
  8. package/dist/analysis/flakiness.d.ts +11 -0
  9. package/dist/analysis/flakiness.d.ts.map +1 -0
  10. package/dist/analysis/flakiness.js +177 -0
  11. package/dist/analysis/flakiness.js.map +1 -0
  12. package/dist/cli.d.ts +5 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +77 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/color.d.ts +19 -0
  17. package/dist/color.d.ts.map +1 -0
  18. package/dist/color.js +36 -0
  19. package/dist/color.js.map +1 -0
  20. package/dist/commands/analyze.d.ts +4 -0
  21. package/dist/commands/analyze.d.ts.map +1 -0
  22. package/dist/commands/analyze.js +106 -0
  23. package/dist/commands/analyze.js.map +1 -0
  24. package/dist/commands/run.d.ts +4 -0
  25. package/dist/commands/run.d.ts.map +1 -0
  26. package/dist/commands/run.js +116 -0
  27. package/dist/commands/run.js.map +1 -0
  28. package/dist/commands/shared.d.ts +16 -0
  29. package/dist/commands/shared.d.ts.map +1 -0
  30. package/dist/commands/shared.js +44 -0
  31. package/dist/commands/shared.js.map +1 -0
  32. package/dist/index.d.ts +14 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +13 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/junit/parser.d.ts +11 -0
  37. package/dist/junit/parser.d.ts.map +1 -0
  38. package/dist/junit/parser.js +98 -0
  39. package/dist/junit/parser.js.map +1 -0
  40. package/dist/junit/xml.d.ts +27 -0
  41. package/dist/junit/xml.d.ts.map +1 -0
  42. package/dist/junit/xml.js +238 -0
  43. package/dist/junit/xml.js.map +1 -0
  44. package/dist/report/json.d.ts +7 -0
  45. package/dist/report/json.d.ts.map +1 -0
  46. package/dist/report/json.js +38 -0
  47. package/dist/report/json.js.map +1 -0
  48. package/dist/report/markdown.d.ts +7 -0
  49. package/dist/report/markdown.d.ts.map +1 -0
  50. package/dist/report/markdown.js +75 -0
  51. package/dist/report/markdown.js.map +1 -0
  52. package/dist/report/terminal.d.ts +4 -0
  53. package/dist/report/terminal.d.ts.map +1 -0
  54. package/dist/report/terminal.js +101 -0
  55. package/dist/report/terminal.js.map +1 -0
  56. package/dist/runner/runner.d.ts +45 -0
  57. package/dist/runner/runner.d.ts.map +1 -0
  58. package/dist/runner/runner.js +125 -0
  59. package/dist/runner/runner.js.map +1 -0
  60. package/dist/types.d.ts +87 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/dist/types.js +2 -0
  63. package/dist/types.js.map +1 -0
  64. package/dist/util/args.d.ts +23 -0
  65. package/dist/util/args.d.ts.map +1 -0
  66. package/dist/util/args.js +83 -0
  67. package/dist/util/args.js.map +1 -0
  68. package/dist/util/glob.d.ts +11 -0
  69. package/dist/util/glob.d.ts.map +1 -0
  70. package/dist/util/glob.js +104 -0
  71. package/dist/util/glob.js.map +1 -0
  72. package/package.json +62 -0
@@ -0,0 +1,177 @@
1
+ import { classifyCause } from "./classify.js";
2
+ const isFailure = (s) => s === "failed" || s === "error";
3
+ /**
4
+ * Collapse a run's raw results (which may contain duplicates from parameterised
5
+ * tests or in-run retries) into a single outcome per test id. A test that both
6
+ * passes and fails within one run counts as a failure — that IS flakiness.
7
+ */
8
+ function collapseRun(run) {
9
+ const map = new Map();
10
+ for (const r of run.results) {
11
+ const existing = map.get(r.id);
12
+ const outcome = isFailure(r.status)
13
+ ? "failed"
14
+ : r.status === "skipped"
15
+ ? "skipped"
16
+ : "passed";
17
+ const sample = isFailure(r.status)
18
+ ? { message: r.message, details: r.details }
19
+ : null;
20
+ if (!existing) {
21
+ map.set(r.id, { outcome, sample });
22
+ continue;
23
+ }
24
+ // Precedence: failed > passed > skipped.
25
+ if (outcome === "failed" && existing.outcome !== "failed") {
26
+ map.set(r.id, { outcome: "failed", sample: sample ?? existing.sample });
27
+ }
28
+ else if (outcome === "failed" && existing.outcome === "failed") {
29
+ existing.sample = existing.sample ?? sample;
30
+ }
31
+ else if (outcome === "passed" && existing.outcome === "skipped") {
32
+ map.set(r.id, { outcome: "passed", sample: existing.sample });
33
+ }
34
+ }
35
+ return map;
36
+ }
37
+ function buildObservations(runs) {
38
+ const obs = new Map();
39
+ const meta = new Map();
40
+ // Pre-scan for stable display names.
41
+ for (const run of runs) {
42
+ for (const r of run.results) {
43
+ if (!meta.has(r.id))
44
+ meta.set(r.id, { name: r.name, classname: r.classname });
45
+ }
46
+ }
47
+ const collapsed = runs.map(collapseRun);
48
+ for (const id of meta.keys()) {
49
+ const m = meta.get(id);
50
+ const outcomes = [];
51
+ const failureSamples = [];
52
+ for (const runMap of collapsed) {
53
+ const entry = runMap.get(id);
54
+ outcomes.push(entry ? entry.outcome : null);
55
+ if (entry?.sample)
56
+ failureSamples.push(entry.sample);
57
+ }
58
+ obs.set(id, { id, name: m.name, classname: m.classname, outcomes, failureSamples });
59
+ }
60
+ return obs;
61
+ }
62
+ function countFlips(outcomes) {
63
+ let flips = 0;
64
+ let prev = null;
65
+ for (const o of outcomes) {
66
+ if (o === null || o === "skipped")
67
+ continue;
68
+ if (prev !== null && o !== prev)
69
+ flips++;
70
+ prev = o;
71
+ }
72
+ return flips;
73
+ }
74
+ function distinct(values, limit) {
75
+ const seen = new Set();
76
+ const out = [];
77
+ for (const v of values) {
78
+ const key = v.trim();
79
+ if (key.length === 0 || seen.has(key))
80
+ continue;
81
+ seen.add(key);
82
+ out.push(key);
83
+ if (out.length >= limit)
84
+ break;
85
+ }
86
+ return out;
87
+ }
88
+ function toVerdict(o) {
89
+ let passes = 0;
90
+ let failures = 0;
91
+ let skips = 0;
92
+ for (const outcome of o.outcomes) {
93
+ if (outcome === "passed")
94
+ passes++;
95
+ else if (outcome === "failed")
96
+ failures++;
97
+ else if (outcome === "skipped")
98
+ skips++;
99
+ }
100
+ const nonSkip = passes + failures;
101
+ const failureRate = nonSkip === 0 ? 0 : failures / nonSkip;
102
+ const flips = countFlips(o.outcomes);
103
+ const flaky = passes > 0 && failures > 0;
104
+ const alwaysFails = passes === 0 && failures > 0;
105
+ // Score = breakage events + nondeterminism events. Simple and explainable:
106
+ // both are "bad events" a maintainer wants to eliminate.
107
+ const score = failures + flips;
108
+ const sampleMessages = distinct(o.failureSamples.map((s) => s.message ?? firstLine(s.details) ?? "").filter((s) => s.length > 0), 3);
109
+ return {
110
+ id: o.id,
111
+ name: o.name,
112
+ classname: o.classname,
113
+ runsSeen: passes + failures + skips,
114
+ passes,
115
+ failures,
116
+ skips,
117
+ failureRate,
118
+ flips,
119
+ flaky,
120
+ alwaysFails,
121
+ score,
122
+ cause: flaky ? classifyCause(o.failureSamples) : null,
123
+ sampleMessages,
124
+ };
125
+ }
126
+ function firstLine(text) {
127
+ if (!text)
128
+ return null;
129
+ const line = text.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
130
+ return line ?? null;
131
+ }
132
+ function runIsGreen(run) {
133
+ if (run.crashed)
134
+ return false;
135
+ if (run.results.length === 0)
136
+ return false;
137
+ return !run.results.some((r) => isFailure(r.status));
138
+ }
139
+ /** Compare flaky verdicts: worst (most worth investigating) first. */
140
+ export function compareFlaky(a, b) {
141
+ if (b.score !== a.score)
142
+ return b.score - a.score;
143
+ if (b.failureRate !== a.failureRate)
144
+ return b.failureRate - a.failureRate;
145
+ if (b.failures !== a.failures)
146
+ return b.failures - a.failures;
147
+ return a.id.localeCompare(b.id);
148
+ }
149
+ /**
150
+ * Analyze a sequence of runs and produce a ranked flakiness report.
151
+ * Run order is significant — flips are computed along it.
152
+ */
153
+ export function analyze(runs) {
154
+ const observations = buildObservations(runs);
155
+ const verdicts = [...observations.values()].map(toVerdict);
156
+ const flaky = verdicts.filter((v) => v.flaky).sort(compareFlaky);
157
+ const alwaysFailing = verdicts
158
+ .filter((v) => v.alwaysFails)
159
+ .sort((a, b) => b.failures - a.failures || a.id.localeCompare(b.id));
160
+ const alwaysPassCount = verdicts.filter((v) => v.passes > 0 && v.failures === 0).length;
161
+ const greenRuns = runs.filter(runIsGreen).length;
162
+ const summary = {
163
+ totalRuns: runs.length,
164
+ crashedRuns: runs.filter((r) => r.crashed).length,
165
+ uniqueTests: verdicts.length,
166
+ flakyCount: flaky.length,
167
+ alwaysFailCount: alwaysFailing.length,
168
+ alwaysPassCount,
169
+ greenRunRate: runs.length === 0 ? 0 : greenRuns / runs.length,
170
+ };
171
+ return { summary, flaky, alwaysFailing };
172
+ }
173
+ /** Convenience: build a Run from already-parsed results. */
174
+ export function runFromResults(label, results) {
175
+ return { label, results, crashed: false };
176
+ }
177
+ //# sourceMappingURL=flakiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flakiness.js","sourceRoot":"","sources":["../../src/analysis/flakiness.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /** Entry point. Returns a process exit code. */
2
+ export declare function main(argv: string[]): Promise<number>;
3
+ /** Read the package version from the nearest package.json. */
4
+ export declare function getVersion(): string;
5
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AA2BA,gDAAgD;AAChD,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAyB1D;AAED,8DAA8D;AAC9D,wBAAgB,UAAU,IAAI,MAAM,CAmBnC"}
package/dist/cli.js ADDED
@@ -0,0 +1,77 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { runAnalyze } from "./commands/analyze.js";
5
+ import { runRun } from "./commands/run.js";
6
+ import { color, setColorEnabled } from "./color.js";
7
+ const TOP_HELP = `flakeradar — find and rank flaky tests in any language
8
+
9
+ USAGE
10
+ flakeradar <command> [options]
11
+
12
+ COMMANDS
13
+ run Run a test command N times and detect flaky tests
14
+ analyze Detect flaky tests from existing JUnit XML reports
15
+ help Show this help
16
+ version Show the version
17
+
18
+ QUICK START
19
+ # Ingest existing CI reports (each file = one run):
20
+ flakeradar analyze "test-reports/**/*.xml"
21
+
22
+ # Or run a suite repeatedly and watch for flakiness:
23
+ flakeradar run -n 20 -- pytest --junitxml={out}
24
+
25
+ Run "flakeradar <command> --help" for command-specific options.`;
26
+ /** Entry point. Returns a process exit code. */
27
+ export async function main(argv) {
28
+ if (argv.includes("--no-color"))
29
+ setColorEnabled(false);
30
+ const [command, ...rest] = argv;
31
+ switch (command) {
32
+ case undefined:
33
+ case "help":
34
+ case "-h":
35
+ case "--help":
36
+ process.stdout.write(`${TOP_HELP}\n`);
37
+ return 0;
38
+ case "version":
39
+ case "-v":
40
+ case "--version":
41
+ process.stdout.write(`${getVersion()}\n`);
42
+ return 0;
43
+ case "run":
44
+ return runRun(rest);
45
+ case "analyze":
46
+ return runAnalyze(rest);
47
+ default:
48
+ process.stderr.write(color.red(`unknown command: ${command}`) + "\n\n" + TOP_HELP + "\n");
49
+ return 1;
50
+ }
51
+ }
52
+ /** Read the package version from the nearest package.json. */
53
+ export function getVersion() {
54
+ try {
55
+ let dir = path.dirname(fileURLToPath(import.meta.url));
56
+ for (let i = 0; i < 5; i++) {
57
+ const candidate = path.join(dir, "package.json");
58
+ try {
59
+ const pkg = JSON.parse(readFileSync(candidate, "utf8"));
60
+ if (pkg.name === "flakeradar" || pkg.version)
61
+ return pkg.version ?? "0.0.0";
62
+ }
63
+ catch {
64
+ /* keep walking up */
65
+ }
66
+ const parent = path.dirname(dir);
67
+ if (parent === dir)
68
+ break;
69
+ dir = parent;
70
+ }
71
+ }
72
+ catch {
73
+ /* fall through */
74
+ }
75
+ return "0.0.0";
76
+ }
77
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;gEAkB+C,CAAC;AAEjE,gDAAgD;AAChD,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAc;IACvC,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IAExD,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAEhC,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ;YACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,IAAI,CAAC,CAAC;YACtC,OAAO,CAAC,CAAC;QACX,KAAK,SAAS,CAAC;QACf,KAAK,IAAI,CAAC;QACV,KAAK,WAAW;YACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,CAAC;YAC1C,OAAO,CAAC,CAAC;QACX,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,KAAK,SAAS;YACZ,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;QAC1B;YACE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,oBAAoB,OAAO,EAAE,CAAC,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC,CAAC;YAC1F,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACvD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YACjD,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAwC,CAAC;gBAC/F,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,CAAC,OAAO;oBAAE,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;YAC9E,CAAC;YAAC,MAAM,CAAC;gBACP,qBAAqB;YACvB,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,MAAM,KAAK,GAAG;gBAAE,MAAM;YAC1B,GAAG,GAAG,MAAM,CAAC;QACf,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Tiny ANSI color helper — zero dependencies. Respects NO_COLOR and a runtime
3
+ * toggle so `--no-color` and non-TTY output stay clean.
4
+ */
5
+ export declare function setColorEnabled(value: boolean): void;
6
+ export declare const color: {
7
+ bold: (s: string) => string;
8
+ dim: (s: string) => string;
9
+ red: (s: string) => string;
10
+ green: (s: string) => string;
11
+ yellow: (s: string) => string;
12
+ blue: (s: string) => string;
13
+ magenta: (s: string) => string;
14
+ cyan: (s: string) => string;
15
+ gray: (s: string) => string;
16
+ };
17
+ /** Visible width of a string (ANSI escape sequences excluded). */
18
+ export declare function visibleWidth(s: string): number;
19
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAEpD;AAMD,eAAO,MAAM,KAAK;cAHL,MAAM,KAAG,MAAM;aAAf,MAAM,KAAG,MAAM;aAAf,MAAM,KAAG,MAAM;eAAf,MAAM,KAAG,MAAM;gBAAf,MAAM,KAAG,MAAM;cAAf,MAAM,KAAG,MAAM;iBAAf,MAAM,KAAG,MAAM;cAAf,MAAM,KAAG,MAAM;cAAf,MAAM,KAAG,MAAM;CAa3B,CAAC;AAEF,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG9C"}
package/dist/color.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Tiny ANSI color helper — zero dependencies. Respects NO_COLOR and a runtime
3
+ * toggle so `--no-color` and non-TTY output stay clean.
4
+ */
5
+ const ESC = "\x1b";
6
+ let enabled = detectColor();
7
+ function detectColor() {
8
+ if (process.env.NO_COLOR !== undefined)
9
+ return false;
10
+ if (process.env.FORCE_COLOR !== undefined && process.env.FORCE_COLOR !== "0")
11
+ return true;
12
+ return Boolean(process.stdout && process.stdout.isTTY);
13
+ }
14
+ export function setColorEnabled(value) {
15
+ enabled = value;
16
+ }
17
+ function wrap(open, close) {
18
+ return (s) => (enabled ? `${ESC}[${open}m${s}${ESC}[${close}m` : s);
19
+ }
20
+ export const color = {
21
+ bold: wrap(1, 22),
22
+ dim: wrap(2, 22),
23
+ red: wrap(31, 39),
24
+ green: wrap(32, 39),
25
+ yellow: wrap(33, 39),
26
+ blue: wrap(34, 39),
27
+ magenta: wrap(35, 39),
28
+ cyan: wrap(36, 39),
29
+ gray: wrap(90, 39),
30
+ };
31
+ /** Visible width of a string (ANSI escape sequences excluded). */
32
+ export function visibleWidth(s) {
33
+ // eslint-disable-next-line no-control-regex
34
+ return s.replace(/\x1b\[[0-9;]*m/g, "").length;
35
+ }
36
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.js","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,GAAG,GAAG,MAAM,CAAC;AAEnB,IAAI,OAAO,GAAG,WAAW,EAAE,CAAC;AAE5B,SAAS,WAAW;IAClB,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACrD,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1F,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,GAAG,KAAK,CAAC;AAClB,CAAC;AAED,SAAS,IAAI,CAAC,IAAY,EAAE,KAAa;IACvC,OAAO,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,IAAI,IAAI,CAAC,GAAG,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtF,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC;IACjB,GAAG,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC;IAChB,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;IACjB,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;IACnB,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;IACpB,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;IAClB,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;IACrB,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;CACnB,CAAC;AAEF,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,4CAA4C;IAC5C,OAAO,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;AACjD,CAAC"}
@@ -0,0 +1,4 @@
1
+ declare const HELP = "flakeradar analyze \u2014 detect flaky tests from existing JUnit XML reports\n\nUSAGE\n flakeradar analyze [options] <glob...>\n\n Each matched file is treated as one run by default. Point it at a directory\n of historical CI reports (one report per CI run) to find flaky tests.\n\nOPTIONS\n --group-by <file|dir> Group reports into runs. \"file\" (default) = one run\n per XML file; \"dir\" = one run per directory.\n --json Emit machine-readable JSON.\n --markdown Emit a Markdown report (great for GitHub issues/PRs).\n --fail-on-flaky Exit with code 2 if any flaky/always-failing test found.\n --no-color Disable ANSI colors.\n -h, --help Show this help.\n\nEXAMPLES\n flakeradar analyze \"test-reports/**/*.xml\"\n flakeradar analyze --group-by dir \"ci-history/*/\"\n flakeradar analyze --markdown \"reports/*.xml\" > flaky.md";
2
+ export declare function runAnalyze(argv: string[]): Promise<number>;
3
+ export { HELP as ANALYZE_HELP };
4
+ //# sourceMappingURL=analyze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,IAAI,46BAoBiD,CAAC;AAE5D,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgDhE;AAsCD,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,CAAC"}
@@ -0,0 +1,106 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { analyze } from "../analysis/flakiness.js";
4
+ import { color, setColorEnabled } from "../color.js";
5
+ import { parseJUnitXml } from "../junit/parser.js";
6
+ import { parseArgs } from "../util/args.js";
7
+ import { globMany } from "../util/glob.js";
8
+ import { exitCodeFor, renderReport, resolveFormat } from "./shared.js";
9
+ const HELP = `flakeradar analyze — detect flaky tests from existing JUnit XML reports
10
+
11
+ USAGE
12
+ flakeradar analyze [options] <glob...>
13
+
14
+ Each matched file is treated as one run by default. Point it at a directory
15
+ of historical CI reports (one report per CI run) to find flaky tests.
16
+
17
+ OPTIONS
18
+ --group-by <file|dir> Group reports into runs. "file" (default) = one run
19
+ per XML file; "dir" = one run per directory.
20
+ --json Emit machine-readable JSON.
21
+ --markdown Emit a Markdown report (great for GitHub issues/PRs).
22
+ --fail-on-flaky Exit with code 2 if any flaky/always-failing test found.
23
+ --no-color Disable ANSI colors.
24
+ -h, --help Show this help.
25
+
26
+ EXAMPLES
27
+ flakeradar analyze "test-reports/**/*.xml"
28
+ flakeradar analyze --group-by dir "ci-history/*/"
29
+ flakeradar analyze --markdown "reports/*.xml" > flaky.md`;
30
+ export async function runAnalyze(argv) {
31
+ const args = parseArgs(argv, {
32
+ bools: ["json", "markdown", "fail-on-flaky", "no-color", "help"],
33
+ values: ["group-by"],
34
+ aliases: { h: "help" },
35
+ });
36
+ if (args.bools.has("help")) {
37
+ process.stdout.write(`${HELP}\n`);
38
+ return 0;
39
+ }
40
+ if (args.bools.has("no-color"))
41
+ setColorEnabled(false);
42
+ if (args.errors.length > 0) {
43
+ return usage(args.errors.join("; "));
44
+ }
45
+ if (args.positionals.length === 0) {
46
+ return usage("no report glob provided");
47
+ }
48
+ const groupBy = args.values.get("group-by") ?? "file";
49
+ if (groupBy !== "file" && groupBy !== "dir") {
50
+ return usage(`--group-by must be "file" or "dir" (got "${groupBy}")`);
51
+ }
52
+ const files = globMany(args.positionals);
53
+ if (files.length === 0) {
54
+ process.stderr.write(color.red("No JUnit XML files matched: ") + args.positionals.join(", ") + "\n");
55
+ return 1;
56
+ }
57
+ const runs = groupBy === "dir" ? groupByDir(files) : groupByFile(files);
58
+ const withData = runs.filter((r) => r.results.length > 0);
59
+ if (withData.length === 0) {
60
+ process.stderr.write(color.red("Matched files contained no parseable test cases.\n"));
61
+ return 1;
62
+ }
63
+ if (withData.length < 2) {
64
+ process.stderr.write(color.yellow("Note: flakiness needs at least 2 runs to compare. Only 1 run of data was found — " +
65
+ "results will show pass/fail state but cannot detect flakiness.\n"));
66
+ }
67
+ const report = analyze(runs);
68
+ process.stdout.write(`${renderReport(report, resolveFormat(args))}\n`);
69
+ return exitCodeFor(report, args.bools.has("fail-on-flaky"));
70
+ }
71
+ function groupByFile(files) {
72
+ return files.map((file) => ({
73
+ label: path.basename(file),
74
+ results: readResults(file),
75
+ crashed: false,
76
+ }));
77
+ }
78
+ function groupByDir(files) {
79
+ const byDir = new Map();
80
+ for (const file of files) {
81
+ const dir = path.dirname(file);
82
+ const list = byDir.get(dir) ?? [];
83
+ list.push(file);
84
+ byDir.set(dir, list);
85
+ }
86
+ return [...byDir.keys()].sort().map((dir) => {
87
+ const results = [];
88
+ for (const file of byDir.get(dir).sort())
89
+ results.push(...readResults(file));
90
+ return { label: path.basename(dir) || dir, results, crashed: false };
91
+ });
92
+ }
93
+ function readResults(file) {
94
+ try {
95
+ return parseJUnitXml(fs.readFileSync(file, "utf8"), file);
96
+ }
97
+ catch {
98
+ return [];
99
+ }
100
+ }
101
+ function usage(message) {
102
+ process.stderr.write(color.red(`error: ${message}`) + "\n\n" + HELP + "\n");
103
+ return 1;
104
+ }
105
+ export { HELP as ANALYZE_HELP };
106
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ declare const HELP = "flakeradar run \u2014 run a test command repeatedly and detect flaky tests\n\nUSAGE\n flakeradar run [options] -- <test command>\n\n The test command must emit a JUnit XML report each run. Tell flakeradar where\n it lands in one of two ways:\n 1. Put the {out} token in your command; it's replaced with a fresh\n report path per run (recommended).\n 2. Pass --report <glob> pointing at the file(s) your command always writes.\n\nOPTIONS\n -n, --runs <N> Number of times to run the suite (default 10).\n -r, --report <glob> Where the command writes its JUnit XML each run.\n -o, --output <dir> Dir for {out} report files (default .flakeradar/runs).\n --stop-on-first-flaky Stop as soon as any flaky test is detected.\n --keep-going Keep running even if a run crashes (default: stop).\n --json Emit machine-readable JSON.\n --markdown Emit a Markdown report.\n --fail-on-flaky Exit with code 2 if any flaky test is found.\n --no-color Disable ANSI colors.\n -h, --help Show this help.\n\nEXAMPLES\n flakeradar run -n 20 -- pytest --junitxml={out}\n flakeradar run -n 15 -- npx jest --reporters=jest-junit # writes junit.xml\n # ...then: -r junit.xml\n flakeradar run -n 10 -r \"build/test-results/test/*.xml\" -- ./gradlew test\n flakeradar run -n 30 --stop-on-first-flaky -- go test ./... -v 2>&1";
2
+ export declare function runRun(argv: string[]): Promise<number>;
3
+ export { HELP as RUN_HELP };
4
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,IAAI,66CA4B4D,CAAC;AAEvE,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA8D5D;AA6BD,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,116 @@
1
+ import path from "node:path";
2
+ import { analyze } from "../analysis/flakiness.js";
3
+ import { color, setColorEnabled } from "../color.js";
4
+ import { executeRuns, OUT_TOKEN } from "../runner/runner.js";
5
+ import { parseArgs } from "../util/args.js";
6
+ import { exitCodeFor, parseRunCount, renderReport, resolveFormat, UsageError } from "./shared.js";
7
+ const HELP = `flakeradar run — run a test command repeatedly and detect flaky tests
8
+
9
+ USAGE
10
+ flakeradar run [options] -- <test command>
11
+
12
+ The test command must emit a JUnit XML report each run. Tell flakeradar where
13
+ it lands in one of two ways:
14
+ 1. Put the ${OUT_TOKEN} token in your command; it's replaced with a fresh
15
+ report path per run (recommended).
16
+ 2. Pass --report <glob> pointing at the file(s) your command always writes.
17
+
18
+ OPTIONS
19
+ -n, --runs <N> Number of times to run the suite (default 10).
20
+ -r, --report <glob> Where the command writes its JUnit XML each run.
21
+ -o, --output <dir> Dir for ${OUT_TOKEN} report files (default .flakeradar/runs).
22
+ --stop-on-first-flaky Stop as soon as any flaky test is detected.
23
+ --keep-going Keep running even if a run crashes (default: stop).
24
+ --json Emit machine-readable JSON.
25
+ --markdown Emit a Markdown report.
26
+ --fail-on-flaky Exit with code 2 if any flaky test is found.
27
+ --no-color Disable ANSI colors.
28
+ -h, --help Show this help.
29
+
30
+ EXAMPLES
31
+ flakeradar run -n 20 -- pytest --junitxml=${OUT_TOKEN}
32
+ flakeradar run -n 15 -- npx jest --reporters=jest-junit # writes junit.xml
33
+ # ...then: -r junit.xml
34
+ flakeradar run -n 10 -r "build/test-results/test/*.xml" -- ./gradlew test
35
+ flakeradar run -n 30 --stop-on-first-flaky -- go test ./... -v 2>&1`;
36
+ export async function runRun(argv) {
37
+ const args = parseArgs(argv, {
38
+ bools: ["stop-on-first-flaky", "keep-going", "json", "markdown", "fail-on-flaky", "no-color", "help"],
39
+ values: ["runs", "report", "output"],
40
+ aliases: { n: "runs", r: "report", o: "output", h: "help" },
41
+ });
42
+ if (args.bools.has("help")) {
43
+ process.stdout.write(`${HELP}\n`);
44
+ return 0;
45
+ }
46
+ if (args.bools.has("no-color"))
47
+ setColorEnabled(false);
48
+ if (args.errors.length > 0)
49
+ return usage(args.errors.join("; "));
50
+ const command = args.rest;
51
+ if (command.length === 0) {
52
+ return usage("no test command provided (put it after `--`)");
53
+ }
54
+ let runs;
55
+ try {
56
+ runs = parseRunCount(args.values.get("runs"), 10);
57
+ }
58
+ catch (err) {
59
+ if (err instanceof UsageError)
60
+ return usage(err.message);
61
+ throw err;
62
+ }
63
+ const reportGlob = args.values.get("report");
64
+ const usesToken = command.some((a) => a.includes(OUT_TOKEN));
65
+ if (!usesToken && !reportGlob) {
66
+ return usage(`don't know where to read test reports. Add the ${OUT_TOKEN} token to your command, or pass --report <glob>.`);
67
+ }
68
+ const outputDir = args.values.get("output") ?? path.join(".flakeradar", "runs");
69
+ const format = resolveFormat(args);
70
+ const quiet = format !== "terminal";
71
+ const collected = await executeRuns({
72
+ command,
73
+ runs,
74
+ reportGlob,
75
+ outputDir,
76
+ keepGoing: args.bools.has("keep-going"),
77
+ onProgress: quiet ? undefined : (info) => printProgress(info),
78
+ afterRun: args.bools.has("stop-on-first-flaky") ? stopWhenFlaky : undefined,
79
+ });
80
+ if (!quiet)
81
+ process.stderr.write("\n");
82
+ const dataRuns = collected.filter((r) => r.results.length > 0);
83
+ if (dataRuns.length === 0) {
84
+ process.stderr.write(color.red("No test reports were produced across any run.\n"));
85
+ const firstNote = collected.find((r) => r.note)?.note;
86
+ if (firstNote)
87
+ process.stderr.write(color.dim(`Hint: ${firstNote}\n`));
88
+ return 1;
89
+ }
90
+ const report = analyze(collected);
91
+ process.stdout.write(`${renderReport(report, format)}\n`);
92
+ return exitCodeFor(report, args.bools.has("fail-on-flaky"));
93
+ }
94
+ function stopWhenFlaky(runs) {
95
+ return analyze(runs).summary.flakyCount > 0;
96
+ }
97
+ function printProgress(info) {
98
+ const status = info.crashed
99
+ ? color.red("CRASH")
100
+ : info.failed > 0
101
+ ? color.yellow(`${info.failed} failed`)
102
+ : color.green("all passed");
103
+ const bar = renderBar(info.index, info.total);
104
+ process.stderr.write(`\r${color.dim(`[${info.index}/${info.total}]`)} ${bar} ${status.padEnd(20)} ${color.dim(`${info.durationMs}ms`)} `);
105
+ }
106
+ function renderBar(done, total) {
107
+ const width = 20;
108
+ const filled = Math.round((done / total) * width);
109
+ return color.cyan("█".repeat(filled)) + color.gray("░".repeat(Math.max(0, width - filled)));
110
+ }
111
+ function usage(message) {
112
+ process.stderr.write(color.red(`error: ${message}`) + "\n\n" + HELP + "\n");
113
+ return 1;
114
+ }
115
+ export { HELP as RUN_HELP };
116
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { AnalysisReport } from "../types.js";
2
+ import type { ParsedArgs } from "../util/args.js";
3
+ export type Format = "terminal" | "json" | "markdown";
4
+ export declare function resolveFormat(args: ParsedArgs): Format;
5
+ export declare function renderReport(report: AnalysisReport, format: Format): string;
6
+ /**
7
+ * Exit code convention:
8
+ * 0 success
9
+ * 1 usage / input error
10
+ * 2 flaky (or always-failing) tests detected AND --fail-on-flaky was set
11
+ */
12
+ export declare function exitCodeFor(report: AnalysisReport, failOnFlaky: boolean): number;
13
+ export declare function parseRunCount(raw: string | undefined, fallback: number): number;
14
+ export declare class UsageError extends Error {
15
+ }
16
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/commands/shared.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AAEtD,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAItD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAS3E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,OAAO,GAAG,MAAM,CAIhF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO/E;AAED,qBAAa,UAAW,SAAQ,KAAK;CAAG"}