velocious 1.0.666 → 1.0.668
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 +40 -11
- package/build/background-jobs/adapter-client.js +22 -0
- package/build/background-jobs/adapter.js +15 -0
- package/build/background-jobs/client.js +147 -1
- package/build/background-jobs/job-semantics.js +30 -0
- package/build/background-jobs/job.js +26 -1
- package/build/background-jobs/local-adapter.js +36 -8
- package/build/background-jobs/local-store.js +410 -39
- package/build/background-jobs/main.js +69 -2
- package/build/background-jobs/platform-job.js +26 -1
- package/build/background-jobs/runtime.js +58 -0
- package/build/background-jobs/store.js +374 -6
- package/build/background-jobs/types.js +27 -2
- package/build/background-jobs/web/controller.js +1 -0
- package/build/configuration.js +30 -6
- package/build/database/drivers/mssql/column.js +2 -2
- package/build/database/drivers/mssql/table.js +19 -5
- package/build/database/pool/async-tracked-multi-connection.js +2 -1
- package/build/database/query/alter-table-base.js +8 -1
- package/build/database/record/attachments/store.js +2 -2
- package/build/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
- package/build/environment-handlers/node/cli/commands/test.js +17 -69
- package/build/frontend-model-controller.js +22 -2
- package/build/frontend-models/websocket-channel.js +18 -0
- package/build/http-server/client/websocket-session.js +41 -4
- package/build/http-server/websocket-channel.js +14 -0
- package/build/http-server/websocket-event-log-store.js +85 -12
- package/build/http-server/websocket-events-host.js +17 -7
- package/build/src/background-jobs/adapter-client.d.ts +17 -0
- package/build/src/background-jobs/adapter-client.d.ts.map +1 -1
- package/build/src/background-jobs/adapter-client.js +19 -1
- package/build/src/background-jobs/adapter.d.ts +15 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -1
- package/build/src/background-jobs/adapter.js +14 -1
- package/build/src/background-jobs/client.d.ts +17 -0
- package/build/src/background-jobs/client.d.ts.map +1 -1
- package/build/src/background-jobs/client.js +129 -1
- package/build/src/background-jobs/job-semantics.d.ts +18 -0
- package/build/src/background-jobs/job-semantics.d.ts.map +1 -1
- package/build/src/background-jobs/job-semantics.js +28 -1
- package/build/src/background-jobs/job.d.ts +15 -0
- package/build/src/background-jobs/job.d.ts.map +1 -1
- package/build/src/background-jobs/job.js +21 -2
- package/build/src/background-jobs/local-adapter.d.ts +23 -8
- package/build/src/background-jobs/local-adapter.d.ts.map +1 -1
- package/build/src/background-jobs/local-adapter.js +35 -9
- package/build/src/background-jobs/local-store.d.ts +120 -5
- package/build/src/background-jobs/local-store.d.ts.map +1 -1
- package/build/src/background-jobs/local-store.js +366 -40
- package/build/src/background-jobs/main.d.ts +24 -2
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +68 -3
- package/build/src/background-jobs/platform-job.d.ts +15 -0
- package/build/src/background-jobs/platform-job.d.ts.map +1 -1
- package/build/src/background-jobs/platform-job.js +19 -2
- package/build/src/background-jobs/runtime.d.ts +39 -0
- package/build/src/background-jobs/runtime.d.ts.map +1 -1
- package/build/src/background-jobs/runtime.js +49 -1
- package/build/src/background-jobs/store.d.ts +100 -1
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +334 -8
- package/build/src/background-jobs/types.d.ts +97 -4
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +28 -3
- package/build/src/background-jobs/web/controller.d.ts.map +1 -1
- package/build/src/background-jobs/web/controller.js +2 -1
- package/build/src/configuration.d.ts +18 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +28 -6
- package/build/src/database/drivers/mssql/column.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/column.js +2 -2
- package/build/src/database/drivers/mssql/table.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/table.js +19 -5
- package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
- package/build/src/database/pool/async-tracked-multi-connection.js +3 -2
- package/build/src/database/query/alter-table-base.d.ts.map +1 -1
- package/build/src/database/query/alter-table-base.js +9 -2
- package/build/src/database/record/attachments/store.js +3 -3
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +3 -47
- package/build/src/environment-handlers/node/cli/commands/test.d.ts +2 -28
- package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test.js +10 -61
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +18 -3
- package/build/src/frontend-models/websocket-channel.d.ts +9 -0
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +16 -1
- package/build/src/http-server/client/websocket-session.d.ts +25 -2
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +40 -5
- package/build/src/http-server/websocket-channel.d.ts +11 -0
- package/build/src/http-server/websocket-channel.d.ts.map +1 -1
- package/build/src/http-server/websocket-channel.js +14 -1
- package/build/src/http-server/websocket-event-log-store.d.ts +60 -41
- package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -1
- package/build/src/http-server/websocket-event-log-store.js +79 -12
- package/build/src/http-server/websocket-events-host.d.ts +10 -3
- package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
- package/build/src/http-server/websocket-events-host.js +17 -8
- package/build/src/testing/test-files-finder.d.ts +17 -117
- package/build/src/testing/test-files-finder.d.ts.map +1 -1
- package/build/src/testing/test-files-finder.js +29 -294
- package/build/src/testing/test-filter-parser.d.ts +32 -22
- package/build/src/testing/test-filter-parser.d.ts.map +1 -1
- package/build/src/testing/test-filter-parser.js +25 -217
- package/build/src/testing/test-profile-activity.d.ts +1 -6
- package/build/src/testing/test-profile-activity.d.ts.map +1 -1
- package/build/src/testing/test-profile-activity.js +2 -12
- package/build/src/testing/test-profile-output.d.ts +1 -40
- package/build/src/testing/test-profile-output.d.ts.map +1 -1
- package/build/src/testing/test-profile-output.js +2 -217
- package/build/src/testing/test-profiler.d.ts +15 -659
- package/build/src/testing/test-profiler.d.ts.map +1 -1
- package/build/src/testing/test-profiler.js +35 -833
- package/build/src/testing/test-runner.d.ts +10 -1
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +26 -4
- package/build/src/testing/test-suite-splitter.d.ts +1 -128
- package/build/src/testing/test-suite-splitter.d.ts.map +1 -1
- package/build/src/testing/test-suite-splitter.js +2 -259
- package/build/src/testing/timing-manifest.d.ts +5 -93
- package/build/src/testing/timing-manifest.d.ts.map +1 -1
- package/build/src/testing/timing-manifest.js +4 -283
- package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -1
- package/build/src/testing/velocious-attempt-executor.js +8 -2
- package/build/testing/test-files-finder.js +32 -343
- package/build/testing/test-filter-parser.js +29 -248
- package/build/testing/test-profile-activity.js +1 -12
- package/build/testing/test-profile-output.js +8 -253
- package/build/testing/test-profiler.js +38 -895
- package/build/testing/test-runner.js +33 -9
- package/build/testing/test-suite-splitter.js +1 -301
- package/build/testing/timing-manifest.js +10 -344
- package/build/testing/velocious-attempt-executor.js +7 -1
- package/package.json +3 -3
- package/scripts/test-browser.js +30 -6
- package/scripts/verify-docker-dev-environment.js +13 -1
- package/src/background-jobs/adapter-client.js +22 -0
- package/src/background-jobs/adapter.js +15 -0
- package/src/background-jobs/client.js +147 -1
- package/src/background-jobs/job-semantics.js +30 -0
- package/src/background-jobs/job.js +26 -1
- package/src/background-jobs/local-adapter.js +36 -8
- package/src/background-jobs/local-store.js +410 -39
- package/src/background-jobs/main.js +69 -2
- package/src/background-jobs/platform-job.js +26 -1
- package/src/background-jobs/runtime.js +58 -0
- package/src/background-jobs/store.js +374 -6
- package/src/background-jobs/types.js +27 -2
- package/src/background-jobs/web/controller.js +1 -0
- package/src/configuration.js +30 -6
- package/src/database/drivers/mssql/column.js +2 -2
- package/src/database/drivers/mssql/table.js +19 -5
- package/src/database/pool/async-tracked-multi-connection.js +2 -1
- package/src/database/query/alter-table-base.js +8 -1
- package/src/database/record/attachments/store.js +2 -2
- package/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
- package/src/environment-handlers/node/cli/commands/test.js +17 -69
- package/src/frontend-model-controller.js +22 -2
- package/src/frontend-models/websocket-channel.js +18 -0
- package/src/http-server/client/websocket-session.js +41 -4
- package/src/http-server/websocket-channel.js +14 -0
- package/src/http-server/websocket-event-log-store.js +85 -12
- package/src/http-server/websocket-events-host.js +17 -7
- package/src/testing/test-files-finder.js +32 -343
- package/src/testing/test-filter-parser.js +29 -248
- package/src/testing/test-profile-activity.js +1 -12
- package/src/testing/test-profile-output.js +8 -253
- package/src/testing/test-profiler.js +38 -895
- package/src/testing/test-runner.js +33 -9
- package/src/testing/test-suite-splitter.js +1 -301
- package/src/testing/timing-manifest.js +10 -344
- package/src/testing/velocious-attempt-executor.js +7 -1
|
@@ -1,129 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
/**
|
|
3
|
-
* - Absolute file path.
|
|
4
|
-
*/
|
|
5
|
-
filePath: string;
|
|
6
|
-
/**
|
|
7
|
-
* - Computed weight for load balancing.
|
|
8
|
-
*/
|
|
9
|
-
weight: number;
|
|
10
|
-
};
|
|
11
|
-
export type GroupBucket = {
|
|
12
|
-
/**
|
|
13
|
-
* - Accumulated weight.
|
|
14
|
-
*/
|
|
15
|
-
totalWeight: number;
|
|
16
|
-
/**
|
|
17
|
-
* - Files assigned to this group.
|
|
18
|
-
*/
|
|
19
|
-
files: string[];
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Splits a list of test files into balanced groups using a greedy load-balancing algorithm.
|
|
23
|
-
* Modeled after test_suite_splitter for RSpec.
|
|
24
|
-
*/
|
|
25
|
-
export default class TestSuiteSplitter {
|
|
26
|
-
_groups: number;
|
|
27
|
-
_groupNumber: number;
|
|
28
|
-
_testFiles: string[];
|
|
29
|
-
_baseDirectory: string;
|
|
30
|
-
_timingManifest: Record<string, number>;
|
|
31
|
-
/**
|
|
32
|
-
* Runs constructor.
|
|
33
|
-
* @param {object} args - Options.
|
|
34
|
-
* @param {number} args.groups - Total number of groups.
|
|
35
|
-
* @param {number} args.groupNumber - Which group to return (1-indexed).
|
|
36
|
-
* @param {string[]} args.testFiles - All discovered test file paths.
|
|
37
|
-
* @param {string} [args.baseDirectory] - Base directory for relative path computation.
|
|
38
|
-
* @param {ReturnType<typeof JSON.parse>} [args.timingManifest] - Relative test paths mapped to durations.
|
|
39
|
-
*/
|
|
40
|
-
constructor({ groups, groupNumber, testFiles, baseDirectory, timingManifest, ...restArgs }: {
|
|
41
|
-
groups: number;
|
|
42
|
-
groupNumber: number;
|
|
43
|
-
testFiles: string[];
|
|
44
|
-
baseDirectory?: string;
|
|
45
|
-
timingManifest?: ReturnType<typeof JSON.parse>;
|
|
46
|
-
});
|
|
47
|
-
/**
|
|
48
|
-
* Returns the test files assigned to this group.
|
|
49
|
-
* @returns {string[]} - File paths for the requested group.
|
|
50
|
-
*/
|
|
51
|
-
getGroupFiles(): string[];
|
|
52
|
-
/**
|
|
53
|
-
* Computes weight for each test file based on directory type and file suffix.
|
|
54
|
-
* @returns {SplitterFileEntry[]} - Weighted file entries.
|
|
55
|
-
*/
|
|
56
|
-
computeWeightedFiles(): SplitterFileEntry[];
|
|
57
|
-
/**
|
|
58
|
-
* Computes the weight for a single file.
|
|
59
|
-
* @param {string} filePath - Absolute file path.
|
|
60
|
-
* @returns {number} - Weight value.
|
|
61
|
-
*/
|
|
62
|
-
computeWeight(filePath: string): number;
|
|
63
|
-
/**
|
|
64
|
-
* Keeps only usable positive finite duration entries keyed by normalized relative path.
|
|
65
|
-
* @param {ReturnType<typeof JSON.parse>} timingManifest - Parsed timing manifest.
|
|
66
|
-
* @returns {Record<string, number>} - Valid normalized duration weights.
|
|
67
|
-
*/
|
|
68
|
-
normalizeTimingManifest(timingManifest: ReturnType<typeof JSON.parse>): Record<string, number>;
|
|
69
|
-
/**
|
|
70
|
-
* Normalizes a relative test path to the manifest's portable slash format.
|
|
71
|
-
* @param {string} filePath - Relative test path.
|
|
72
|
-
* @returns {string} - Normalized relative test path.
|
|
73
|
-
*/
|
|
74
|
-
normalizeRelativePath(filePath: string): string;
|
|
75
|
-
/**
|
|
76
|
-
* Returns the permissive relative path used only for heuristic classification.
|
|
77
|
-
* @param {string} filePath - Absolute test file path.
|
|
78
|
-
* @returns {string} - Portable relative path which may escape the profiling base.
|
|
79
|
-
*/
|
|
80
|
-
heuristicRelativePath(filePath: string): string;
|
|
81
|
-
/**
|
|
82
|
-
* Returns a canonical manifest key when the file is representable under the profiling base.
|
|
83
|
-
* @param {string} filePath - Absolute test file path.
|
|
84
|
-
* @returns {string | undefined} - Canonical relative path, or undefined for an external file.
|
|
85
|
-
*/
|
|
86
|
-
manifestRelativePath(filePath: string): string | undefined;
|
|
87
|
-
/**
|
|
88
|
-
* Returns the first manifest entry matching a discovered test file.
|
|
89
|
-
* @param {string} filePath - Absolute test file path.
|
|
90
|
-
* @returns {number | undefined} - Recorded duration, including zero.
|
|
91
|
-
*/
|
|
92
|
-
timingManifestDuration(filePath: string): number | undefined;
|
|
93
|
-
/**
|
|
94
|
-
* Returns compatible manifest keys for one discovered file.
|
|
95
|
-
* @param {string} filePath - Absolute test file path.
|
|
96
|
-
* @returns {string[]} - Canonical keys in matching priority order.
|
|
97
|
-
*/
|
|
98
|
-
timingManifestPaths(filePath: string): string[];
|
|
99
|
-
/**
|
|
100
|
-
* Summarizes timing-history coverage for the complete discovered suite.
|
|
101
|
-
* @returns {{heuristicFiles: number, measuredFiles: number, staleEntries: number}} - Compact coverage counts.
|
|
102
|
-
*/
|
|
103
|
-
getTimingManifestCoverage(): {
|
|
104
|
-
heuristicFiles: number;
|
|
105
|
-
measuredFiles: number;
|
|
106
|
-
staleEntries: number;
|
|
107
|
-
};
|
|
108
|
-
/**
|
|
109
|
-
* Sorts files by weight descending, then by path for determinism.
|
|
110
|
-
* @param {SplitterFileEntry[]} files - Weighted files.
|
|
111
|
-
* @returns {SplitterFileEntry[]} - Sorted files.
|
|
112
|
-
*/
|
|
113
|
-
sortByWeightDescending(files: SplitterFileEntry[]): SplitterFileEntry[];
|
|
114
|
-
/**
|
|
115
|
-
* Distributes files greedily into N balanced groups.
|
|
116
|
-
* Each file is assigned to the group with the least accumulated weight.
|
|
117
|
-
* @param {SplitterFileEntry[]} sortedFiles - Files sorted by weight descending.
|
|
118
|
-
* @returns {GroupBucket[]} - Array of group buckets.
|
|
119
|
-
*/
|
|
120
|
-
distributeGreedily(sortedFiles: SplitterFileEntry[]): GroupBucket[];
|
|
121
|
-
/**
|
|
122
|
-
* Finds the bucket with the least accumulated weight.
|
|
123
|
-
* Ties are broken by bucket index (earlier bucket wins) for determinism.
|
|
124
|
-
* @param {GroupBucket[]} buckets - Group buckets.
|
|
125
|
-
* @returns {GroupBucket} - The lightest bucket.
|
|
126
|
-
*/
|
|
127
|
-
findLightestBucket(buckets: GroupBucket[]): GroupBucket;
|
|
128
|
-
}
|
|
1
|
+
export { TestSuiteSplitter as default } from "@velocious/testing/node";
|
|
129
2
|
//# sourceMappingURL=test-suite-splitter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-suite-splitter.d.ts","sourceRoot":"","sources":["../../../src/testing/test-suite-splitter.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"test-suite-splitter.d.ts","sourceRoot":"","sources":["../../../src/testing/test-suite-splitter.js"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,IAAI,OAAO,EAAE,MAAM,yBAAyB,CAAA"}
|
|
@@ -1,260 +1,3 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { canonicalTimingManifestPath, compareTimingManifestPaths } from "./timing-manifest.js";
|
|
5
|
-
/**
|
|
6
|
-
* SplitterFileEntry type.
|
|
7
|
-
* @typedef {object} SplitterFileEntry
|
|
8
|
-
* @property {string} filePath - Absolute file path.
|
|
9
|
-
* @property {number} weight - Computed weight for load balancing.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* GroupBucket type.
|
|
13
|
-
* @typedef {object} GroupBucket
|
|
14
|
-
* @property {number} totalWeight - Accumulated weight.
|
|
15
|
-
* @property {string[]} files - Files assigned to this group.
|
|
16
|
-
*/
|
|
17
|
-
/** Default weight for a regular test file. */
|
|
18
|
-
const DEFAULT_WEIGHT = 1;
|
|
19
|
-
/**
|
|
20
|
-
* Weight multipliers by spec directory name.
|
|
21
|
-
* Heavier test types get higher weights so greedy distribution balances wall-clock time.
|
|
22
|
-
* @type {Record<string, number>}
|
|
23
|
-
*/
|
|
24
|
-
const DIRECTORY_WEIGHTS = {
|
|
25
|
-
system: 20,
|
|
26
|
-
"frontend-models": 10,
|
|
27
|
-
controller: 3
|
|
28
|
-
};
|
|
29
|
-
/** Extra multiplier applied to browser spec files on top of directory weight. */
|
|
30
|
-
const BROWSER_SPEC_MULTIPLIER = 2;
|
|
31
|
-
/**
|
|
32
|
-
* Splits a list of test files into balanced groups using a greedy load-balancing algorithm.
|
|
33
|
-
* Modeled after test_suite_splitter for RSpec.
|
|
34
|
-
*/
|
|
35
|
-
export default class TestSuiteSplitter {
|
|
36
|
-
/**
|
|
37
|
-
* Runs constructor.
|
|
38
|
-
* @param {object} args - Options.
|
|
39
|
-
* @param {number} args.groups - Total number of groups.
|
|
40
|
-
* @param {number} args.groupNumber - Which group to return (1-indexed).
|
|
41
|
-
* @param {string[]} args.testFiles - All discovered test file paths.
|
|
42
|
-
* @param {string} [args.baseDirectory] - Base directory for relative path computation.
|
|
43
|
-
* @param {ReturnType<typeof JSON.parse>} [args.timingManifest] - Relative test paths mapped to durations.
|
|
44
|
-
*/
|
|
45
|
-
constructor({ groups, groupNumber, testFiles, baseDirectory, timingManifest, ...restArgs }) {
|
|
46
|
-
restArgsError(restArgs);
|
|
47
|
-
if (!Number.isInteger(groups) || groups < 1) {
|
|
48
|
-
throw new Error(`--groups must be a positive integer, got: ${groups}`);
|
|
49
|
-
}
|
|
50
|
-
if (!Number.isInteger(groupNumber) || groupNumber < 1 || groupNumber > groups) {
|
|
51
|
-
throw new Error(`--group-number must be between 1 and ${groups}, got: ${groupNumber}`);
|
|
52
|
-
}
|
|
53
|
-
this._groups = groups;
|
|
54
|
-
this._groupNumber = groupNumber;
|
|
55
|
-
this._testFiles = testFiles;
|
|
56
|
-
this._baseDirectory = baseDirectory || process.cwd();
|
|
57
|
-
this._timingManifest = this.normalizeTimingManifest(timingManifest);
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Returns the test files assigned to this group.
|
|
61
|
-
* @returns {string[]} - File paths for the requested group.
|
|
62
|
-
*/
|
|
63
|
-
getGroupFiles() {
|
|
64
|
-
const weighted = this.computeWeightedFiles();
|
|
65
|
-
const sorted = this.sortByWeightDescending(weighted);
|
|
66
|
-
const buckets = this.distributeGreedily(sorted);
|
|
67
|
-
return buckets[this._groupNumber - 1].files;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Computes weight for each test file based on directory type and file suffix.
|
|
71
|
-
* @returns {SplitterFileEntry[]} - Weighted file entries.
|
|
72
|
-
*/
|
|
73
|
-
computeWeightedFiles() {
|
|
74
|
-
return this._testFiles.map((filePath) => ({
|
|
75
|
-
filePath,
|
|
76
|
-
weight: this.computeWeight(filePath)
|
|
77
|
-
}));
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Computes the weight for a single file.
|
|
81
|
-
* @param {string} filePath - Absolute file path.
|
|
82
|
-
* @returns {number} - Weight value.
|
|
83
|
-
*/
|
|
84
|
-
computeWeight(filePath) {
|
|
85
|
-
const duration = this.timingManifestDuration(filePath);
|
|
86
|
-
if (duration !== undefined && duration > 0) {
|
|
87
|
-
return duration;
|
|
88
|
-
}
|
|
89
|
-
const relativePath = this.heuristicRelativePath(filePath);
|
|
90
|
-
let weight = DEFAULT_WEIGHT;
|
|
91
|
-
// Extract the type directory from the relative path.
|
|
92
|
-
// Matches both "spec/system/..." (base is project root) and "system/..." (base is spec/ itself).
|
|
93
|
-
const specDirMatch = relativePath.match(/^(?:(?:spec|__tests__|tests)\/)?([^/]+)\//);
|
|
94
|
-
if (specDirMatch) {
|
|
95
|
-
const dirName = specDirMatch[1];
|
|
96
|
-
if (DIRECTORY_WEIGHTS[dirName] !== undefined) {
|
|
97
|
-
weight = DIRECTORY_WEIGHTS[dirName];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Browser spec files are heavier
|
|
101
|
-
if (filePath.endsWith(".browser-spec.js") || filePath.endsWith(".browser-spec.mjs")) {
|
|
102
|
-
weight *= BROWSER_SPEC_MULTIPLIER;
|
|
103
|
-
}
|
|
104
|
-
return weight;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Keeps only usable positive finite duration entries keyed by normalized relative path.
|
|
108
|
-
* @param {ReturnType<typeof JSON.parse>} timingManifest - Parsed timing manifest.
|
|
109
|
-
* @returns {Record<string, number>} - Valid normalized duration weights.
|
|
110
|
-
*/
|
|
111
|
-
normalizeTimingManifest(timingManifest) {
|
|
112
|
-
/** @type {Record<string, number>} */
|
|
113
|
-
const normalized = {};
|
|
114
|
-
if (!timingManifest || typeof timingManifest !== "object" || Array.isArray(timingManifest)) {
|
|
115
|
-
return normalized;
|
|
116
|
-
}
|
|
117
|
-
for (const [filePath, duration] of Object.entries(timingManifest)) {
|
|
118
|
-
if (typeof duration !== "number" || !Number.isFinite(duration) || duration < 0)
|
|
119
|
-
continue;
|
|
120
|
-
try {
|
|
121
|
-
normalized[this.normalizeRelativePath(filePath)] = duration;
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
if (!(error instanceof Error))
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return normalized;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Normalizes a relative test path to the manifest's portable slash format.
|
|
132
|
-
* @param {string} filePath - Relative test path.
|
|
133
|
-
* @returns {string} - Normalized relative test path.
|
|
134
|
-
*/
|
|
135
|
-
normalizeRelativePath(filePath) {
|
|
136
|
-
return canonicalTimingManifestPath(filePath);
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Returns the permissive relative path used only for heuristic classification.
|
|
140
|
-
* @param {string} filePath - Absolute test file path.
|
|
141
|
-
* @returns {string} - Portable relative path which may escape the profiling base.
|
|
142
|
-
*/
|
|
143
|
-
heuristicRelativePath(filePath) {
|
|
144
|
-
return path.relative(this._baseDirectory, filePath)
|
|
145
|
-
.replaceAll("\\", "/")
|
|
146
|
-
.replace(/^\.\//, "");
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Returns a canonical manifest key when the file is representable under the profiling base.
|
|
150
|
-
* @param {string} filePath - Absolute test file path.
|
|
151
|
-
* @returns {string | undefined} - Canonical relative path, or undefined for an external file.
|
|
152
|
-
*/
|
|
153
|
-
manifestRelativePath(filePath) {
|
|
154
|
-
const relativePath = path.relative(this._baseDirectory, filePath);
|
|
155
|
-
if (!relativePath || path.isAbsolute(relativePath))
|
|
156
|
-
return;
|
|
157
|
-
if (relativePath === ".." || relativePath.startsWith(`..${path.sep}`))
|
|
158
|
-
return;
|
|
159
|
-
return this.normalizeRelativePath(relativePath);
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Returns the first manifest entry matching a discovered test file.
|
|
163
|
-
* @param {string} filePath - Absolute test file path.
|
|
164
|
-
* @returns {number | undefined} - Recorded duration, including zero.
|
|
165
|
-
*/
|
|
166
|
-
timingManifestDuration(filePath) {
|
|
167
|
-
for (const manifestPath of this.timingManifestPaths(filePath)) {
|
|
168
|
-
if (Object.hasOwn(this._timingManifest, manifestPath))
|
|
169
|
-
return this._timingManifest[manifestPath];
|
|
170
|
-
}
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Returns compatible manifest keys for one discovered file.
|
|
175
|
-
* @param {string} filePath - Absolute test file path.
|
|
176
|
-
* @returns {string[]} - Canonical keys in matching priority order.
|
|
177
|
-
*/
|
|
178
|
-
timingManifestPaths(filePath) {
|
|
179
|
-
const relativePath = this.manifestRelativePath(filePath);
|
|
180
|
-
if (!relativePath)
|
|
181
|
-
return [];
|
|
182
|
-
const projectRelativePath = this.normalizeRelativePath(path.join(path.basename(this._baseDirectory), relativePath));
|
|
183
|
-
return relativePath === projectRelativePath ? [relativePath] : [relativePath, projectRelativePath];
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Summarizes timing-history coverage for the complete discovered suite.
|
|
187
|
-
* @returns {{heuristicFiles: number, measuredFiles: number, staleEntries: number}} - Compact coverage counts.
|
|
188
|
-
*/
|
|
189
|
-
getTimingManifestCoverage() {
|
|
190
|
-
const matchedManifestPaths = new Set();
|
|
191
|
-
let measuredFiles = 0;
|
|
192
|
-
for (const filePath of this._testFiles) {
|
|
193
|
-
let matchedDuration;
|
|
194
|
-
for (const manifestPath of this.timingManifestPaths(filePath)) {
|
|
195
|
-
if (!Object.hasOwn(this._timingManifest, manifestPath))
|
|
196
|
-
continue;
|
|
197
|
-
matchedManifestPaths.add(manifestPath);
|
|
198
|
-
matchedDuration = this._timingManifest[manifestPath];
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
if (matchedDuration !== undefined && matchedDuration > 0)
|
|
202
|
-
measuredFiles++;
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
heuristicFiles: this._testFiles.length - measuredFiles,
|
|
206
|
-
measuredFiles,
|
|
207
|
-
staleEntries: Object.keys(this._timingManifest).length - matchedManifestPaths.size
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Sorts files by weight descending, then by path for determinism.
|
|
212
|
-
* @param {SplitterFileEntry[]} files - Weighted files.
|
|
213
|
-
* @returns {SplitterFileEntry[]} - Sorted files.
|
|
214
|
-
*/
|
|
215
|
-
sortByWeightDescending(files) {
|
|
216
|
-
return [...files].sort((a, b) => {
|
|
217
|
-
if (b.weight !== a.weight) {
|
|
218
|
-
return b.weight - a.weight;
|
|
219
|
-
}
|
|
220
|
-
return compareTimingManifestPaths(a.filePath, b.filePath);
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Distributes files greedily into N balanced groups.
|
|
225
|
-
* Each file is assigned to the group with the least accumulated weight.
|
|
226
|
-
* @param {SplitterFileEntry[]} sortedFiles - Files sorted by weight descending.
|
|
227
|
-
* @returns {GroupBucket[]} - Array of group buckets.
|
|
228
|
-
*/
|
|
229
|
-
distributeGreedily(sortedFiles) {
|
|
230
|
-
/**
|
|
231
|
-
* Buckets.
|
|
232
|
-
* @type {GroupBucket[]} */
|
|
233
|
-
const buckets = [];
|
|
234
|
-
for (let i = 0; i < this._groups; i++) {
|
|
235
|
-
buckets.push({ totalWeight: 0, files: [] });
|
|
236
|
-
}
|
|
237
|
-
for (const entry of sortedFiles) {
|
|
238
|
-
const lightest = this.findLightestBucket(buckets);
|
|
239
|
-
lightest.files.push(entry.filePath);
|
|
240
|
-
lightest.totalWeight += entry.weight;
|
|
241
|
-
}
|
|
242
|
-
return buckets;
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Finds the bucket with the least accumulated weight.
|
|
246
|
-
* Ties are broken by bucket index (earlier bucket wins) for determinism.
|
|
247
|
-
* @param {GroupBucket[]} buckets - Group buckets.
|
|
248
|
-
* @returns {GroupBucket} - The lightest bucket.
|
|
249
|
-
*/
|
|
250
|
-
findLightestBucket(buckets) {
|
|
251
|
-
let lightest = buckets[0];
|
|
252
|
-
for (let i = 1; i < buckets.length; i++) {
|
|
253
|
-
if (buckets[i].totalWeight < lightest.totalWeight) {
|
|
254
|
-
lightest = buckets[i];
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return lightest;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC1zdWl0ZS1zcGxpdHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy90ZXN0aW5nL3Rlc3Qtc3VpdGUtc3BsaXR0ZXIuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQUN2QixPQUFPLGFBQWEsTUFBTSw2QkFBNkIsQ0FBQTtBQUN2RCxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQTtBQUU5Rjs7Ozs7R0FLRztBQUVIOzs7OztHQUtHO0FBRUgsOENBQThDO0FBQzlDLE1BQU0sY0FBYyxHQUFHLENBQUMsQ0FBQTtBQUV4Qjs7OztHQUlHO0FBQ0gsTUFBTSxpQkFBaUIsR0FBRztJQUN4QixNQUFNLEVBQUUsRUFBRTtJQUNWLGlCQUFpQixFQUFFLEVBQUU7SUFDckIsVUFBVSxFQUFFLENBQUM7Q0FDZCxDQUFBO0FBRUQsaUZBQWlGO0FBQ2pGLE1BQU0sdUJBQXVCLEdBQUcsQ0FBQyxDQUFBO0FBRWpDOzs7R0FHRztBQUNILE1BQU0sQ0FBQyxPQUFPLE9BQU8saUJBQWlCO0lBQ3BDOzs7Ozs7OztPQVFHO0lBQ0gsWUFBWSxFQUFDLE1BQU0sRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLGFBQWEsRUFBRSxjQUFjLEVBQUUsR0FBRyxRQUFRLEVBQUM7UUFDdEYsYUFBYSxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBRXZCLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxJQUFJLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUM1QyxNQUFNLElBQUksS0FBSyxDQUFDLDZDQUE2QyxNQUFNLEVBQUUsQ0FBQyxDQUFBO1FBQ3hFLENBQUM7UUFFRCxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxXQUFXLENBQUMsSUFBSSxXQUFXLEdBQUcsQ0FBQyxJQUFJLFdBQVcsR0FBRyxNQUFNLEVB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RUFBRSxFQUFFLENBQUM7WUFDdEMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFDLFdBQVcsRUFBRSxDQUFDLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBQyxDQUFDLENBQUE7UUFDM0MsQ0FBQztRQUVELEtBQUssTUFBTSxLQUFLLElBQUksV0FBVyxFQUFFLENBQUM7WUFDaEMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxDQUFBO1lBRWpELFFBQVEsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQTtZQUNuQyxRQUFRLENBQUMsV0FBVyxJQUFJLEtBQUssQ0FBQyxNQUFNLENBQUE7UUFDdEMsQ0FBQztRQUVELE9BQU8sT0FBTyxDQUFBO0lBQ2hCLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNILGtCQUFrQixDQUFDLE9BQU87UUFDeEIsSUFBSSxRQUFRLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBRXpCLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxPQUFPLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7WUFDeEMsSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxHQUFHLFFBQVEsQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDbEQsUUFBUSxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQTtZQUN2QixDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sUUFBUSxDQUFBO0lBQ2pCLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5pbXBvcnQgcGF0aCBmcm9tIFwicGF0aFwiXG5pbXBvcnQgcmVzdEFyZ3NFcnJvciBmcm9tIFwiLi4vdXRpbHMvcmVzdC1hcmdzLWVycm9yLmpzXCJcbmltcG9ydCB7IGNhbm9uaWNhbFRpbWluZ01hbmlmZXN0UGF0aCwgY29tcGFyZVRpbWluZ01hbmlmZXN0UGF0aHMgfSBmcm9tIFwiLi90aW1pbmctbWFuaWZlc3QuanNcIlxuXG4vKipcbiAqIFNwbGl0dGVyRmlsZUVudHJ5IHR5cGUuXG4gKiBAdHlwZWRlZiB7b2JqZWN0fSBTcGxpdHRlckZpbGVFbnRyeVxuICogQHByb3BlcnR5IHtzdHJpbmd9IGZpbGVQYXRoIC0gQWJzb2x1dGUgZmlsZSBwYXRoLlxuICogQHByb3BlcnR5IHtudW1iZXJ9IHdlaWdodCAtIENvbXB1dGVkIHdlaWdodCBmb3IgbG9hZCBiYWxhbmNpbmcuXG4gKi9cblxuLyoqXG4gKiBHcm91cEJ1Y2tldCB0eXBlLlxuICogQHR5cGVkZWYge29iamVjdH0gR3JvdXBCdWNrZXRcbiAqIEBwcm9wZXJ0eSB7bnVtYmVyfSB0b3RhbFdlaWdodCAtIEFjY3VtdWxhdGVkIHdlaWdodC5cbiAqIEBwcm9wZXJ0eSB7c3RyaW5nW119IGZpbGVzIC0gRmlsZXMgYXNzaWduZWQgdG8gdGhpcyBncm91cC5cbiAqL1xuXG4vKiogRGVmYXVsdCB3ZWlnaHQgZm9yIGEgcmVndWxhciB0ZXN0IGZpbGUuICovXG5jb25zdCBERUZBVUxUX1dFSUdIVCA9IDFcblxuLyoqXG4gKiBXZWlnaHQgbXVsdGlwbGllcnMgYnkgc3BlYyBkaXJlY3RvcnkgbmFtZS5cbiAqIEhlYXZpZXIgdGVzdCB0eXBlcyBnZXQgaGlnaGVyIHdlaWdodHMgc28gZ3JlZWR5IGRpc3RyaWJ1dGlvbiBiYWxhbmNlcyB3YWxsLWNsb2NrIHRpbWUuXG4gKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgbnVtYmVyPn1cbiAqL1xuY29uc3QgRElSRUNUT1JZX1dFSUdIVFMgPSB7XG4gIHN5c3RlbTogMjAsXG4gIFwiZnJvbnRlbmQtbW9kZWxzXCI6IDEwLFxuICBjb250cm9sbGVyOiAzXG59XG5cbi8qKiBFeHRyYSBtdWx0aXBsaWVyIGFwcGxpZWQgdG8gYnJvd3NlciBzcGVjIGZpbGVzIG9uIHRvcCBvZiBkaXJlY3Rvcnkgd2VpZ2h0LiAqL1xuY29uc3QgQlJPV1NFUl9TUEVDX01VTFRJUExJRVIgPSAyXG5cbi8qKlxuICogU3BsaXRzIGEgbGlzdCBvZiB0ZXN0IGZpbGVzIGludG8gYmFsYW5jZWQgZ3JvdXBzIHVzaW5nIGEgZ3JlZWR5IGxvYWQtYmFsYW5jaW5nIGFsZ29yaXRobS5cbiAqIE1vZGVsZWQgYWZ0ZXIgdGVzdF9zdWl0ZV9zcGxpdHRlciBmb3IgUlNwZWMuXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFRlc3RTdWl0ZVNwbGl0dGVyIHtcbiAgLyoqXG4gICAqIFJ1bnMgY29uc3RydWN0b3IuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3B0aW9ucy5cbiAgICogQHBhcmFtIHtudW1iZXJ9IGFyZ3MuZ3JvdXBzIC0gVG90YWwgbnVtYmVyIG9mIGdyb3Vwcy5cbiAgICogQHBhcmFtIHtudW1iZXJ9IGFyZ3MuZ3JvdXBOdW1iZXIgLSBXaGljaCBncm91cCB0byByZXR1cm4gKDEtaW5kZXhlZCkuXG4gICAqIEBwYXJhbSB7c3RyaW5nW119IGFyZ3MudGVzdEZpbGVzIC0gQWxsIGRpc2NvdmVyZWQgdGVzdCBmaWxlIHBhdGhzLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gW2FyZ3MuYmFzZURpcmVjdG9yeV0gLSBCYXNlIGRpcmVjdG9yeSBmb3IgcmVsYXRpdmUgcGF0aCBjb21wdXRhdGlvbi5cbiAgICogQHBhcmFtIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gW2FyZ3MudGltaW5nTWFuaWZlc3RdIC0gUmVsYXRpdmUgdGVzdCBwYXRocyBtYXBwZWQgdG8gZHVyYXRpb25zLlxuICAgKi9cbiAgY29uc3RydWN0b3Ioe2dyb3VwcywgZ3JvdXBOdW1iZXIsIHRlc3RGaWxlcywgYmFzZURpcmVjdG9yeSwgdGltaW5nTWFuaWZlc3QsIC4uLnJlc3RBcmdzfSkge1xuICAgIHJlc3RBcmdzRXJyb3IocmVzdEFyZ3MpXG5cbiAgICBpZiAoIU51bWJlci5pc0ludGVnZXIoZ3JvdXBzKSB8fCBncm91cHMgPCAxKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYC0tZ3JvdXBzIG11c3QgYmUgYSBwb3NpdGl2ZSBpbnRlZ2VyLCBnb3Q6ICR7Z3JvdXBzfWApXG4gICAgfVxuXG4gICAgaWYgKCFOdW1iZXIuaXNJbnRlZ2VyKGdyb3VwTnVtYmVyKSB8fCBncm91cE51bWJlciA8IDEgfHwgZ3JvdXBOdW1iZXIgPiBncm91cHMpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgLS1ncm91cC1udW1iZXIgbXVzdCBiZSBiZXR3ZWVuIDEgYW5kICR7Z3JvdXBzfSwgZ290OiAke2dyb3VwTnVtYmVyfWApXG4gICAgfVxuXG4gICAgdGhpcy5fZ3JvdXBzID0gZ3JvdXBzXG4gICAgdGhpcy5fZ3JvdXBOdW1iZXIgPSBncm91cE51bWJlclxuICAgIHRoaXMuX3Rlc3RGaWxlcyA9IHRlc3RGaWxlc1xuICAgIHRoaXMuX2Jhc2VEaXJlY3RvcnkgPSBiYXNlRGlyZWN0b3J5IHx8IHByb2Nlc3MuY3dkKClcbiAgICB0aGlzLl90aW1pbmdNYW5pZmVzdCA9IHRoaXMubm9ybWFsaXplVGltaW5nTWFuaWZlc3QodGltaW5nTWFuaWZlc3QpXG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgdGVzdCBmaWxlcyBhc3NpZ25lZCB0byB0aGlzIGdyb3VwLlxuICAgKiBAcmV0dXJucyB7c3RyaW5nW119IC0gRmlsZSBwYXRocyBmb3IgdGhlIHJlcXVlc3RlZCBncm91cC5cbiAgICovXG4gIGdldEdyb3VwRmlsZXMoKSB7XG4gICAgY29uc3Qgd2VpZ2h0ZWQgPSB0aGlzLmNvbXB1dGVXZWlnaHRlZEZpbGVzKClcbiAgICBjb25zdCBzb3J0ZWQgPSB0aGlzLnNvcnRCeVdlaWdodERlc2NlbmRpbmcod2VpZ2h0ZWQpXG4gICAgY29uc3QgYnVja2V0cyA9IHRoaXMuZGlzdHJpYnV0ZUdyZWVkaWx5KHNvcnRlZClcblxuICAgIHJldHVybiBidWNrZXRzW3RoaXMuX2dyb3VwTnVtYmVyIC0gMV0uZmlsZXNcbiAgfVxuXG4gIC8qKlxuICAgKiBDb21wdXRlcyB3ZWlnaHQgZm9yIGVhY2ggdGVzdCBmaWxlIGJhc2VkIG9uIGRpcmVjdG9yeSB0eXBlIGFuZCBmaWxlIHN1ZmZpeC5cbiAgICogQHJldHVybnMge1NwbGl0dGVyRmlsZUVudHJ5W119IC0gV2VpZ2h0ZWQgZmlsZSBlbnRyaWVzLlxuICAgKi9cbiAgY29tcHV0ZVdlaWdodGVkRmlsZXMoKSB7XG4gICAgcmV0dXJuIHRoaXMuX3Rlc3RGaWxlcy5tYXAoKGZpbGVQYXRoKSA9PiAoe1xuICAgICAgZmlsZVBhdGgsXG4gICAgICB3ZWlnaHQ6IHRoaXMuY29tcHV0ZVdlaWdodChmaWxlUGF0aClcbiAgICB9KSlcbiAgfVxuXG4gIC8qKlxuICAgKiBDb21wdXRlcyB0aGUgd2VpZ2h0IGZvciBhIHNpbmdsZSBmaWxlLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gZmlsZVBhdGggLSBBYnNvbHV0ZSBmaWxlIHBhdGguXG4gICAqIEByZXR1cm5zIHtudW1iZXJ9IC0gV2VpZ2h0IHZhbHVlLlxuICAgKi9cbiAgY29tcHV0ZVdlaWdodChmaWxlUGF0aCkge1xuICAgIGNvbnN0IGR1cmF0aW9uID0gdGhpcy50aW1pbmdNYW5pZmVzdER1cmF0aW9uKGZpbGVQYXRoKVxuXG4gICAgaWYgKGR1cmF0aW9uICE9PSB1bmRlZmluZWQgJiYgZHVyYXRpb24gPiAwKSB7XG4gICAgICByZXR1cm4gZHVyYXRpb25cbiAgICB9XG5cbiAgICBjb25zdCByZWxhdGl2ZVBhdGggPSB0aGlzLmhldXJpc3RpY1JlbGF0aXZlUGF0aChmaWxlUGF0aClcbiAgICBsZXQgd2VpZ2h0ID0gREVGQVVMVF9XRUlHSFRcblxuICAgIC8vIEV4dHJhY3QgdGhlIHR5cGUgZGlyZWN0b3J5IGZyb20gdGhlIHJlbGF0aXZlIHBhdGguXG4gICAgLy8gTWF0Y2hlcyBib3RoIFwic3BlYy9zeXN0ZW0vLi4uXCIgKGJhc2UgaXMgcHJvamVjdCByb290KSBhbmQgXCJzeXN0ZW0vLi4uXCIgKGJhc2UgaXMgc3BlYy8gaXRzZWxmKS5cbiAgICBjb25zdCBzcGVjRGlyTWF0Y2ggPSByZWxhdGl2ZVBhdGgubWF0Y2goL14oPzooPzpzcGVjfF9fdGVzdHNfX3x0ZXN0cylcXC8pPyhbXi9dKylcXC8vKVxuXG4gICAgaWYgKHNwZWNEaXJNYXRjaCkge1xuICAgICAgY29uc3QgZGlyTmFtZSA9IHNwZWNEaXJNYXRjaFsxXVxuXG4gICAgICBpZiAoRElSRUNUT1JZX1dFSUdIVFNbZGlyTmFtZV0gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICB3ZWlnaHQgPSBESVJFQ1RPUllfV0VJR0hUU1tkaXJOYW1lXVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIEJyb3dzZXIgc3BlYyBmaWxlcyBhcmUgaGVhdmllclxuICAgIGlmIChmaWxlUGF0aC5lbmRzV2l0aChcIi5icm93c2VyLXNwZWMuanNcIikgfHwgZmlsZVBhdGguZW5kc1dpdGgoXCIuYnJvd3Nlci1zcGVjLm1qc1wiKSkge1xuICAgICAgd2VpZ2h0ICo9IEJST1dTRVJfU1BFQ19NVUxUSVBMSUVSXG4gICAgfVxuXG4gICAgcmV0dXJuIHdlaWdodFxuICB9XG5cbiAgLyoqXG4gICAqIEtlZXBzIG9ubHkgdXNhYmxlIHBvc2l0aXZlIGZpbml0ZSBkdXJhdGlvbiBlbnRyaWVzIGtleWVkIGJ5IG5vcm1hbGl6ZWQgcmVsYXRpdmUgcGF0aC5cbiAgICogQHBhcmFtIHtSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPn0gdGltaW5nTWFuaWZlc3QgLSBQYXJzZWQgdGltaW5nIG1hbmlmZXN0LlxuICAgKiBAcmV0dXJucyB7UmVjb3JkPHN0cmluZywgbnVtYmVyPn0gLSBWYWxpZCBub3JtYWxpemVkIGR1cmF0aW9uIHdlaWdodHMuXG4gICAqL1xuICBub3JtYWxpemVUaW1pbmdNYW5pZmVzdCh0aW1pbmdNYW5pZmVzdCkge1xuICAgIC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgbnVtYmVyPn0gKi9cbiAgICBjb25zdCBub3JtYWxpemVkID0ge31cblxuICAgIGlmICghdGltaW5nTWFuaWZlc3QgfHwgdHlwZW9mIHRpbWluZ01hbmlmZXN0ICE9PSBcIm9iamVjdFwiIHx8IEFycmF5LmlzQXJyYXkodGltaW5nTWFuaWZlc3QpKSB7XG4gICAgICByZXR1cm4gbm9ybWFsaXplZFxuICAgIH1cblxuICAgIGZvciAoY29uc3QgW2ZpbGVQYXRoLCBkdXJhdGlvbl0gb2YgT2JqZWN0LmVudHJpZXModGltaW5nTWFuaWZlc3QpKSB7XG4gICAgICBpZiAodHlwZW9mIGR1cmF0aW9uICE9PSBcIm51bWJlclwiIHx8ICFOdW1iZXIuaXNGaW5pdGUoZHVyYXRpb24pIHx8IGR1cmF0aW9uIDwgMCkgY29udGludWVcblxuICAgICAgdHJ5IHtcbiAgICAgICAgbm9ybWFsaXplZFt0aGlzLm5vcm1hbGl6ZVJlbGF0aXZlUGF0aChmaWxlUGF0aCldID0gZHVyYXRpb25cbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGlmICghKGVycm9yIGluc3RhbmNlb2YgRXJyb3IpKSB0aHJvdyBlcnJvclxuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBub3JtYWxpemVkXG4gIH1cblxuICAvKipcbiAgICogTm9ybWFsaXplcyBhIHJlbGF0aXZlIHRlc3QgcGF0aCB0byB0aGUgbWFuaWZlc3QncyBwb3J0YWJsZSBzbGFzaCBmb3JtYXQuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBmaWxlUGF0aCAtIFJlbGF0aXZlIHRlc3QgcGF0aC5cbiAgICogQHJldHVybnMge3N0cmluZ30gLSBOb3JtYWxpemVkIHJlbGF0aXZlIHRlc3QgcGF0aC5cbiAgICovXG4gIG5vcm1hbGl6ZVJlbGF0aXZlUGF0aChmaWxlUGF0aCkge1xuICAgIHJldHVybiBjYW5vbmljYWxUaW1pbmdNYW5pZmVzdFBhdGgoZmlsZVBhdGgpXG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgcGVybWlzc2l2ZSByZWxhdGl2ZSBwYXRoIHVzZWQgb25seSBmb3IgaGV1cmlzdGljIGNsYXNzaWZpY2F0aW9uLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gZmlsZVBhdGggLSBBYnNvbHV0ZSB0ZXN0IGZpbGUgcGF0aC5cbiAgICogQHJldHVybnMge3N0cmluZ30gLSBQb3J0YWJsZSByZWxhdGl2ZSBwYXRoIHdoaWNoIG1heSBlc2NhcGUgdGhlIHByb2ZpbGluZyBiYXNlLlxuICAgKi9cbiAgaGV1cmlzdGljUmVsYXRpdmVQYXRoKGZpbGVQYXRoKSB7XG4gICAgcmV0dXJuIHBhdGgucmVsYXRpdmUodGhpcy5fYmFzZURpcmVjdG9yeSwgZmlsZVBhdGgpXG4gICAgICAucmVwbGFjZUFsbChcIlxcXFxcIiwgXCIvXCIpXG4gICAgICAucmVwbGFjZSgvXlxcLlxcLy8sIFwiXCIpXG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBhIGNhbm9uaWNhbCBtYW5pZmVzdCBrZXkgd2hlbiB0aGUgZmlsZSBpcyByZXByZXNlbnRhYmxlIHVuZGVyIHRoZSBwcm9maWxpbmcgYmFzZS5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGZpbGVQYXRoIC0gQWJzb2x1dGUgdGVzdCBmaWxlIHBhdGguXG4gICAqIEByZXR1cm5zIHtzdHJpbmcgfCB1bmRlZmluZWR9IC0gQ2Fub25pY2FsIHJlbGF0aXZlIHBhdGgsIG9yIHVuZGVmaW5lZCBmb3IgYW4gZXh0ZXJuYWwgZmlsZS5cbiAgICovXG4gIG1hbmlmZXN0UmVsYXRpdmVQYXRoKGZpbGVQYXRoKSB7XG4gICAgY29uc3QgcmVsYXRpdmVQYXRoID0gcGF0aC5yZWxhdGl2ZSh0aGlzLl9iYXNlRGlyZWN0b3J5LCBmaWxlUGF0aClcblxuICAgIGlmICghcmVsYXRpdmVQYXRoIHx8IHBhdGguaXNBYnNvbHV0ZShyZWxhdGl2ZVBhdGgpKSByZXR1cm5cbiAgICBpZiAocmVsYXRpdmVQYXRoID09PSBcIi4uXCIgfHwgcmVsYXRpdmVQYXRoLnN0YXJ0c1dpdGgoYC4uJHtwYXRoLnNlcH1gKSkgcmV0dXJuXG5cbiAgICByZXR1cm4gdGhpcy5ub3JtYWxpemVSZWxhdGl2ZVBhdGgocmVsYXRpdmVQYXRoKVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGZpcnN0IG1hbmlmZXN0IGVudHJ5IG1hdGNoaW5nIGEgZGlzY292ZXJlZCB0ZXN0IGZpbGUuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBmaWxlUGF0aCAtIEFic29sdXRlIHRlc3QgZmlsZSBwYXRoLlxuICAgKiBAcmV0dXJucyB7bnVtYmVyIHwgdW5kZWZpbmVkfSAtIFJlY29yZGVkIGR1cmF0aW9uLCBpbmNsdWRpbmcgemVyby5cbiAgICovXG4gIHRpbWluZ01hbmlmZXN0RHVyYXRpb24oZmlsZVBhdGgpIHtcbiAgICBmb3IgKGNvbnN0IG1hbmlmZXN0UGF0aCBvZiB0aGlzLnRpbWluZ01hbmlmZXN0UGF0aHMoZmlsZVBhdGgpKSB7XG4gICAgICBpZiAoT2JqZWN0Lmhhc093bih0aGlzLl90aW1pbmdNYW5pZmVzdCwgbWFuaWZlc3RQYXRoKSkgcmV0dXJuIHRoaXMuX3RpbWluZ01hbmlmZXN0W21hbmlmZXN0UGF0aF1cbiAgICB9XG5cbiAgICByZXR1cm4gdW5kZWZpbmVkXG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBjb21wYXRpYmxlIG1hbmlmZXN0IGtleXMgZm9yIG9uZSBkaXNjb3ZlcmVkIGZpbGUuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBmaWxlUGF0aCAtIEFic29sdXRlIHRlc3QgZmlsZSBwYXRoLlxuICAgKiBAcmV0dXJucyB7c3RyaW5nW119IC0gQ2Fub25pY2FsIGtleXMgaW4gbWF0Y2hpbmcgcHJpb3JpdHkgb3JkZXIuXG4gICAqL1xuICB0aW1pbmdNYW5pZmVzdFBhdGhzKGZpbGVQYXRoKSB7XG4gICAgY29uc3QgcmVsYXRpdmVQYXRoID0gdGhpcy5tYW5pZmVzdFJlbGF0aXZlUGF0aChmaWxlUGF0aClcblxuICAgIGlmICghcmVsYXRpdmVQYXRoKSByZXR1cm4gW11cblxuICAgIGNvbnN0IHByb2plY3RSZWxhdGl2ZVBhdGggPSB0aGlzLm5vcm1hbGl6ZVJlbGF0aXZlUGF0aChwYXRoLmpvaW4ocGF0aC5iYXNlbmFtZSh0aGlzLl9iYXNlRGlyZWN0b3J5KSwgcmVsYXRpdmVQYXRoKSlcblxuICAgIHJldHVybiByZWxhdGl2ZVBhdGggPT09IHByb2plY3RSZWxhdGl2ZVBhdGggPyBbcmVsYXRpdmVQYXRoXSA6IFtyZWxhdGl2ZVBhdGgsIHByb2plY3RSZWxhdGl2ZVBhdGhdXG4gIH1cblxuICAvKipcbiAgICogU3VtbWFyaXplcyB0aW1pbmctaGlzdG9yeSBjb3ZlcmFnZSBmb3IgdGhlIGNvbXBsZXRlIGRpc2NvdmVyZWQgc3VpdGUuXG4gICAqIEByZXR1cm5zIHt7aGV1cmlzdGljRmlsZXM6IG51bWJlciwgbWVhc3VyZWRGaWxlczogbnVtYmVyLCBzdGFsZUVudHJpZXM6IG51bWJlcn19IC0gQ29tcGFjdCBjb3ZlcmFnZSBjb3VudHMuXG4gICAqL1xuICBnZXRUaW1pbmdNYW5pZmVzdENvdmVyYWdlKCkge1xuICAgIGNvbnN0IG1hdGNoZWRNYW5pZmVzdFBhdGhzID0gbmV3IFNldCgpXG4gICAgbGV0IG1lYXN1cmVkRmlsZXMgPSAwXG5cbiAgICBmb3IgKGNvbnN0IGZpbGVQYXRoIG9mIHRoaXMuX3Rlc3RGaWxlcykge1xuICAgICAgbGV0IG1hdGNoZWREdXJhdGlvblxuXG4gICAgICBmb3IgKGNvbnN0IG1hbmlmZXN0UGF0aCBvZiB0aGlzLnRpbWluZ01hbmlmZXN0UGF0aHMoZmlsZVBhdGgpKSB7XG4gICAgICAgIGlmICghT2JqZWN0Lmhhc093bih0aGlzLl90aW1pbmdNYW5pZmVzdCwgbWFuaWZlc3RQYXRoKSkgY29udGludWVcblxuICAgICAgICBtYXRjaGVkTWFuaWZlc3RQYXRocy5hZGQobWFuaWZlc3RQYXRoKVxuICAgICAgICBtYXRjaGVkRHVyYXRpb24gPSB0aGlzLl90aW1pbmdNYW5pZmVzdFttYW5pZmVzdFBhdGhdXG4gICAgICAgIGJyZWFrXG4gICAgICB9XG5cbiAgICAgIGlmIChtYXRjaGVkRHVyYXRpb24gIT09IHVuZGVmaW5lZCAmJiBtYXRjaGVkRHVyYXRpb24gPiAwKSBtZWFzdXJlZEZpbGVzKytcbiAgICB9XG5cbiAgICByZXR1cm4ge1xuICAgICAgaGV1cmlzdGljRmlsZXM6IHRoaXMuX3Rlc3RGaWxlcy5sZW5ndGggLSBtZWFzdXJlZEZpbGVzLFxuICAgICAgbWVhc3VyZWRGaWxlcyxcbiAgICAgIHN0YWxlRW50cmllczogT2JqZWN0LmtleXModGhpcy5fdGltaW5nTWFuaWZlc3QpLmxlbmd0aCAtIG1hdGNoZWRNYW5pZmVzdFBhdGhzLnNpemVcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU29ydHMgZmlsZXMgYnkgd2VpZ2h0IGRlc2NlbmRpbmcsIHRoZW4gYnkgcGF0aCBmb3IgZGV0ZXJtaW5pc20uXG4gICAqIEBwYXJhbSB7U3BsaXR0ZXJGaWxlRW50cnlbXX0gZmlsZXMgLSBXZWlnaHRlZCBmaWxlcy5cbiAgICogQHJldHVybnMge1NwbGl0dGVyRmlsZUVudHJ5W119IC0gU29ydGVkIGZpbGVzLlxuICAgKi9cbiAgc29ydEJ5V2VpZ2h0RGVzY2VuZGluZyhmaWxlcykge1xuICAgIHJldHVybiBbLi4uZmlsZXNdLnNvcnQoKGEsIGIpID0+IHtcbiAgICAgIGlmIChiLndlaWdodCAhPT0gYS53ZWlnaHQpIHtcbiAgICAgICAgcmV0dXJuIGIud2VpZ2h0IC0gYS53ZWlnaHRcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIGNvbXBhcmVUaW1pbmdNYW5pZmVzdFBhdGhzKGEuZmlsZVBhdGgsIGIuZmlsZVBhdGgpXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBEaXN0cmlidXRlcyBmaWxlcyBncmVlZGlseSBpbnRvIE4gYmFsYW5jZWQgZ3JvdXBzLlxuICAgKiBFYWNoIGZpbGUgaXMgYXNzaWduZWQgdG8gdGhlIGdyb3VwIHdpdGggdGhlIGxlYXN0IGFjY3VtdWxhdGVkIHdlaWdodC5cbiAgICogQHBhcmFtIHtTcGxpdHRlckZpbGVFbnRyeVtdfSBzb3J0ZWRGaWxlcyAtIEZpbGVzIHNvcnRlZCBieSB3ZWlnaHQgZGVzY2VuZGluZy5cbiAgICogQHJldHVybnMge0dyb3VwQnVja2V0W119IC0gQXJyYXkgb2YgZ3JvdXAgYnVja2V0cy5cbiAgICovXG4gIGRpc3RyaWJ1dGVHcmVlZGlseShzb3J0ZWRGaWxlcykge1xuICAgIC8qKlxuICAgICAqIEJ1Y2tldHMuXG4gICAgICogQHR5cGUge0dyb3VwQnVja2V0W119ICovXG4gICAgY29uc3QgYnVja2V0cyA9IFtdXG5cbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRoaXMuX2dyb3VwczsgaSsrKSB7XG4gICAgICBidWNrZXRzLnB1c2goe3RvdGFsV2VpZ2h0OiAwLCBmaWxlczogW119KVxuICAgIH1cblxuICAgIGZvciAoY29uc3QgZW50cnkgb2Ygc29ydGVkRmlsZXMpIHtcbiAgICAgIGNvbnN0IGxpZ2h0ZXN0ID0gdGhpcy5maW5kTGlnaHRlc3RCdWNrZXQoYnVja2V0cylcblxuICAgICAgbGlnaHRlc3QuZmlsZXMucHVzaChlbnRyeS5maWxlUGF0aClcbiAgICAgIGxpZ2h0ZXN0LnRvdGFsV2VpZ2h0ICs9IGVudHJ5LndlaWdodFxuICAgIH1cblxuICAgIHJldHVybiBidWNrZXRzXG4gIH1cblxuICAvKipcbiAgICogRmluZHMgdGhlIGJ1Y2tldCB3aXRoIHRoZSBsZWFzdCBhY2N1bXVsYXRlZCB3ZWlnaHQuXG4gICAqIFRpZXMgYXJlIGJyb2tlbiBieSBidWNrZXQgaW5kZXggKGVhcmxpZXIgYnVja2V0IHdpbnMpIGZvciBkZXRlcm1pbmlzbS5cbiAgICogQHBhcmFtIHtHcm91cEJ1Y2tldFtdfSBidWNrZXRzIC0gR3JvdXAgYnVja2V0cy5cbiAgICogQHJldHVybnMge0dyb3VwQnVja2V0fSAtIFRoZSBsaWdodGVzdCBidWNrZXQuXG4gICAqL1xuICBmaW5kTGlnaHRlc3RCdWNrZXQoYnVja2V0cykge1xuICAgIGxldCBsaWdodGVzdCA9IGJ1Y2tldHNbMF1cblxuICAgIGZvciAobGV0IGkgPSAxOyBpIDwgYnVja2V0cy5sZW5ndGg7IGkrKykge1xuICAgICAgaWYgKGJ1Y2tldHNbaV0udG90YWxXZWlnaHQgPCBsaWdodGVzdC50b3RhbFdlaWdodCkge1xuICAgICAgICBsaWdodGVzdCA9IGJ1Y2tldHNbaV1cbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gbGlnaHRlc3RcbiAgfVxufVxuIl19
|
|
2
|
+
export { TestSuiteSplitter as default } from "@velocious/testing/node";
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC1zdWl0ZS1zcGxpdHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy90ZXN0aW5nL3Rlc3Qtc3VpdGUtc3BsaXR0ZXIuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8sRUFBRSxpQkFBaUIsSUFBSSxPQUFPLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5leHBvcnQgeyBUZXN0U3VpdGVTcGxpdHRlciBhcyBkZWZhdWx0IH0gZnJvbSBcIkB2ZWxvY2lvdXMvdGVzdGluZy9ub2RlXCJcbiJdfQ==
|
|
@@ -1,94 +1,6 @@
|
|
|
1
|
-
export type TimingManifest =
|
|
2
|
-
export type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
discoveredFileCount: number;
|
|
7
|
-
/**
|
|
8
|
-
* - Selected post-shard file count.
|
|
9
|
-
*/
|
|
10
|
-
fileCount: number;
|
|
11
|
-
/**
|
|
12
|
-
* - One-indexed shard number.
|
|
13
|
-
*/
|
|
14
|
-
groupNumber: number;
|
|
15
|
-
/**
|
|
16
|
-
* - Complete shard count.
|
|
17
|
-
*/
|
|
18
|
-
groups: number;
|
|
19
|
-
/**
|
|
20
|
-
* - Profiling path-base semantics.
|
|
21
|
-
*/
|
|
22
|
-
pathBase: string;
|
|
23
|
-
/**
|
|
24
|
-
* - Complete canonical file-set identity.
|
|
25
|
-
*/
|
|
26
|
-
testFileSetHash: string;
|
|
27
|
-
/**
|
|
28
|
-
* - Canonical shard timing map.
|
|
29
|
-
*/
|
|
30
|
-
timingManifest: TimingManifest;
|
|
31
|
-
};
|
|
32
|
-
export type TestProfileTimingManifestInput = {
|
|
33
|
-
/**
|
|
34
|
-
* - Parsed rich test profile.
|
|
35
|
-
*/
|
|
36
|
-
profile: ReturnType<typeof JSON.parse>;
|
|
37
|
-
/**
|
|
38
|
-
* - Human-readable input source for validation errors.
|
|
39
|
-
*/
|
|
40
|
-
source: string;
|
|
41
|
-
};
|
|
42
|
-
/** @typedef {Record<string, number>} TimingManifest */
|
|
43
|
-
/**
|
|
44
|
-
* ValidatedProfileShard type.
|
|
45
|
-
* @typedef {object} ValidatedProfileShard
|
|
46
|
-
* @property {number} discoveredFileCount - Complete pre-shard file count.
|
|
47
|
-
* @property {number} fileCount - Selected post-shard file count.
|
|
48
|
-
* @property {number} groupNumber - One-indexed shard number.
|
|
49
|
-
* @property {number} groups - Complete shard count.
|
|
50
|
-
* @property {string} pathBase - Profiling path-base semantics.
|
|
51
|
-
* @property {string} testFileSetHash - Complete canonical file-set identity.
|
|
52
|
-
* @property {TimingManifest} timingManifest - Canonical shard timing map.
|
|
53
|
-
*/
|
|
54
|
-
/**
|
|
55
|
-
* TestProfileTimingManifestInput type.
|
|
56
|
-
* @typedef {object} TestProfileTimingManifestInput
|
|
57
|
-
* @property {ReturnType<typeof JSON.parse>} profile - Parsed rich test profile.
|
|
58
|
-
* @property {string} source - Human-readable input source for validation errors.
|
|
59
|
-
*/
|
|
60
|
-
/**
|
|
61
|
-
* Canonicalizes a timing-manifest path relative to its profiling base.
|
|
62
|
-
* @param {string} filePath - Candidate relative path.
|
|
63
|
-
* @returns {string} - Portable canonical path.
|
|
64
|
-
*/
|
|
65
|
-
export declare function canonicalTimingManifestPath(filePath: string): string;
|
|
66
|
-
/**
|
|
67
|
-
* Compares timing-manifest paths by JavaScript code units without locale rules.
|
|
68
|
-
* @param {string} filePathA - First path.
|
|
69
|
-
* @param {string} filePathB - Second path.
|
|
70
|
-
* @returns {number} - Negative, zero, or positive ordering result.
|
|
71
|
-
*/
|
|
72
|
-
export declare function compareTimingManifestPaths(filePathA: string, filePathB: string): number;
|
|
73
|
-
/**
|
|
74
|
-
* Validates and sorts a plain timing manifest.
|
|
75
|
-
* @param {ReturnType<typeof JSON.parse>} timingManifest - Parsed timing manifest.
|
|
76
|
-
* @param {{source?: string}} [options] - Validation context.
|
|
77
|
-
* @returns {TimingManifest} - Canonical sorted timing manifest.
|
|
78
|
-
*/
|
|
79
|
-
export declare function validateTimingManifest(timingManifest: ReturnType<typeof JSON.parse>, { source }?: {
|
|
80
|
-
source?: string;
|
|
81
|
-
}): TimingManifest;
|
|
82
|
-
/**
|
|
83
|
-
* Returns an opaque deterministic identity for a complete canonical test-file set.
|
|
84
|
-
* @param {string[]} filePaths - Paths relative to one profiling base.
|
|
85
|
-
* @returns {string} - SHA-256 file-set identity.
|
|
86
|
-
*/
|
|
87
|
-
export declare function timingManifestFileSetHash(filePaths: string[]): string;
|
|
88
|
-
/**
|
|
89
|
-
* Merges a complete compatible set of rich Velocious shard profiles.
|
|
90
|
-
* @param {TestProfileTimingManifestInput[]} inputs - Parsed profile documents and sources.
|
|
91
|
-
* @returns {TimingManifest} - Complete sorted plain timing manifest.
|
|
92
|
-
*/
|
|
93
|
-
export declare function mergeTestProfileTimingManifests(inputs: TestProfileTimingManifestInput[]): TimingManifest;
|
|
1
|
+
export type TimingManifest = import("@velocious/testing/node").TimingManifest;
|
|
2
|
+
export type TestProfileTimingManifestInput = import("@velocious/testing/node").TestProfileTimingManifestInput;
|
|
3
|
+
/** @typedef {import("@velocious/testing/node").TimingManifest} TimingManifest */
|
|
4
|
+
/** @typedef {import("@velocious/testing/node").TestProfileTimingManifestInput} TestProfileTimingManifestInput */
|
|
5
|
+
export { canonicalTimingManifestPath, compareTimingManifestPaths, mergeTestProfileTimingManifests, timingManifestFileSetHash, validateTimingManifest } from "@velocious/testing/node";
|
|
94
6
|
//# sourceMappingURL=timing-manifest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing-manifest.d.ts","sourceRoot":"","sources":["../../../src/testing/timing-manifest.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"timing-manifest.d.ts","sourceRoot":"","sources":["../../../src/testing/timing-manifest.js"],"names":[],"mappings":"AAEI,YAA4D,cAAc,GAAhE,OAAO,yBAAyB,EAAE,cAAc,CAAgB;AAC1E,YAA4E,8BAA8B,GAAhG,OAAO,yBAAyB,EAAE,8BAA8B,CAAgC;AAD9G,iFAAiF;AACjF,iHAAiH;AAEjH,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,+BAA+B,EAC/B,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,yBAAyB,CAAA"}
|