@flakiness/sdk 0.151.0 → 0.153.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/README.md +4 -3
- package/lib/browser.js +73 -7
- package/lib/index.js +307 -164
- package/package.json +5 -6
- package/types/src/_telemetry.d.ts +7 -0
- package/types/src/_telemetry.d.ts.map +1 -0
- package/types/src/collectSources.d.ts +14 -0
- package/types/src/collectSources.d.ts.map +1 -0
- package/types/src/cpuUtilization.d.ts +30 -0
- package/types/src/cpuUtilization.d.ts.map +1 -0
- package/types/src/createEnvironment.d.ts +4 -8
- package/types/src/createEnvironment.d.ts.map +1 -1
- package/types/src/index.d.ts +3 -2
- package/types/src/index.d.ts.map +1 -1
- package/types/src/normalizeReport.d.ts +1 -0
- package/types/src/normalizeReport.d.ts.map +1 -1
- package/types/src/ramUtilization.d.ts +28 -0
- package/types/src/ramUtilization.d.ts.map +1 -0
- package/types/src/reportUtils.d.ts +2 -1
- package/types/src/reportUtils.d.ts.map +1 -1
- package/types/src/reportUtilsBrowser.d.ts +1 -0
- package/types/src/reportUtilsBrowser.d.ts.map +1 -1
- package/types/src/validateReport.d.ts +9 -0
- package/types/src/validateReport.d.ts.map +1 -0
- package/types/src/createTestStepSnippets.d.ts +0 -30
- package/types/src/createTestStepSnippets.d.ts.map +0 -1
- package/types/src/systemUtilizationSampler.d.ts +0 -43
- package/types/src/systemUtilizationSampler.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -40,8 +40,8 @@ const report: FlakinessReport.Report = {
|
|
|
40
40
|
location: { file: 'test.spec.ts', line: 10, column: 1 },
|
|
41
41
|
attempts: [{
|
|
42
42
|
environmentIdx: 0,
|
|
43
|
+
status: 'passed',
|
|
43
44
|
expectedStatus: 'passed',
|
|
44
|
-
actualStatus: 'passed',
|
|
45
45
|
duration: 100 as FlakinessReport.DurationMS,
|
|
46
46
|
}],
|
|
47
47
|
}],
|
|
@@ -87,11 +87,12 @@ Use this entry point when you need to process or manipulate reports in browser-b
|
|
|
87
87
|
- **`ReportUtils`** - Namespace with utilities for report creation and manipulation:
|
|
88
88
|
- `createEnvironment()` - Create environment objects with system information
|
|
89
89
|
- `normalizeReport()` - Deduplicate environments, suites, and tests
|
|
90
|
-
- `
|
|
90
|
+
- `collectSources()` - Extract source code snippets for locations in the report
|
|
91
91
|
- `stripAnsi()` - Remove ANSI escape codes from strings
|
|
92
92
|
- `visitTests()` - Recursively visit all tests in a report
|
|
93
93
|
- `createFileAttachment()` / `createDataAttachment()` - Create report attachments
|
|
94
|
-
- **`
|
|
94
|
+
- **`CPUUtilization`** - Track CPU utilization over time via periodic sampling
|
|
95
|
+
- **`RAMUtilization`** - Track RAM utilization over time via periodic sampling
|
|
95
96
|
|
|
96
97
|
### Working with Reports
|
|
97
98
|
- **`showReport()`** - Start a local server and open the report in your browser
|
package/lib/browser.js
CHANGED
|
@@ -5,13 +5,14 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/browser.ts
|
|
8
|
-
import { FlakinessReport } from "@flakiness/flakiness-report";
|
|
8
|
+
import { FlakinessReport as FlakinessReport2 } from "@flakiness/flakiness-report";
|
|
9
9
|
|
|
10
10
|
// src/reportUtilsBrowser.ts
|
|
11
11
|
var reportUtilsBrowser_exports = {};
|
|
12
12
|
__export(reportUtilsBrowser_exports, {
|
|
13
13
|
normalizeReport: () => normalizeReport,
|
|
14
14
|
stripAnsi: () => stripAnsi,
|
|
15
|
+
validateReport: () => validateReport,
|
|
15
16
|
visitTests: () => visitTests
|
|
16
17
|
});
|
|
17
18
|
|
|
@@ -29,6 +30,47 @@ var Multimap = class {
|
|
|
29
30
|
}
|
|
30
31
|
};
|
|
31
32
|
function normalizeReport(report) {
|
|
33
|
+
report = deduplicateTestsSuitesEnvironments(report);
|
|
34
|
+
function cleanupTestStep(step) {
|
|
35
|
+
return {
|
|
36
|
+
...step,
|
|
37
|
+
duration: step.duration === 0 ? void 0 : step.duration,
|
|
38
|
+
steps: step.steps && step.steps.length ? step.steps.map(cleanupTestStep) : void 0
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function cleanupAttempt(attempt) {
|
|
42
|
+
return {
|
|
43
|
+
...attempt,
|
|
44
|
+
status: attempt.status === "passed" ? void 0 : attempt.status,
|
|
45
|
+
expectedStatus: attempt.expectedStatus === "passed" ? void 0 : attempt.expectedStatus,
|
|
46
|
+
environmentIdx: attempt.environmentIdx === 0 ? void 0 : attempt.environmentIdx,
|
|
47
|
+
duration: attempt.duration === 0 ? void 0 : attempt.duration,
|
|
48
|
+
stdout: attempt.stdout && attempt.stdout.length ? attempt.stdout : void 0,
|
|
49
|
+
stderr: attempt.stderr && attempt.stderr.length ? attempt.stderr : void 0,
|
|
50
|
+
attachments: attempt.attachments && attempt.attachments.length ? attempt.attachments : void 0,
|
|
51
|
+
steps: attempt.steps && attempt.steps.length ? attempt.steps.map(cleanupTestStep) : void 0
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function cleanupTest(test) {
|
|
55
|
+
return {
|
|
56
|
+
...test,
|
|
57
|
+
attempts: test.attempts.map(cleanupAttempt)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function cleanupSuite(suite) {
|
|
61
|
+
return {
|
|
62
|
+
...suite,
|
|
63
|
+
tests: suite.tests && suite.tests.length ? suite.tests.map(cleanupTest) : void 0,
|
|
64
|
+
suites: suite.suites && suite.suites.length ? suite.suites.map(cleanupSuite) : void 0
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...report,
|
|
69
|
+
tests: report.tests && report.tests.length ? report.tests.map(cleanupTest) : void 0,
|
|
70
|
+
suites: report.suites && report.suites.length ? report.suites.map(cleanupSuite) : void 0
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function deduplicateTestsSuitesEnvironments(report) {
|
|
32
74
|
const gEnvs = /* @__PURE__ */ new Map();
|
|
33
75
|
const gSuites = /* @__PURE__ */ new Map();
|
|
34
76
|
const gTests = new Multimap();
|
|
@@ -50,9 +92,15 @@ function normalizeReport(report) {
|
|
|
50
92
|
gTestIds.set(test, testId);
|
|
51
93
|
gSuiteTests.set(suiteId, test);
|
|
52
94
|
for (const attempt of test.attempts) {
|
|
53
|
-
const env = report.environments[attempt.environmentIdx];
|
|
95
|
+
const env = report.environments[attempt.environmentIdx ?? 0];
|
|
54
96
|
const envId = gEnvIds.get(env);
|
|
55
97
|
usedEnvIds.add(envId);
|
|
98
|
+
if (attempt.annotations && !attempt.annotations.length)
|
|
99
|
+
delete attempt.annotations;
|
|
100
|
+
if (attempt.stdout && !attempt.stdout.length)
|
|
101
|
+
delete attempt.stdout;
|
|
102
|
+
if (attempt.stderr && !attempt.stderr.length)
|
|
103
|
+
delete attempt.stderr;
|
|
56
104
|
}
|
|
57
105
|
}
|
|
58
106
|
}
|
|
@@ -77,7 +125,7 @@ function normalizeReport(report) {
|
|
|
77
125
|
tags: tags.length ? tags : void 0,
|
|
78
126
|
attempts: tests2.map((t) => t.attempts).flat().map((attempt) => ({
|
|
79
127
|
...attempt,
|
|
80
|
-
environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx]))
|
|
128
|
+
environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx ?? 0]))
|
|
81
129
|
}))
|
|
82
130
|
};
|
|
83
131
|
});
|
|
@@ -96,14 +144,14 @@ function normalizeReport(report) {
|
|
|
96
144
|
});
|
|
97
145
|
}
|
|
98
146
|
visitTests2(report.tests ?? [], "suiteless");
|
|
99
|
-
for (const suite of report.suites)
|
|
147
|
+
for (const suite of report.suites ?? [])
|
|
100
148
|
visitSuite(suite);
|
|
101
149
|
const newEnvironments = [...usedEnvIds];
|
|
102
150
|
const envIdToIndex = new Map(newEnvironments.map((envId, index) => [envId, index]));
|
|
103
151
|
return {
|
|
104
152
|
...report,
|
|
105
153
|
environments: newEnvironments.map((envId) => gEnvs.get(envId)),
|
|
106
|
-
suites: transformSuites(report.suites),
|
|
154
|
+
suites: transformSuites(report.suites ?? []),
|
|
107
155
|
tests: transformTests(report.tests ?? [])
|
|
108
156
|
};
|
|
109
157
|
}
|
|
@@ -126,6 +174,24 @@ function computeTestId(test, suiteId) {
|
|
|
126
174
|
});
|
|
127
175
|
}
|
|
128
176
|
|
|
177
|
+
// src/validateReport.ts
|
|
178
|
+
import { Schema } from "@flakiness/flakiness-report";
|
|
179
|
+
import z from "zod";
|
|
180
|
+
function validateReport(report) {
|
|
181
|
+
const validation = Schema.Report.safeParse(report);
|
|
182
|
+
if (!validation.success) {
|
|
183
|
+
const MAX_ISSUES = 5;
|
|
184
|
+
const allIssues = validation.error.issues;
|
|
185
|
+
const shownIssues = allIssues.slice(0, MAX_ISSUES);
|
|
186
|
+
const remaining = allIssues.length - shownIssues.length;
|
|
187
|
+
const base = [z.prettifyError(new z.ZodError(shownIssues))];
|
|
188
|
+
if (remaining > 0)
|
|
189
|
+
base.push(`... and ${remaining} more issue${remaining === 1 ? "" : "s"} ...`);
|
|
190
|
+
return base.join("\n");
|
|
191
|
+
}
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
129
195
|
// src/stripAnsi.ts
|
|
130
196
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
131
197
|
function stripAnsi(str) {
|
|
@@ -144,11 +210,11 @@ function visitTests(report, testVisitor) {
|
|
|
144
210
|
}
|
|
145
211
|
for (const test of report.tests ?? [])
|
|
146
212
|
testVisitor(test, []);
|
|
147
|
-
for (const suite of report.suites)
|
|
213
|
+
for (const suite of report.suites ?? [])
|
|
148
214
|
visitSuite(suite, []);
|
|
149
215
|
}
|
|
150
216
|
export {
|
|
151
|
-
FlakinessReport,
|
|
217
|
+
FlakinessReport2 as FlakinessReport,
|
|
152
218
|
reportUtilsBrowser_exports as ReportUtils
|
|
153
219
|
};
|
|
154
220
|
//# sourceMappingURL=browser.js.map
|
package/lib/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
-
import { FlakinessReport } from "@flakiness/flakiness-report";
|
|
8
|
+
import { FlakinessReport as FlakinessReport2, Schema as Schema2 } from "@flakiness/flakiness-report";
|
|
9
9
|
|
|
10
10
|
// src/ciUtils.ts
|
|
11
11
|
var CIUtils;
|
|
@@ -46,6 +46,86 @@ function azure() {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// src/cpuUtilization.ts
|
|
50
|
+
import os from "os";
|
|
51
|
+
|
|
52
|
+
// src/_telemetry.ts
|
|
53
|
+
function addTelemetryPoint(collection, newPoint, precision) {
|
|
54
|
+
const lastPoint = collection.at(-1);
|
|
55
|
+
const preLastPoint = collection.at(-2);
|
|
56
|
+
if (lastPoint && preLastPoint && Math.abs(lastPoint.value - preLastPoint.value) < precision && Math.abs(lastPoint.value - newPoint.value) < precision) {
|
|
57
|
+
lastPoint.timestamp = newPoint.timestamp;
|
|
58
|
+
} else {
|
|
59
|
+
collection.push(newPoint);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function toProtocolTelemetry(collection) {
|
|
63
|
+
if (!collection.length)
|
|
64
|
+
return [];
|
|
65
|
+
let lastTimestamp = collection[0].timestamp;
|
|
66
|
+
return collection.map((x, idx) => {
|
|
67
|
+
const dts = idx === 0 ? x.timestamp : x.timestamp - lastTimestamp;
|
|
68
|
+
lastTimestamp = x.timestamp;
|
|
69
|
+
return [dts, Math.round(x.value * 100) / 100];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/cpuUtilization.ts
|
|
74
|
+
function sampleCpus() {
|
|
75
|
+
return os.cpus().map((cpu) => {
|
|
76
|
+
const totalTicks = cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq + cpu.times.idle;
|
|
77
|
+
const idleTicks = cpu.times.idle;
|
|
78
|
+
const busyTicks = totalTicks - idleTicks;
|
|
79
|
+
return { totalTicks, busyTicks };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
var CPUUtilization = class {
|
|
83
|
+
_lastSample = sampleCpus();
|
|
84
|
+
_precision;
|
|
85
|
+
_cpuAvg = [];
|
|
86
|
+
_cpuMax = [];
|
|
87
|
+
/**
|
|
88
|
+
* @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 7.
|
|
89
|
+
*/
|
|
90
|
+
constructor(options) {
|
|
91
|
+
this._precision = options?.precision ?? 7;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Records the current CPU utilization since the last sample.
|
|
95
|
+
* The first call primes the baseline; subsequent calls record deltas.
|
|
96
|
+
*/
|
|
97
|
+
sample() {
|
|
98
|
+
const newSample = sampleCpus();
|
|
99
|
+
if (newSample.length === this._lastSample.length) {
|
|
100
|
+
const utilization = newSample.map(
|
|
101
|
+
(cpu, idx) => (
|
|
102
|
+
// If the CPU did no work since the last sample, then it's
|
|
103
|
+
// utilization is effectively 0.
|
|
104
|
+
cpu.totalTicks === this._lastSample[idx].totalTicks ? 0 : (cpu.busyTicks - this._lastSample[idx].busyTicks) / (cpu.totalTicks - this._lastSample[idx].totalTicks) * 100
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
const timestamp = Date.now();
|
|
108
|
+
addTelemetryPoint(this._cpuAvg, {
|
|
109
|
+
timestamp,
|
|
110
|
+
value: utilization.reduce((acc, x) => acc + x) / utilization.length
|
|
111
|
+
}, this._precision);
|
|
112
|
+
addTelemetryPoint(this._cpuMax, {
|
|
113
|
+
timestamp,
|
|
114
|
+
value: Math.max(...utilization)
|
|
115
|
+
}, this._precision);
|
|
116
|
+
}
|
|
117
|
+
this._lastSample = newSample;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Adds collected CPU telemetry to the report.
|
|
121
|
+
*/
|
|
122
|
+
enrich(report) {
|
|
123
|
+
report.cpuCount = os.cpus().length;
|
|
124
|
+
report.cpuMax = toProtocolTelemetry(this._cpuMax);
|
|
125
|
+
report.cpuAvg = toProtocolTelemetry(this._cpuAvg);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
49
129
|
// src/gitWorktree.ts
|
|
50
130
|
import assert from "assert";
|
|
51
131
|
import { exec } from "child_process";
|
|
@@ -335,23 +415,155 @@ async function listCommits(gitRoot, head, count) {
|
|
|
335
415
|
}
|
|
336
416
|
}
|
|
337
417
|
|
|
418
|
+
// src/ramUtilization.ts
|
|
419
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
420
|
+
import os2 from "os";
|
|
421
|
+
function getAvailableMemMacOS() {
|
|
422
|
+
const lines = spawnSync2("vm_stat", { encoding: "utf8" }).stdout.trim().split("\n");
|
|
423
|
+
const pageSize = parseInt(lines[0].match(/page size of (\d+) bytes/)[1], 10);
|
|
424
|
+
if (isNaN(pageSize)) {
|
|
425
|
+
console.warn("[flakiness.io] Error detecting macos page size");
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
let totalFree = 0;
|
|
429
|
+
for (const line of lines) {
|
|
430
|
+
if (/Pages (free|inactive|speculative):/.test(line)) {
|
|
431
|
+
const match = line.match(/\d+/);
|
|
432
|
+
if (match)
|
|
433
|
+
totalFree += parseInt(match[0], 10);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return totalFree * pageSize;
|
|
437
|
+
}
|
|
438
|
+
var RAMUtilization = class {
|
|
439
|
+
_precision;
|
|
440
|
+
_totalBytes = os2.totalmem();
|
|
441
|
+
_ram = [];
|
|
442
|
+
/**
|
|
443
|
+
* @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 1.
|
|
444
|
+
*/
|
|
445
|
+
constructor(options) {
|
|
446
|
+
this._precision = options?.precision ?? 1;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Records the current RAM utilization.
|
|
450
|
+
*/
|
|
451
|
+
sample() {
|
|
452
|
+
const freeBytes = os2.platform() === "darwin" ? getAvailableMemMacOS() : os2.freemem();
|
|
453
|
+
addTelemetryPoint(this._ram, {
|
|
454
|
+
timestamp: Date.now(),
|
|
455
|
+
value: (this._totalBytes - freeBytes) / this._totalBytes * 100
|
|
456
|
+
}, this._precision);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Adds collected RAM telemetry to the report.
|
|
460
|
+
*/
|
|
461
|
+
enrich(report) {
|
|
462
|
+
report.ramBytes = this._totalBytes;
|
|
463
|
+
report.ram = toProtocolTelemetry(this._ram);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
338
467
|
// src/reportUtils.ts
|
|
339
468
|
var reportUtils_exports = {};
|
|
340
469
|
__export(reportUtils_exports, {
|
|
470
|
+
collectSources: () => collectSources,
|
|
341
471
|
createDataAttachment: () => createDataAttachment,
|
|
342
472
|
createEnvironment: () => createEnvironment,
|
|
343
473
|
createFileAttachment: () => createFileAttachment,
|
|
344
|
-
createTestStepSnippetsInplace: () => createTestStepSnippetsInplace,
|
|
345
474
|
normalizeReport: () => normalizeReport,
|
|
346
475
|
stripAnsi: () => stripAnsi,
|
|
476
|
+
validateReport: () => validateReport,
|
|
347
477
|
visitTests: () => visitTests
|
|
348
478
|
});
|
|
349
479
|
|
|
350
|
-
// src/
|
|
480
|
+
// src/collectSources.ts
|
|
351
481
|
import fs2 from "fs";
|
|
352
|
-
|
|
482
|
+
function collectLocationsFromTestStep(testStep, onLocation) {
|
|
483
|
+
onLocation(testStep.location);
|
|
484
|
+
for (const step of testStep.steps ?? [])
|
|
485
|
+
collectLocationsFromTestStep(step, onLocation);
|
|
486
|
+
}
|
|
487
|
+
function collectLocationsFromTest(test, onLocation) {
|
|
488
|
+
onLocation(test.location);
|
|
489
|
+
for (const attempt of test.attempts) {
|
|
490
|
+
for (const annotation of attempt.annotations ?? [])
|
|
491
|
+
onLocation(annotation.location);
|
|
492
|
+
for (const err of attempt.errors ?? [])
|
|
493
|
+
onLocation(err.location);
|
|
494
|
+
for (const step of attempt.steps ?? [])
|
|
495
|
+
collectLocationsFromTestStep(step, onLocation);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function collectLocationsFromSuite(suite, onLocation) {
|
|
499
|
+
onLocation(suite.location);
|
|
500
|
+
for (const child of suite.suites ?? [])
|
|
501
|
+
collectLocationsFromSuite(child, onLocation);
|
|
502
|
+
for (const test of suite.tests ?? [])
|
|
503
|
+
collectLocationsFromTest(test, onLocation);
|
|
504
|
+
}
|
|
505
|
+
function collectLocationsFromReport(report, onLocation) {
|
|
506
|
+
for (const e of report.unattributedErrors ?? [])
|
|
507
|
+
onLocation(e.location);
|
|
508
|
+
for (const test of report.tests ?? [])
|
|
509
|
+
collectLocationsFromTest(test, onLocation);
|
|
510
|
+
for (const suite of report.suites ?? [])
|
|
511
|
+
collectLocationsFromSuite(suite, onLocation);
|
|
512
|
+
}
|
|
513
|
+
function lineNumbersToChunks(lineNumbers, options) {
|
|
514
|
+
const context = options.context;
|
|
515
|
+
const result = [];
|
|
516
|
+
let current;
|
|
517
|
+
for (const ln of Array.from(lineNumbers).sort((a, b) => a - b)) {
|
|
518
|
+
const span = [ln - context, ln + context];
|
|
519
|
+
if (!current || current[1] + 1 < span[0]) {
|
|
520
|
+
result.push(span);
|
|
521
|
+
current = span;
|
|
522
|
+
} else {
|
|
523
|
+
current[1] = span[1];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
528
|
+
function collectSources(worktree, report) {
|
|
529
|
+
const filesToLines = /* @__PURE__ */ new Map();
|
|
530
|
+
collectLocationsFromReport(report, (location) => {
|
|
531
|
+
if (!location)
|
|
532
|
+
return;
|
|
533
|
+
let lineNumbers = filesToLines.get(location.file);
|
|
534
|
+
if (!lineNumbers) {
|
|
535
|
+
lineNumbers = /* @__PURE__ */ new Set();
|
|
536
|
+
filesToLines.set(location.file, lineNumbers);
|
|
537
|
+
}
|
|
538
|
+
lineNumbers.add(location.line);
|
|
539
|
+
});
|
|
540
|
+
const sources = [];
|
|
541
|
+
for (const [gitFilePath, lineNumbers] of filesToLines) {
|
|
542
|
+
let source;
|
|
543
|
+
try {
|
|
544
|
+
source = fs2.readFileSync(worktree.absolutePath(gitFilePath), "utf-8");
|
|
545
|
+
} catch (e) {
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
const sourceLines = source.split("\n");
|
|
549
|
+
for (const chunk of lineNumbersToChunks(lineNumbers, { context: 5 })) {
|
|
550
|
+
const from = Math.max(chunk[0] - 1, 0);
|
|
551
|
+
const to = Math.min(chunk[1], sourceLines.length);
|
|
552
|
+
sources.push({
|
|
553
|
+
filePath: gitFilePath,
|
|
554
|
+
lineOffset: from !== 0 ? from + 1 : void 0,
|
|
555
|
+
text: sourceLines.slice(from, to).join("\n")
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
report.sources = sources;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/createEnvironment.ts
|
|
563
|
+
import fs3 from "fs";
|
|
564
|
+
import os3 from "os";
|
|
353
565
|
function readLinuxOSRelease() {
|
|
354
|
-
const osReleaseText =
|
|
566
|
+
const osReleaseText = fs3.readFileSync("/etc/os-release", "utf-8");
|
|
355
567
|
return new Map(osReleaseText.toLowerCase().split("\n").filter((line) => line.includes("=")).map((line) => {
|
|
356
568
|
line = line.trim();
|
|
357
569
|
let [key, value] = line.split("=");
|
|
@@ -376,7 +588,7 @@ function osDarwinInfo() {
|
|
|
376
588
|
function osWinInfo() {
|
|
377
589
|
const name = "win";
|
|
378
590
|
const arch = process.arch;
|
|
379
|
-
const version =
|
|
591
|
+
const version = os3.release();
|
|
380
592
|
return { name, arch, version };
|
|
381
593
|
}
|
|
382
594
|
function getOSInfo() {
|
|
@@ -401,74 +613,11 @@ function createEnvironment(options) {
|
|
|
401
613
|
osName: osInfo.name,
|
|
402
614
|
osVersion: osInfo.version
|
|
403
615
|
},
|
|
404
|
-
|
|
616
|
+
metadata: {
|
|
405
617
|
...extractEnvConfiguration(),
|
|
406
|
-
...options.
|
|
407
|
-
},
|
|
408
|
-
opaqueData: options.opaqueData
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// src/createTestStepSnippets.ts
|
|
413
|
-
import { codeFrameColumns } from "@babel/code-frame";
|
|
414
|
-
import fs3 from "fs";
|
|
415
|
-
|
|
416
|
-
// src/visitTests.ts
|
|
417
|
-
function visitTests(report, testVisitor) {
|
|
418
|
-
function visitSuite(suite, parents) {
|
|
419
|
-
parents.push(suite);
|
|
420
|
-
for (const test of suite.tests ?? [])
|
|
421
|
-
testVisitor(test, parents);
|
|
422
|
-
for (const childSuite of suite.suites ?? [])
|
|
423
|
-
visitSuite(childSuite, parents);
|
|
424
|
-
parents.pop();
|
|
425
|
-
}
|
|
426
|
-
for (const test of report.tests ?? [])
|
|
427
|
-
testVisitor(test, []);
|
|
428
|
-
for (const suite of report.suites)
|
|
429
|
-
visitSuite(suite, []);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// src/createTestStepSnippets.ts
|
|
433
|
-
function createTestStepSnippetsInplace(worktree, report) {
|
|
434
|
-
const allSteps = /* @__PURE__ */ new Map();
|
|
435
|
-
visitTests(report, (test) => {
|
|
436
|
-
for (const attempt of test.attempts) {
|
|
437
|
-
for (const step of attempt.steps ?? []) {
|
|
438
|
-
if (!step.location)
|
|
439
|
-
continue;
|
|
440
|
-
let fileSteps = allSteps.get(step.location.file);
|
|
441
|
-
if (!fileSteps) {
|
|
442
|
-
fileSteps = /* @__PURE__ */ new Set();
|
|
443
|
-
allSteps.set(step.location.file, fileSteps);
|
|
444
|
-
}
|
|
445
|
-
fileSteps.add(step);
|
|
446
|
-
}
|
|
618
|
+
...options.metadata ?? {}
|
|
447
619
|
}
|
|
448
|
-
}
|
|
449
|
-
for (const [gitFilePath, steps] of allSteps) {
|
|
450
|
-
let source;
|
|
451
|
-
try {
|
|
452
|
-
source = fs3.readFileSync(worktree.absolutePath(gitFilePath), "utf-8");
|
|
453
|
-
} catch (e) {
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
const lines = source.split("\n").length;
|
|
457
|
-
const highlighted = codeFrameColumns(source, { start: { line: lines, column: 1 } }, { highlightCode: true, linesAbove: lines, linesBelow: 0 });
|
|
458
|
-
const highlightedLines = highlighted.split("\n");
|
|
459
|
-
const lineWithArrow = highlightedLines[highlightedLines.length - 1];
|
|
460
|
-
for (const step of steps) {
|
|
461
|
-
if (!step.location)
|
|
462
|
-
continue;
|
|
463
|
-
if (step.location.line < 2 || step.location.line >= lines)
|
|
464
|
-
continue;
|
|
465
|
-
const snippetLines = highlightedLines.slice(step.location.line - 2, step.location.line + 1);
|
|
466
|
-
const index = lineWithArrow.indexOf("^");
|
|
467
|
-
const shiftedArrow = lineWithArrow.slice(0, index) + " ".repeat(step.location.column - 1) + lineWithArrow.slice(index);
|
|
468
|
-
snippetLines.splice(2, 0, shiftedArrow);
|
|
469
|
-
step.snippet = snippetLines.join("\n");
|
|
470
|
-
}
|
|
471
|
-
}
|
|
620
|
+
};
|
|
472
621
|
}
|
|
473
622
|
|
|
474
623
|
// src/normalizeReport.ts
|
|
@@ -485,6 +634,47 @@ var Multimap = class {
|
|
|
485
634
|
}
|
|
486
635
|
};
|
|
487
636
|
function normalizeReport(report) {
|
|
637
|
+
report = deduplicateTestsSuitesEnvironments(report);
|
|
638
|
+
function cleanupTestStep(step) {
|
|
639
|
+
return {
|
|
640
|
+
...step,
|
|
641
|
+
duration: step.duration === 0 ? void 0 : step.duration,
|
|
642
|
+
steps: step.steps && step.steps.length ? step.steps.map(cleanupTestStep) : void 0
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function cleanupAttempt(attempt) {
|
|
646
|
+
return {
|
|
647
|
+
...attempt,
|
|
648
|
+
status: attempt.status === "passed" ? void 0 : attempt.status,
|
|
649
|
+
expectedStatus: attempt.expectedStatus === "passed" ? void 0 : attempt.expectedStatus,
|
|
650
|
+
environmentIdx: attempt.environmentIdx === 0 ? void 0 : attempt.environmentIdx,
|
|
651
|
+
duration: attempt.duration === 0 ? void 0 : attempt.duration,
|
|
652
|
+
stdout: attempt.stdout && attempt.stdout.length ? attempt.stdout : void 0,
|
|
653
|
+
stderr: attempt.stderr && attempt.stderr.length ? attempt.stderr : void 0,
|
|
654
|
+
attachments: attempt.attachments && attempt.attachments.length ? attempt.attachments : void 0,
|
|
655
|
+
steps: attempt.steps && attempt.steps.length ? attempt.steps.map(cleanupTestStep) : void 0
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function cleanupTest(test) {
|
|
659
|
+
return {
|
|
660
|
+
...test,
|
|
661
|
+
attempts: test.attempts.map(cleanupAttempt)
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function cleanupSuite(suite) {
|
|
665
|
+
return {
|
|
666
|
+
...suite,
|
|
667
|
+
tests: suite.tests && suite.tests.length ? suite.tests.map(cleanupTest) : void 0,
|
|
668
|
+
suites: suite.suites && suite.suites.length ? suite.suites.map(cleanupSuite) : void 0
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
...report,
|
|
673
|
+
tests: report.tests && report.tests.length ? report.tests.map(cleanupTest) : void 0,
|
|
674
|
+
suites: report.suites && report.suites.length ? report.suites.map(cleanupSuite) : void 0
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function deduplicateTestsSuitesEnvironments(report) {
|
|
488
678
|
const gEnvs = /* @__PURE__ */ new Map();
|
|
489
679
|
const gSuites = /* @__PURE__ */ new Map();
|
|
490
680
|
const gTests = new Multimap();
|
|
@@ -506,9 +696,15 @@ function normalizeReport(report) {
|
|
|
506
696
|
gTestIds.set(test, testId);
|
|
507
697
|
gSuiteTests.set(suiteId, test);
|
|
508
698
|
for (const attempt of test.attempts) {
|
|
509
|
-
const env = report.environments[attempt.environmentIdx];
|
|
699
|
+
const env = report.environments[attempt.environmentIdx ?? 0];
|
|
510
700
|
const envId = gEnvIds.get(env);
|
|
511
701
|
usedEnvIds.add(envId);
|
|
702
|
+
if (attempt.annotations && !attempt.annotations.length)
|
|
703
|
+
delete attempt.annotations;
|
|
704
|
+
if (attempt.stdout && !attempt.stdout.length)
|
|
705
|
+
delete attempt.stdout;
|
|
706
|
+
if (attempt.stderr && !attempt.stderr.length)
|
|
707
|
+
delete attempt.stderr;
|
|
512
708
|
}
|
|
513
709
|
}
|
|
514
710
|
}
|
|
@@ -533,7 +729,7 @@ function normalizeReport(report) {
|
|
|
533
729
|
tags: tags.length ? tags : void 0,
|
|
534
730
|
attempts: tests2.map((t) => t.attempts).flat().map((attempt) => ({
|
|
535
731
|
...attempt,
|
|
536
|
-
environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx]))
|
|
732
|
+
environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx ?? 0]))
|
|
537
733
|
}))
|
|
538
734
|
};
|
|
539
735
|
});
|
|
@@ -552,14 +748,14 @@ function normalizeReport(report) {
|
|
|
552
748
|
});
|
|
553
749
|
}
|
|
554
750
|
visitTests2(report.tests ?? [], "suiteless");
|
|
555
|
-
for (const suite of report.suites)
|
|
751
|
+
for (const suite of report.suites ?? [])
|
|
556
752
|
visitSuite(suite);
|
|
557
753
|
const newEnvironments = [...usedEnvIds];
|
|
558
754
|
const envIdToIndex = new Map(newEnvironments.map((envId, index) => [envId, index]));
|
|
559
755
|
return {
|
|
560
756
|
...report,
|
|
561
757
|
environments: newEnvironments.map((envId) => gEnvs.get(envId)),
|
|
562
|
-
suites: transformSuites(report.suites),
|
|
758
|
+
suites: transformSuites(report.suites ?? []),
|
|
563
759
|
tests: transformTests(report.tests ?? [])
|
|
564
760
|
};
|
|
565
761
|
}
|
|
@@ -582,6 +778,24 @@ function computeTestId(test, suiteId) {
|
|
|
582
778
|
});
|
|
583
779
|
}
|
|
584
780
|
|
|
781
|
+
// src/validateReport.ts
|
|
782
|
+
import { Schema } from "@flakiness/flakiness-report";
|
|
783
|
+
import z from "zod";
|
|
784
|
+
function validateReport(report) {
|
|
785
|
+
const validation = Schema.Report.safeParse(report);
|
|
786
|
+
if (!validation.success) {
|
|
787
|
+
const MAX_ISSUES = 5;
|
|
788
|
+
const allIssues = validation.error.issues;
|
|
789
|
+
const shownIssues = allIssues.slice(0, MAX_ISSUES);
|
|
790
|
+
const remaining = allIssues.length - shownIssues.length;
|
|
791
|
+
const base = [z.prettifyError(new z.ZodError(shownIssues))];
|
|
792
|
+
if (remaining > 0)
|
|
793
|
+
base.push(`... and ${remaining} more issue${remaining === 1 ? "" : "s"} ...`);
|
|
794
|
+
return base.join("\n");
|
|
795
|
+
}
|
|
796
|
+
return void 0;
|
|
797
|
+
}
|
|
798
|
+
|
|
585
799
|
// src/stripAnsi.ts
|
|
586
800
|
var ansiRegex = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
587
801
|
function stripAnsi(str) {
|
|
@@ -758,94 +972,21 @@ var ReportUpload = class {
|
|
|
758
972
|
}
|
|
759
973
|
};
|
|
760
974
|
|
|
761
|
-
// src/
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
let totalFree = 0;
|
|
772
|
-
for (const line of lines) {
|
|
773
|
-
if (/Pages (free|inactive|speculative):/.test(line)) {
|
|
774
|
-
const match = line.match(/\d+/);
|
|
775
|
-
if (match)
|
|
776
|
-
totalFree += parseInt(match[0], 10);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
return totalFree * pageSize;
|
|
780
|
-
}
|
|
781
|
-
function getSystemUtilization() {
|
|
782
|
-
let idleTicks = 0;
|
|
783
|
-
let totalTicks = 0;
|
|
784
|
-
for (const cpu of os2.cpus()) {
|
|
785
|
-
totalTicks += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq + cpu.times.idle;
|
|
786
|
-
idleTicks += cpu.times.idle;
|
|
975
|
+
// src/visitTests.ts
|
|
976
|
+
function visitTests(report, testVisitor) {
|
|
977
|
+
function visitSuite(suite, parents) {
|
|
978
|
+
parents.push(suite);
|
|
979
|
+
for (const test of suite.tests ?? [])
|
|
980
|
+
testVisitor(test, parents);
|
|
981
|
+
for (const childSuite of suite.suites ?? [])
|
|
982
|
+
visitSuite(childSuite, parents);
|
|
983
|
+
parents.pop();
|
|
787
984
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
freeBytes: os2.platform() === "darwin" ? getAvailableMemMacOS() : os2.freemem()
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
function toFKUtilization(sample, previous) {
|
|
796
|
-
const idleTicks = sample.idleTicks - previous.idleTicks;
|
|
797
|
-
const totalTicks = sample.totalTicks - previous.totalTicks;
|
|
798
|
-
const cpuUtilization = Math.floor((1 - idleTicks / totalTicks) * 1e4) / 100;
|
|
799
|
-
const memoryUtilization = Math.floor((1 - sample.freeBytes / os2.totalmem()) * 1e4) / 100;
|
|
800
|
-
return {
|
|
801
|
-
cpuUtilization,
|
|
802
|
-
memoryUtilization,
|
|
803
|
-
dts: sample.timestamp - previous.timestamp
|
|
804
|
-
};
|
|
985
|
+
for (const test of report.tests ?? [])
|
|
986
|
+
testVisitor(test, []);
|
|
987
|
+
for (const suite of report.suites ?? [])
|
|
988
|
+
visitSuite(suite, []);
|
|
805
989
|
}
|
|
806
|
-
var SystemUtilizationSampler = class {
|
|
807
|
-
/**
|
|
808
|
-
* The accumulated system utilization data.
|
|
809
|
-
*
|
|
810
|
-
* This object is populated as samples are collected and can be directly included in
|
|
811
|
-
* Flakiness reports. It contains:
|
|
812
|
-
* - `samples` - Array of utilization samples with CPU/memory percentages and durations
|
|
813
|
-
* - `startTimestamp` - Timestamp when sampling began
|
|
814
|
-
* - `totalMemoryBytes` - Total system memory in bytes
|
|
815
|
-
*/
|
|
816
|
-
result;
|
|
817
|
-
_lastSample = getSystemUtilization();
|
|
818
|
-
_timer;
|
|
819
|
-
/**
|
|
820
|
-
* Creates a new SystemUtilizationSampler and starts sampling immediately.
|
|
821
|
-
*
|
|
822
|
-
* The first sample is collected after 50ms, and subsequent samples are collected
|
|
823
|
-
* every 1000ms. Call `dispose()` to stop sampling and clean up resources.
|
|
824
|
-
*/
|
|
825
|
-
constructor() {
|
|
826
|
-
this.result = {
|
|
827
|
-
samples: [],
|
|
828
|
-
startTimestamp: this._lastSample.timestamp,
|
|
829
|
-
totalMemoryBytes: os2.totalmem()
|
|
830
|
-
};
|
|
831
|
-
this._timer = setTimeout(this._addSample.bind(this), 50);
|
|
832
|
-
}
|
|
833
|
-
_addSample() {
|
|
834
|
-
const sample = getSystemUtilization();
|
|
835
|
-
this.result.samples.push(toFKUtilization(sample, this._lastSample));
|
|
836
|
-
this._lastSample = sample;
|
|
837
|
-
this._timer = setTimeout(this._addSample.bind(this), 1e3);
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
|
-
* Stops sampling and cleans up resources.
|
|
841
|
-
*
|
|
842
|
-
* Call this method when you're done collecting utilization data to stop the sampling
|
|
843
|
-
* timer and prevent memory leaks. The `result` object remains accessible after disposal.
|
|
844
|
-
*/
|
|
845
|
-
dispose() {
|
|
846
|
-
clearTimeout(this._timer);
|
|
847
|
-
}
|
|
848
|
-
};
|
|
849
990
|
|
|
850
991
|
// src/showReport.ts
|
|
851
992
|
import chalk from "chalk";
|
|
@@ -1187,11 +1328,13 @@ async function writeReport(report, attachments, outputFolder) {
|
|
|
1187
1328
|
}
|
|
1188
1329
|
export {
|
|
1189
1330
|
CIUtils,
|
|
1331
|
+
CPUUtilization,
|
|
1190
1332
|
FlakinessProjectConfig,
|
|
1191
|
-
FlakinessReport,
|
|
1333
|
+
FlakinessReport2 as FlakinessReport,
|
|
1192
1334
|
GitWorktree,
|
|
1335
|
+
RAMUtilization,
|
|
1193
1336
|
reportUtils_exports as ReportUtils,
|
|
1194
|
-
|
|
1337
|
+
Schema2 as Schema,
|
|
1195
1338
|
showReport,
|
|
1196
1339
|
uploadReport,
|
|
1197
1340
|
writeReport
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flakiness/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.153.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"type": "module",
|
|
22
|
-
"description": "",
|
|
22
|
+
"description": "Comprehensive SDK for creating and managing Flakiness JSON Reports in Node.js",
|
|
23
23
|
"types": "./types/index.d.ts",
|
|
24
24
|
"scripts": {
|
|
25
25
|
"minor": "./version.mjs minor",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"author": "Degu Labs, Inc",
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@types/babel__code-frame": "^7.0.6",
|
|
33
32
|
"@types/debug": "^4.1.12",
|
|
34
33
|
"@types/node": "^25.0.3",
|
|
35
34
|
"esbuild": "^0.27.0",
|
|
@@ -38,11 +37,11 @@
|
|
|
38
37
|
"typescript": "^5.6.2"
|
|
39
38
|
},
|
|
40
39
|
"dependencies": {
|
|
41
|
-
"@
|
|
42
|
-
"@flakiness/flakiness-report": "^0.16.0",
|
|
40
|
+
"@flakiness/flakiness-report": "^0.20.0",
|
|
43
41
|
"chalk": "^5.6.2",
|
|
44
42
|
"debug": "^4.4.3",
|
|
45
43
|
"open": "^10.2.0",
|
|
46
|
-
"stable-hash": "^0.0.6"
|
|
44
|
+
"stable-hash": "^0.0.6",
|
|
45
|
+
"zod": "^4.3.5"
|
|
47
46
|
}
|
|
48
47
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type TelemetryPoint = {
|
|
2
|
+
timestamp: number;
|
|
3
|
+
value: number;
|
|
4
|
+
};
|
|
5
|
+
export declare function addTelemetryPoint(collection: TelemetryPoint[], newPoint: TelemetryPoint, precision: number): void;
|
|
6
|
+
export declare function toProtocolTelemetry(collection: TelemetryPoint[]): [number, number][];
|
|
7
|
+
//# sourceMappingURL=_telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_telemetry.d.ts","sourceRoot":"","sources":["../../src/_telemetry.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAY1G;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CASpF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FlakinessReport as FK } from '@flakiness/flakiness-report';
|
|
2
|
+
import { GitWorktree } from './gitWorktree.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extracts source code snippets referenced by locations in the report.
|
|
5
|
+
*
|
|
6
|
+
* Scans all locations in the report (tests, steps, errors, annotations) and collects
|
|
7
|
+
* the relevant source code chunks with surrounding context (±5 lines). The collected
|
|
8
|
+
* sources are stored directly in `report.sources`.
|
|
9
|
+
*
|
|
10
|
+
* @param worktree - Git worktree for resolving file paths.
|
|
11
|
+
* @param report - Report to scan and enrich with source snippets.
|
|
12
|
+
*/
|
|
13
|
+
export declare function collectSources(worktree: GitWorktree, report: FK.Report): void;
|
|
14
|
+
//# sourceMappingURL=collectSources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collectSources.d.ts","sourceRoot":"","sources":["../../src/collectSources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,6BAA6B,CAAC;AAEpE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAqD/C;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,QAiCtE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FlakinessReport as FK } from "@flakiness/flakiness-report";
|
|
2
|
+
/**
|
|
3
|
+
* Tracks CPU utilization over time by recording periodic samples.
|
|
4
|
+
*
|
|
5
|
+
* Call `sample()` at desired intervals (e.g., test start/end, every second) to record
|
|
6
|
+
* CPU usage. The class tracks both average and max utilization across all CPU cores.
|
|
7
|
+
* Use `enrich()` to add the collected telemetry to a report.
|
|
8
|
+
*/
|
|
9
|
+
export declare class CPUUtilization {
|
|
10
|
+
private _lastSample;
|
|
11
|
+
private _precision;
|
|
12
|
+
private _cpuAvg;
|
|
13
|
+
private _cpuMax;
|
|
14
|
+
/**
|
|
15
|
+
* @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 7.
|
|
16
|
+
*/
|
|
17
|
+
constructor(options?: {
|
|
18
|
+
precision?: number;
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Records the current CPU utilization since the last sample.
|
|
22
|
+
* The first call primes the baseline; subsequent calls record deltas.
|
|
23
|
+
*/
|
|
24
|
+
sample(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Adds collected CPU telemetry to the report.
|
|
27
|
+
*/
|
|
28
|
+
enrich(report: FK.Report): void;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=cpuUtilization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cpuUtilization.d.ts","sourceRoot":"","sources":["../../src/cpuUtilization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,6BAA6B,CAAC;AAkBpE;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAgB;IAEnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,OAAO,CAAwB;IAEvC;;OAEG;gBACS,OAAO,CAAC,EAAE;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;IAID;;;OAGG;IACH,MAAM;IAwBN;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;CAKzB"}
|
|
@@ -9,16 +9,13 @@ import { FlakinessReport } from '@flakiness/flakiness-report';
|
|
|
9
9
|
*
|
|
10
10
|
* @param {Object} options - Configuration object for the environment.
|
|
11
11
|
* @param {string} options.name - Human-readable name for the environment (e.g., 'CI', 'Local Dev', 'Staging').
|
|
12
|
-
* @param {Record<string, string>} [options.
|
|
12
|
+
* @param {Record<string, string>} [options.metadata] - Additional key-value pairs to include
|
|
13
13
|
* in the environment data. These are merged with `FK_ENV_*` environment variables.
|
|
14
|
-
* @param {any} [options.opaqueData] - Optional opaque data object that will be stored with the
|
|
15
|
-
* environment but not used for environment deduplication.
|
|
16
14
|
*
|
|
17
15
|
* @returns {FlakinessReport.Environment} Environment object containing:
|
|
18
16
|
* - `name` - The provided environment name
|
|
19
17
|
* - `systemData` - Automatically detected OS information (arch, name, version)
|
|
20
|
-
* - `
|
|
21
|
-
* - `opaqueData` - The provided opaque data, if any
|
|
18
|
+
* - `metadata` - Merged data from `FK_ENV_*` variables and `userSuppliedData` option
|
|
22
19
|
*
|
|
23
20
|
* @example
|
|
24
21
|
* ```typescript
|
|
@@ -28,13 +25,12 @@ import { FlakinessReport } from '@flakiness/flakiness-report';
|
|
|
28
25
|
* // With custom data
|
|
29
26
|
* const env = createEnvironment({
|
|
30
27
|
* name: 'Staging',
|
|
31
|
-
*
|
|
28
|
+
* metadata: { region: 'us-east-1', instance: 'large' }
|
|
32
29
|
* });
|
|
33
30
|
* ```
|
|
34
31
|
*/
|
|
35
32
|
export declare function createEnvironment(options: {
|
|
36
33
|
name: string;
|
|
37
|
-
|
|
38
|
-
opaqueData?: any;
|
|
34
|
+
metadata?: Record<string, string>;
|
|
39
35
|
}): FlakinessReport.Environment;
|
|
40
36
|
//# sourceMappingURL=createEnvironment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createEnvironment.d.ts","sourceRoot":"","sources":["../../src/createEnvironment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAwD9D
|
|
1
|
+
{"version":3,"file":"createEnvironment.d.ts","sourceRoot":"","sources":["../../src/createEnvironment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAwD9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,GAAG,eAAe,CAAC,WAAW,CAc9B"}
|
package/types/src/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export { FlakinessReport } from '@flakiness/flakiness-report';
|
|
1
|
+
export { FlakinessReport, Schema } from '@flakiness/flakiness-report';
|
|
2
2
|
export { CIUtils } from './ciUtils.js';
|
|
3
|
+
export { CPUUtilization } from './cpuUtilization.js';
|
|
3
4
|
export { GitWorktree } from './gitWorktree.js';
|
|
5
|
+
export { RAMUtilization } from './ramUtilization.js';
|
|
4
6
|
export * as ReportUtils from './reportUtils.js';
|
|
5
|
-
export { SystemUtilizationSampler } from './systemUtilizationSampler.js';
|
|
6
7
|
export { showReport } from './showReport.js';
|
|
7
8
|
export { uploadReport } from './uploadReport.js';
|
|
8
9
|
export { writeReport } from './writeReport.js';
|
package/types/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAGtE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FlakinessReport } from "@flakiness/flakiness-report";
|
|
2
2
|
/**
|
|
3
3
|
* Normalizes a Flakiness report by deduplicating environments, suites, and tests.
|
|
4
|
+
* It also drops the fields from JSON that are equal to their default values.
|
|
4
5
|
*
|
|
5
6
|
* This function processes a report to:
|
|
6
7
|
* - Deduplicate environments based on their content (using stable hashing)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalizeReport.d.ts","sourceRoot":"","sources":["../../src/normalizeReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAyB9D
|
|
1
|
+
{"version":3,"file":"normalizeReport.d.ts","sourceRoot":"","sources":["../../src/normalizeReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAyB9D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,GAAG,eAAe,CAAC,MAAM,CA+CtF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FlakinessReport as FK } from '@flakiness/flakiness-report';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks RAM utilization over time by recording periodic samples.
|
|
4
|
+
*
|
|
5
|
+
* Call `sample()` at desired intervals (e.g., test start/end, every second) to record
|
|
6
|
+
* memory usage as a percentage of total system RAM. Use `enrich()` to add the collected
|
|
7
|
+
* telemetry to a report.
|
|
8
|
+
*/
|
|
9
|
+
export declare class RAMUtilization {
|
|
10
|
+
private _precision;
|
|
11
|
+
private _totalBytes;
|
|
12
|
+
private _ram;
|
|
13
|
+
/**
|
|
14
|
+
* @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 1.
|
|
15
|
+
*/
|
|
16
|
+
constructor(options?: {
|
|
17
|
+
precision?: number;
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Records the current RAM utilization.
|
|
21
|
+
*/
|
|
22
|
+
sample(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Adds collected RAM telemetry to the report.
|
|
25
|
+
*/
|
|
26
|
+
enrich(report: FK.Report): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=ramUtilization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ramUtilization.d.ts","sourceRoot":"","sources":["../../src/ramUtilization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,6BAA6B,CAAC;AA4BpE;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAiB;IAEpC,OAAO,CAAC,IAAI,CAAwB;IAEpC;;OAEG;gBACS,OAAO,CAAC,EAAE;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;IAID;;OAEG;IACH,MAAM;IAQN;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;CAIzB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
export { collectSources } from './collectSources.js';
|
|
1
2
|
export { createEnvironment } from './createEnvironment.js';
|
|
2
|
-
export { createTestStepSnippetsInplace } from './createTestStepSnippets.js';
|
|
3
3
|
export { normalizeReport } from './normalizeReport.js';
|
|
4
|
+
export { validateReport } from './validateReport.js';
|
|
4
5
|
export { stripAnsi } from './stripAnsi.js';
|
|
5
6
|
export { createDataAttachment, createFileAttachment } from './uploadReport.js';
|
|
6
7
|
export type { Attachment, DataAttachment, FileAttachment } from './uploadReport.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reportUtils.d.ts","sourceRoot":"","sources":["../../src/reportUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"reportUtils.d.ts","sourceRoot":"","sources":["../../src/reportUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,UAAU,EAAE,cAAc,EAC1B,cAAc,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reportUtilsBrowser.d.ts","sourceRoot":"","sources":["../../src/reportUtilsBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"reportUtilsBrowser.d.ts","sourceRoot":"","sources":["../../src/reportUtilsBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FlakinessReport } from '@flakiness/flakiness-report';
|
|
2
|
+
/**
|
|
3
|
+
* Validates a report object against the Flakiness Report schema.
|
|
4
|
+
*
|
|
5
|
+
* @param report - The report object to validate
|
|
6
|
+
* @returns A formatted error string if validation fails, or `undefined` if the report is valid
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateReport(report: FlakinessReport.Report): string | undefined;
|
|
9
|
+
//# sourceMappingURL=validateReport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateReport.d.ts","sourceRoot":"","sources":["../../src/validateReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAU,MAAM,6BAA6B,CAAC;AAGtE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,GAAG,MAAM,GAAC,SAAS,CAe/E"}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { FlakinessReport } from '@flakiness/flakiness-report';
|
|
2
|
-
import { GitWorktree } from './gitWorktree.js';
|
|
3
|
-
/**
|
|
4
|
-
* Generates code snippets for test steps and attaches them to the report in-place.
|
|
5
|
-
*
|
|
6
|
-
* This function reads source files from the git worktree and creates highlighted code snippets
|
|
7
|
-
* for each test step that has a location. The snippets include 3 lines of context (1 before,
|
|
8
|
-
* the line itself, 1 after) with syntax highlighting and a visual indicator pointing to the
|
|
9
|
-
* exact column position.
|
|
10
|
-
*
|
|
11
|
-
* The snippets are attached directly to the `step.snippet` property of each test step in the
|
|
12
|
-
* report object. Steps without locations or with invalid file paths are silently skipped.
|
|
13
|
-
*
|
|
14
|
-
* @param {GitWorktree} worktree - Git worktree instance used to resolve file paths from
|
|
15
|
-
* git-relative paths to absolute paths for reading source files.
|
|
16
|
-
*
|
|
17
|
-
* @param {FlakinessReport.Report} report - Flakiness report to process. The report is modified
|
|
18
|
-
* in-place by adding `snippet` properties to test steps.
|
|
19
|
-
*
|
|
20
|
-
* @returns {void} This function modifies the report in-place and does not return a value.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```typescript
|
|
24
|
-
* const worktree = GitWorktree.create(process.cwd());
|
|
25
|
-
* createTestStepSnippetsInplace(worktree, report);
|
|
26
|
-
* // Report steps now have .snippet properties with highlighted code
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export declare function createTestStepSnippetsInplace(worktree: GitWorktree, report: FlakinessReport.Report): void;
|
|
30
|
-
//# sourceMappingURL=createTestStepSnippets.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createTestStepSnippets.d.ts","sourceRoot":"","sources":["../../src/createTestStepSnippets.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,QA4ClG"}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { FlakinessReport } from "@flakiness/flakiness-report";
|
|
2
|
-
/**
|
|
3
|
-
* Samples and records system CPU and memory utilization over time.
|
|
4
|
-
*
|
|
5
|
-
* This class continuously monitors system resource usage at regular intervals and stores
|
|
6
|
-
* the samples in a format suitable for inclusion in Flakiness reports. Sampling starts
|
|
7
|
-
* immediately upon construction and continues until `dispose()` is called.
|
|
8
|
-
*
|
|
9
|
-
* The first sample is collected after 50ms, and subsequent samples are collected every
|
|
10
|
-
* 1000ms (1 second). CPU utilization is calculated as a percentage based on CPU tick
|
|
11
|
-
* differences between samples. Memory utilization uses platform-specific methods for
|
|
12
|
-
* accurate measurement (especially on macOS).
|
|
13
|
-
*/
|
|
14
|
-
export declare class SystemUtilizationSampler {
|
|
15
|
-
/**
|
|
16
|
-
* The accumulated system utilization data.
|
|
17
|
-
*
|
|
18
|
-
* This object is populated as samples are collected and can be directly included in
|
|
19
|
-
* Flakiness reports. It contains:
|
|
20
|
-
* - `samples` - Array of utilization samples with CPU/memory percentages and durations
|
|
21
|
-
* - `startTimestamp` - Timestamp when sampling began
|
|
22
|
-
* - `totalMemoryBytes` - Total system memory in bytes
|
|
23
|
-
*/
|
|
24
|
-
readonly result: FlakinessReport.SystemUtilization;
|
|
25
|
-
private _lastSample;
|
|
26
|
-
private _timer;
|
|
27
|
-
/**
|
|
28
|
-
* Creates a new SystemUtilizationSampler and starts sampling immediately.
|
|
29
|
-
*
|
|
30
|
-
* The first sample is collected after 50ms, and subsequent samples are collected
|
|
31
|
-
* every 1000ms. Call `dispose()` to stop sampling and clean up resources.
|
|
32
|
-
*/
|
|
33
|
-
constructor();
|
|
34
|
-
private _addSample;
|
|
35
|
-
/**
|
|
36
|
-
* Stops sampling and cleans up resources.
|
|
37
|
-
*
|
|
38
|
-
* Call this method when you're done collecting utilization data to stop the sampling
|
|
39
|
-
* timer and prevent memory leaks. The `result` object remains accessible after disposal.
|
|
40
|
-
*/
|
|
41
|
-
dispose(): void;
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=systemUtilizationSampler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"systemUtilizationSampler.d.ts","sourceRoot":"","sources":["../../src/systemUtilizationSampler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA6D9D;;;;;;;;;;;GAWG;AACH,qBAAa,wBAAwB;IACnC;;;;;;;;OAQG;IACH,SAAgB,MAAM,EAAE,eAAe,CAAC,iBAAiB,CAAC;IAE1D,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,MAAM,CAAiB;IAE/B;;;;;OAKG;;IAWH,OAAO,CAAC,UAAU;IAOlB;;;;;OAKG;IACH,OAAO;CAGR"}
|