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
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs/promises"
|
|
4
4
|
import path from "path"
|
|
5
|
-
import {AsyncLocalStorage} from "node:async_hooks"
|
|
6
|
-
import {createTestContext, defaultTestContext} from "@velocious/testing"
|
|
7
|
-
import {
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks"
|
|
6
|
+
import { createTestContext, defaultTestContext } from "@velocious/testing"
|
|
7
|
+
import { slowestTestResults } from "@velocious/testing/reporters"
|
|
8
|
+
import { TestRunner as PackageTestRunner } from "@velocious/testing/runner"
|
|
8
9
|
import Application from "../../src/application.js"
|
|
9
10
|
import RequestClient from "./request-client.js"
|
|
10
11
|
import picocolors from "picocolors"
|
|
11
12
|
import restArgsError from "../utils/rest-args-error.js"
|
|
12
|
-
import {testConfig} from "./test.js"
|
|
13
|
-
import {fileURLToPath, pathToFileURL} from "url"
|
|
13
|
+
import { testConfig } from "./test.js"
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from "url"
|
|
14
15
|
import SharedTransactionBroker from "./shared-transaction-broker.js"
|
|
15
16
|
import { SHARED_TRANSACTION_BROKER_ENV } from "./shared-transaction-proxy-driver.js"
|
|
16
17
|
import VelociousAttemptExecutor from "./velocious-attempt-executor.js"
|
|
17
|
-
import VelociousRunnerReporter, {AbortRemainingTestsError} from "./velocious-runner-reporter.js"
|
|
18
|
+
import VelociousRunnerReporter, { AbortRemainingTestsError } from "./velocious-runner-reporter.js"
|
|
18
19
|
import VelociousSuiteHookExecutor from "./velocious-suite-hook-executor.js"
|
|
19
20
|
import VelociousTestArguments from "./velocious-test-arguments.js"
|
|
20
21
|
|
|
@@ -177,8 +178,11 @@ export default class TestRunner {
|
|
|
177
178
|
* @param {Record<string, number[]>} [args.lineFilters] - Line filters by file.
|
|
178
179
|
* @param {RegExp[]} [args.examplePatterns] - Example patterns.
|
|
179
180
|
* @param {import("./test-profiler.js").default} [args.profiler] - Opt-in profiler.
|
|
181
|
+
* @param {number} [args.retries] - Default retry count.
|
|
182
|
+
* @param {string[]} [args.setupFiles] - Setup files imported before test files.
|
|
183
|
+
* @param {number} [args.timeoutMs] - Default lifecycle timeout.
|
|
180
184
|
*/
|
|
181
|
-
constructor({configuration, context = defaultTestContext, excludeTags, includeTags, testFiles, lineFilters, examplePatterns, profiler, ...restArgs}) {
|
|
185
|
+
constructor({configuration, context = defaultTestContext, excludeTags, includeTags, testFiles, lineFilters, examplePatterns, profiler, retries, setupFiles, timeoutMs, ...restArgs}) {
|
|
182
186
|
restArgsError(restArgs)
|
|
183
187
|
|
|
184
188
|
if (!configuration) throw new Error("configuration is required")
|
|
@@ -193,6 +197,9 @@ export default class TestRunner {
|
|
|
193
197
|
this._lineFilters = lineFilters || {}
|
|
194
198
|
this._examplePatterns = examplePatterns || []
|
|
195
199
|
this._profiler = profiler
|
|
200
|
+
this._retries = retries
|
|
201
|
+
this._setupFiles = setupFiles || []
|
|
202
|
+
this._timeoutMs = timeoutMs
|
|
196
203
|
this._abortRemainingTests = false
|
|
197
204
|
|
|
198
205
|
this._failedTests = 0
|
|
@@ -1124,6 +1131,15 @@ export default class TestRunner {
|
|
|
1124
1131
|
* @returns {Array<{fullDescription: string, filePath: string, line: number, durationMs: number}>} - Slowest tests, slowest first.
|
|
1125
1132
|
*/
|
|
1126
1133
|
getSlowestTests(limit = 10) {
|
|
1134
|
+
if (this._packageResult) {
|
|
1135
|
+
return slowestTestResults(this._packageResult, {limit}).map((result) => ({
|
|
1136
|
+
fullDescription: result.fullName,
|
|
1137
|
+
filePath: result.filePath ?? "<unknown>",
|
|
1138
|
+
line: result.line ?? 0,
|
|
1139
|
+
durationMs: result.durationMs
|
|
1140
|
+
}))
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1127
1143
|
const sorted = [...this._testDurations].sort((testA, testB) => testB.durationMs - testA.durationMs)
|
|
1128
1144
|
|
|
1129
1145
|
return limit > 0 ? sorted.slice(0, limit) : sorted
|
|
@@ -1160,6 +1176,12 @@ export default class TestRunner {
|
|
|
1160
1176
|
const testingConfigPath = this.getConfiguration().getTesting()
|
|
1161
1177
|
|
|
1162
1178
|
await context.describe("", {databaseCleaning: {transaction: true}}, async () => {
|
|
1179
|
+
if (this._setupFiles.length > 0) {
|
|
1180
|
+
await this.runProfileSpan({phase: "testing config/global setup"}, async () => {
|
|
1181
|
+
await this.getConfiguration().getEnvironmentHandler().importTestFiles(this._setupFiles)
|
|
1182
|
+
})
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1163
1185
|
if (testingConfigPath) {
|
|
1164
1186
|
await this.runProfileSpan({phase: "testing config/global setup"}, async () => {
|
|
1165
1187
|
await this.getConfiguration().getEnvironmentHandler().importTestingConfigPath()
|
|
@@ -1549,7 +1571,7 @@ export default class TestRunner {
|
|
|
1549
1571
|
* @returns {number} - Effective retry count.
|
|
1550
1572
|
*/
|
|
1551
1573
|
retryCount(test) {
|
|
1552
|
-
const value = test.options.retries ?? test.options.retry ?? this.getTestContext().config.retries
|
|
1574
|
+
const value = test.options.retries ?? test.options.retry ?? this._retries ?? this.getTestContext().config.retries
|
|
1553
1575
|
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0
|
|
1554
1576
|
}
|
|
1555
1577
|
|
|
@@ -1650,7 +1672,9 @@ export default class TestRunner {
|
|
|
1650
1672
|
attemptExecutor: (input) => this._attemptExecutor.execute(input),
|
|
1651
1673
|
testArgumentResolver: (input) => this._testArguments.resolve(input),
|
|
1652
1674
|
suiteHookExecutor: (input) => this._suiteHookExecutor.execute(input),
|
|
1653
|
-
reporter: this._runnerReporter
|
|
1675
|
+
reporter: this._runnerReporter,
|
|
1676
|
+
retries: this._retries,
|
|
1677
|
+
timeoutMs: this._timeoutMs
|
|
1654
1678
|
})
|
|
1655
1679
|
const failureStart = this._suiteHookFailures.length
|
|
1656
1680
|
const restoreRetryOptions = this.normalizePackageRetriesForExecution()
|
|
@@ -1,303 +1,3 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import restArgsError from "../utils/rest-args-error.js"
|
|
5
|
-
import { canonicalTimingManifestPath, compareTimingManifestPaths } from "./timing-manifest.js"
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* SplitterFileEntry type.
|
|
9
|
-
* @typedef {object} SplitterFileEntry
|
|
10
|
-
* @property {string} filePath - Absolute file path.
|
|
11
|
-
* @property {number} weight - Computed weight for load balancing.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* GroupBucket type.
|
|
16
|
-
* @typedef {object} GroupBucket
|
|
17
|
-
* @property {number} totalWeight - Accumulated weight.
|
|
18
|
-
* @property {string[]} files - Files assigned to this group.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/** Default weight for a regular test file. */
|
|
22
|
-
const DEFAULT_WEIGHT = 1
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Weight multipliers by spec directory name.
|
|
26
|
-
* Heavier test types get higher weights so greedy distribution balances wall-clock time.
|
|
27
|
-
* @type {Record<string, number>}
|
|
28
|
-
*/
|
|
29
|
-
const DIRECTORY_WEIGHTS = {
|
|
30
|
-
system: 20,
|
|
31
|
-
"frontend-models": 10,
|
|
32
|
-
controller: 3
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Extra multiplier applied to browser spec files on top of directory weight. */
|
|
36
|
-
const BROWSER_SPEC_MULTIPLIER = 2
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Splits a list of test files into balanced groups using a greedy load-balancing algorithm.
|
|
40
|
-
* Modeled after test_suite_splitter for RSpec.
|
|
41
|
-
*/
|
|
42
|
-
export default class TestSuiteSplitter {
|
|
43
|
-
/**
|
|
44
|
-
* Runs constructor.
|
|
45
|
-
* @param {object} args - Options.
|
|
46
|
-
* @param {number} args.groups - Total number of groups.
|
|
47
|
-
* @param {number} args.groupNumber - Which group to return (1-indexed).
|
|
48
|
-
* @param {string[]} args.testFiles - All discovered test file paths.
|
|
49
|
-
* @param {string} [args.baseDirectory] - Base directory for relative path computation.
|
|
50
|
-
* @param {ReturnType<typeof JSON.parse>} [args.timingManifest] - Relative test paths mapped to durations.
|
|
51
|
-
*/
|
|
52
|
-
constructor({groups, groupNumber, testFiles, baseDirectory, timingManifest, ...restArgs}) {
|
|
53
|
-
restArgsError(restArgs)
|
|
54
|
-
|
|
55
|
-
if (!Number.isInteger(groups) || groups < 1) {
|
|
56
|
-
throw new Error(`--groups must be a positive integer, got: ${groups}`)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!Number.isInteger(groupNumber) || groupNumber < 1 || groupNumber > groups) {
|
|
60
|
-
throw new Error(`--group-number must be between 1 and ${groups}, got: ${groupNumber}`)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
this._groups = groups
|
|
64
|
-
this._groupNumber = groupNumber
|
|
65
|
-
this._testFiles = testFiles
|
|
66
|
-
this._baseDirectory = baseDirectory || process.cwd()
|
|
67
|
-
this._timingManifest = this.normalizeTimingManifest(timingManifest)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Returns the test files assigned to this group.
|
|
72
|
-
* @returns {string[]} - File paths for the requested group.
|
|
73
|
-
*/
|
|
74
|
-
getGroupFiles() {
|
|
75
|
-
const weighted = this.computeWeightedFiles()
|
|
76
|
-
const sorted = this.sortByWeightDescending(weighted)
|
|
77
|
-
const buckets = this.distributeGreedily(sorted)
|
|
78
|
-
|
|
79
|
-
return buckets[this._groupNumber - 1].files
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Computes weight for each test file based on directory type and file suffix.
|
|
84
|
-
* @returns {SplitterFileEntry[]} - Weighted file entries.
|
|
85
|
-
*/
|
|
86
|
-
computeWeightedFiles() {
|
|
87
|
-
return this._testFiles.map((filePath) => ({
|
|
88
|
-
filePath,
|
|
89
|
-
weight: this.computeWeight(filePath)
|
|
90
|
-
}))
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Computes the weight for a single file.
|
|
95
|
-
* @param {string} filePath - Absolute file path.
|
|
96
|
-
* @returns {number} - Weight value.
|
|
97
|
-
*/
|
|
98
|
-
computeWeight(filePath) {
|
|
99
|
-
const duration = this.timingManifestDuration(filePath)
|
|
100
|
-
|
|
101
|
-
if (duration !== undefined && duration > 0) {
|
|
102
|
-
return duration
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const relativePath = this.heuristicRelativePath(filePath)
|
|
106
|
-
let weight = DEFAULT_WEIGHT
|
|
107
|
-
|
|
108
|
-
// Extract the type directory from the relative path.
|
|
109
|
-
// Matches both "spec/system/..." (base is project root) and "system/..." (base is spec/ itself).
|
|
110
|
-
const specDirMatch = relativePath.match(/^(?:(?:spec|__tests__|tests)\/)?([^/]+)\//)
|
|
111
|
-
|
|
112
|
-
if (specDirMatch) {
|
|
113
|
-
const dirName = specDirMatch[1]
|
|
114
|
-
|
|
115
|
-
if (DIRECTORY_WEIGHTS[dirName] !== undefined) {
|
|
116
|
-
weight = DIRECTORY_WEIGHTS[dirName]
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Browser spec files are heavier
|
|
121
|
-
if (filePath.endsWith(".browser-spec.js") || filePath.endsWith(".browser-spec.mjs")) {
|
|
122
|
-
weight *= BROWSER_SPEC_MULTIPLIER
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return weight
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Keeps only usable positive finite duration entries keyed by normalized relative path.
|
|
130
|
-
* @param {ReturnType<typeof JSON.parse>} timingManifest - Parsed timing manifest.
|
|
131
|
-
* @returns {Record<string, number>} - Valid normalized duration weights.
|
|
132
|
-
*/
|
|
133
|
-
normalizeTimingManifest(timingManifest) {
|
|
134
|
-
/** @type {Record<string, number>} */
|
|
135
|
-
const normalized = {}
|
|
136
|
-
|
|
137
|
-
if (!timingManifest || typeof timingManifest !== "object" || Array.isArray(timingManifest)) {
|
|
138
|
-
return normalized
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
for (const [filePath, duration] of Object.entries(timingManifest)) {
|
|
142
|
-
if (typeof duration !== "number" || !Number.isFinite(duration) || duration < 0) continue
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
normalized[this.normalizeRelativePath(filePath)] = duration
|
|
146
|
-
} catch (error) {
|
|
147
|
-
if (!(error instanceof Error)) throw error
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return normalized
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Normalizes a relative test path to the manifest's portable slash format.
|
|
156
|
-
* @param {string} filePath - Relative test path.
|
|
157
|
-
* @returns {string} - Normalized relative test path.
|
|
158
|
-
*/
|
|
159
|
-
normalizeRelativePath(filePath) {
|
|
160
|
-
return canonicalTimingManifestPath(filePath)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Returns the permissive relative path used only for heuristic classification.
|
|
165
|
-
* @param {string} filePath - Absolute test file path.
|
|
166
|
-
* @returns {string} - Portable relative path which may escape the profiling base.
|
|
167
|
-
*/
|
|
168
|
-
heuristicRelativePath(filePath) {
|
|
169
|
-
return path.relative(this._baseDirectory, filePath)
|
|
170
|
-
.replaceAll("\\", "/")
|
|
171
|
-
.replace(/^\.\//, "")
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Returns a canonical manifest key when the file is representable under the profiling base.
|
|
176
|
-
* @param {string} filePath - Absolute test file path.
|
|
177
|
-
* @returns {string | undefined} - Canonical relative path, or undefined for an external file.
|
|
178
|
-
*/
|
|
179
|
-
manifestRelativePath(filePath) {
|
|
180
|
-
const relativePath = path.relative(this._baseDirectory, filePath)
|
|
181
|
-
|
|
182
|
-
if (!relativePath || path.isAbsolute(relativePath)) return
|
|
183
|
-
if (relativePath === ".." || relativePath.startsWith(`..${path.sep}`)) return
|
|
184
|
-
|
|
185
|
-
return this.normalizeRelativePath(relativePath)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Returns the first manifest entry matching a discovered test file.
|
|
190
|
-
* @param {string} filePath - Absolute test file path.
|
|
191
|
-
* @returns {number | undefined} - Recorded duration, including zero.
|
|
192
|
-
*/
|
|
193
|
-
timingManifestDuration(filePath) {
|
|
194
|
-
for (const manifestPath of this.timingManifestPaths(filePath)) {
|
|
195
|
-
if (Object.hasOwn(this._timingManifest, manifestPath)) return this._timingManifest[manifestPath]
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return undefined
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Returns compatible manifest keys for one discovered file.
|
|
203
|
-
* @param {string} filePath - Absolute test file path.
|
|
204
|
-
* @returns {string[]} - Canonical keys in matching priority order.
|
|
205
|
-
*/
|
|
206
|
-
timingManifestPaths(filePath) {
|
|
207
|
-
const relativePath = this.manifestRelativePath(filePath)
|
|
208
|
-
|
|
209
|
-
if (!relativePath) return []
|
|
210
|
-
|
|
211
|
-
const projectRelativePath = this.normalizeRelativePath(path.join(path.basename(this._baseDirectory), relativePath))
|
|
212
|
-
|
|
213
|
-
return relativePath === projectRelativePath ? [relativePath] : [relativePath, projectRelativePath]
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Summarizes timing-history coverage for the complete discovered suite.
|
|
218
|
-
* @returns {{heuristicFiles: number, measuredFiles: number, staleEntries: number}} - Compact coverage counts.
|
|
219
|
-
*/
|
|
220
|
-
getTimingManifestCoverage() {
|
|
221
|
-
const matchedManifestPaths = new Set()
|
|
222
|
-
let measuredFiles = 0
|
|
223
|
-
|
|
224
|
-
for (const filePath of this._testFiles) {
|
|
225
|
-
let matchedDuration
|
|
226
|
-
|
|
227
|
-
for (const manifestPath of this.timingManifestPaths(filePath)) {
|
|
228
|
-
if (!Object.hasOwn(this._timingManifest, manifestPath)) continue
|
|
229
|
-
|
|
230
|
-
matchedManifestPaths.add(manifestPath)
|
|
231
|
-
matchedDuration = this._timingManifest[manifestPath]
|
|
232
|
-
break
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (matchedDuration !== undefined && matchedDuration > 0) measuredFiles++
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return {
|
|
239
|
-
heuristicFiles: this._testFiles.length - measuredFiles,
|
|
240
|
-
measuredFiles,
|
|
241
|
-
staleEntries: Object.keys(this._timingManifest).length - matchedManifestPaths.size
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Sorts files by weight descending, then by path for determinism.
|
|
247
|
-
* @param {SplitterFileEntry[]} files - Weighted files.
|
|
248
|
-
* @returns {SplitterFileEntry[]} - Sorted files.
|
|
249
|
-
*/
|
|
250
|
-
sortByWeightDescending(files) {
|
|
251
|
-
return [...files].sort((a, b) => {
|
|
252
|
-
if (b.weight !== a.weight) {
|
|
253
|
-
return b.weight - a.weight
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return compareTimingManifestPaths(a.filePath, b.filePath)
|
|
257
|
-
})
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Distributes files greedily into N balanced groups.
|
|
262
|
-
* Each file is assigned to the group with the least accumulated weight.
|
|
263
|
-
* @param {SplitterFileEntry[]} sortedFiles - Files sorted by weight descending.
|
|
264
|
-
* @returns {GroupBucket[]} - Array of group buckets.
|
|
265
|
-
*/
|
|
266
|
-
distributeGreedily(sortedFiles) {
|
|
267
|
-
/**
|
|
268
|
-
* Buckets.
|
|
269
|
-
* @type {GroupBucket[]} */
|
|
270
|
-
const buckets = []
|
|
271
|
-
|
|
272
|
-
for (let i = 0; i < this._groups; i++) {
|
|
273
|
-
buckets.push({totalWeight: 0, files: []})
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
for (const entry of sortedFiles) {
|
|
277
|
-
const lightest = this.findLightestBucket(buckets)
|
|
278
|
-
|
|
279
|
-
lightest.files.push(entry.filePath)
|
|
280
|
-
lightest.totalWeight += entry.weight
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return buckets
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Finds the bucket with the least accumulated weight.
|
|
288
|
-
* Ties are broken by bucket index (earlier bucket wins) for determinism.
|
|
289
|
-
* @param {GroupBucket[]} buckets - Group buckets.
|
|
290
|
-
* @returns {GroupBucket} - The lightest bucket.
|
|
291
|
-
*/
|
|
292
|
-
findLightestBucket(buckets) {
|
|
293
|
-
let lightest = buckets[0]
|
|
294
|
-
|
|
295
|
-
for (let i = 1; i < buckets.length; i++) {
|
|
296
|
-
if (buckets[i].totalWeight < lightest.totalWeight) {
|
|
297
|
-
lightest = buckets[i]
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return lightest
|
|
302
|
-
}
|
|
303
|
-
}
|
|
3
|
+
export { TestSuiteSplitter as default } from "@velocious/testing/node"
|