@flemist/test-variants 2.0.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle/browser.js +599 -517
- package/dist/lib/index.cjs +2 -1
- package/dist/lib/index.d.ts +2 -1
- package/dist/lib/index.mjs +1 -1
- package/dist/lib/test-variants/createTestVariants.cjs +5 -2
- package/dist/lib/test-variants/createTestVariants.mjs +5 -2
- package/dist/lib/test-variants/createTestVariants.perf.cjs +1 -1
- package/dist/lib/test-variants/createTestVariants.perf.mjs +1 -1
- package/dist/lib/test-variants/testVariantsIterable.cjs +3 -0
- package/dist/lib/test-variants/testVariantsIterable.d.ts +3 -0
- package/dist/lib/test-variants/testVariantsIterable.mjs +3 -0
- package/dist/lib/test-variants/testVariantsIterator.cjs +357 -0
- package/dist/lib/test-variants/testVariantsIterator.d.ts +67 -0
- package/dist/lib/test-variants/testVariantsIterator.mjs +353 -0
- package/dist/lib/test-variants/testVariantsRun.cjs +160 -172
- package/dist/lib/test-variants/testVariantsRun.d.ts +6 -16
- package/dist/lib/test-variants/testVariantsRun.mjs +160 -172
- package/dist/lib/test-variants/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -7,28 +7,28 @@ import { generateErrorVariantFilePath, readErrorVariantFiles, parseErrorVariantF
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import 'fs';
|
|
9
9
|
|
|
10
|
+
function formatDuration(ms) {
|
|
11
|
+
const seconds = ms / 1000;
|
|
12
|
+
if (seconds < 60) {
|
|
13
|
+
return `${seconds.toFixed(1)}s`;
|
|
14
|
+
}
|
|
15
|
+
const minutes = seconds / 60;
|
|
16
|
+
if (minutes < 60) {
|
|
17
|
+
return `${minutes.toFixed(1)}m`;
|
|
18
|
+
}
|
|
19
|
+
const hours = minutes / 60;
|
|
20
|
+
return `${hours.toFixed(1)}h`;
|
|
21
|
+
}
|
|
10
22
|
function testVariantsRun(testRun, variants, options = {}) {
|
|
11
23
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
12
24
|
return __awaiter(this, void 0, void 0, function* () {
|
|
13
25
|
const saveErrorVariants = options.saveErrorVariants;
|
|
14
26
|
const retriesPerVariant = (_a = saveErrorVariants === null || saveErrorVariants === void 0 ? void 0 : saveErrorVariants.retriesPerVariant) !== null && _a !== void 0 ? _a : 1;
|
|
27
|
+
const useToFindBestError = saveErrorVariants === null || saveErrorVariants === void 0 ? void 0 : saveErrorVariants.useToFindBestError;
|
|
15
28
|
const sessionDate = new Date();
|
|
16
29
|
const errorVariantFilePath = saveErrorVariants
|
|
17
30
|
? path.resolve(saveErrorVariants.dir, (_c = (_b = saveErrorVariants.getFilePath) === null || _b === void 0 ? void 0 : _b.call(saveErrorVariants, { sessionDate })) !== null && _c !== void 0 ? _c : generateErrorVariantFilePath({ sessionDate }))
|
|
18
31
|
: null;
|
|
19
|
-
// Replay phase: run previously saved error variants before normal iteration
|
|
20
|
-
if (saveErrorVariants) {
|
|
21
|
-
const files = yield readErrorVariantFiles(saveErrorVariants.dir);
|
|
22
|
-
for (const filePath of files) {
|
|
23
|
-
const args = yield parseErrorVariantFile(filePath, saveErrorVariants.jsonToArgs);
|
|
24
|
-
for (let retry = 0; retry < retriesPerVariant; retry++) {
|
|
25
|
-
const promiseOrResult = testRun(args, -1, null);
|
|
26
|
-
if (isPromiseLike(promiseOrResult)) {
|
|
27
|
-
yield promiseOrResult;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
32
|
const GC_Iterations = (_d = options.GC_Iterations) !== null && _d !== void 0 ? _d : 1000000;
|
|
33
33
|
const GC_IterationsAsync = (_e = options.GC_IterationsAsync) !== null && _e !== void 0 ? _e : 10000;
|
|
34
34
|
const GC_Interval = (_f = options.GC_Interval) !== null && _f !== void 0 ? _f : 1000;
|
|
@@ -36,80 +36,47 @@ function testVariantsRun(testRun, variants, options = {}) {
|
|
|
36
36
|
const logCompleted = (_h = options.logCompleted) !== null && _h !== void 0 ? _h : true;
|
|
37
37
|
const abortSignalExternal = options.abortSignal;
|
|
38
38
|
const findBestError = options.findBestError;
|
|
39
|
+
const cycles = (_j = findBestError === null || findBestError === void 0 ? void 0 : findBestError.cycles) !== null && _j !== void 0 ? _j : 1;
|
|
39
40
|
const parallel = options.parallel === true
|
|
40
41
|
? Math.pow(2, 31)
|
|
41
42
|
: !options.parallel || options.parallel <= 0
|
|
42
43
|
? 1
|
|
43
44
|
: options.parallel;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let repeatIndex = 0;
|
|
48
|
-
let seed = void 0;
|
|
49
|
-
let bestError = null;
|
|
50
|
-
let index = -1;
|
|
51
|
-
let args = {};
|
|
52
|
-
let variantsIterator = variants[Symbol.iterator]();
|
|
53
|
-
function getLimitVariantsCount() {
|
|
54
|
-
if (limitVariantsCount != null && bestError != null) {
|
|
55
|
-
return Math.min(limitVariantsCount, bestError.index);
|
|
56
|
-
}
|
|
57
|
-
if (limitVariantsCount != null) {
|
|
58
|
-
return limitVariantsCount;
|
|
59
|
-
}
|
|
60
|
-
if (bestError != null) {
|
|
61
|
-
return bestError.index;
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
45
|
+
// Apply initial limits
|
|
46
|
+
if (options.limitVariantsCount != null) {
|
|
47
|
+
variants.addLimit({ index: options.limitVariantsCount });
|
|
64
48
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
return true;
|
|
49
|
+
// Replay phase: run previously saved error variants before normal iteration
|
|
50
|
+
if (saveErrorVariants) {
|
|
51
|
+
const files = yield readErrorVariantFiles(saveErrorVariants.dir);
|
|
52
|
+
for (const filePath of files) {
|
|
53
|
+
const args = yield parseErrorVariantFile(filePath, saveErrorVariants.jsonToArgs);
|
|
54
|
+
for (let retry = 0; retry < retriesPerVariant; retry++) {
|
|
55
|
+
try {
|
|
56
|
+
const promiseOrResult = testRun(args, -1, null);
|
|
57
|
+
if (isPromiseLike(promiseOrResult)) {
|
|
58
|
+
yield promiseOrResult;
|
|
59
|
+
}
|
|
78
60
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const result = variantsIterator.next();
|
|
88
|
-
if (!result.done) {
|
|
89
|
-
args = result.value;
|
|
90
|
-
if (findBestError) {
|
|
91
|
-
seed = findBestError.getSeed({
|
|
92
|
-
variantIndex: index,
|
|
93
|
-
cycleIndex,
|
|
94
|
-
repeatIndex,
|
|
95
|
-
totalIndex: cycleIndex * findBestError.repeatsPerVariant + repeatIndex,
|
|
96
|
-
});
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (useToFindBestError && findBestError) {
|
|
63
|
+
// Store as pending limit for findBestError cycle
|
|
64
|
+
variants.addLimit({ args, error });
|
|
65
|
+
break; // Exit retry loop, continue to next file
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
throw error;
|
|
97
69
|
}
|
|
98
|
-
return true;
|
|
99
70
|
}
|
|
100
71
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
prevCycleVariantsCount = index;
|
|
105
|
-
cycleIndex++;
|
|
106
|
-
if (cycleIndex >= findBestError.cycles) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
index = -1;
|
|
110
|
-
variantsIterator = variants[Symbol.iterator]();
|
|
72
|
+
// If no error occurred during replays, the saved variant is no longer reproducible
|
|
73
|
+
// (templates may have changed) - silently skip
|
|
111
74
|
}
|
|
112
75
|
}
|
|
76
|
+
let prevCycleVariantsCount = null;
|
|
77
|
+
let prevCycleDuration = null;
|
|
78
|
+
const startTime = Date.now();
|
|
79
|
+
let cycleStartTime = startTime;
|
|
113
80
|
const abortControllerParallel = new AbortControllerFast();
|
|
114
81
|
const abortSignalParallel = combineAbortSignals(abortSignalExternal, abortControllerParallel.signal);
|
|
115
82
|
const abortSignalAll = abortSignalParallel;
|
|
@@ -125,50 +92,105 @@ function testVariantsRun(testRun, variants, options = {}) {
|
|
|
125
92
|
: new Pool(parallel);
|
|
126
93
|
function onCompleted() {
|
|
127
94
|
if (logCompleted) {
|
|
128
|
-
console.log(`[test-variants] variants: ${index}, iterations: ${iterations}, async: ${iterationsAsync}`);
|
|
95
|
+
console.log(`[test-variants] variants: ${variants.index}, iterations: ${iterations}, async: ${iterationsAsync}`);
|
|
129
96
|
}
|
|
130
97
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
98
|
+
// Main iteration using iterator
|
|
99
|
+
variants.start();
|
|
100
|
+
while (variants.cycleIndex < cycles) {
|
|
101
|
+
let args;
|
|
102
|
+
while (!(abortSignalExternal === null || abortSignalExternal === void 0 ? void 0 : abortSignalExternal.aborted) && (debug || (args = variants.next()) != null)) {
|
|
103
|
+
const _index = variants.index;
|
|
104
|
+
const _args = args;
|
|
105
|
+
const now = (logInterval || GC_Interval) && Date.now();
|
|
106
|
+
if (logInterval && now - prevLogTime >= logInterval) {
|
|
107
|
+
// the log is required to prevent the karma browserNoActivityTimeout
|
|
108
|
+
let log = '';
|
|
109
|
+
const cycleElapsed = now - cycleStartTime;
|
|
110
|
+
const totalElapsed = now - startTime;
|
|
111
|
+
if (findBestError) {
|
|
112
|
+
log += `cycle: ${variants.cycleIndex}, variant: ${variants.index}`;
|
|
113
|
+
let max = variants.count;
|
|
114
|
+
if (max != null) {
|
|
115
|
+
if (prevCycleVariantsCount != null && prevCycleVariantsCount < max) {
|
|
116
|
+
max = prevCycleVariantsCount;
|
|
150
117
|
}
|
|
151
118
|
}
|
|
119
|
+
if (max != null && variants.index > 0) {
|
|
120
|
+
let estimatedCycleTime;
|
|
121
|
+
if (prevCycleDuration != null && prevCycleVariantsCount != null
|
|
122
|
+
&& variants.index < prevCycleVariantsCount && cycleElapsed < prevCycleDuration) {
|
|
123
|
+
const adjustedDuration = prevCycleDuration - cycleElapsed;
|
|
124
|
+
const adjustedCount = prevCycleVariantsCount - variants.index;
|
|
125
|
+
const speedForRemaining = adjustedDuration / adjustedCount;
|
|
126
|
+
const remainingTime = (max - variants.index) * speedForRemaining;
|
|
127
|
+
estimatedCycleTime = cycleElapsed + remainingTime;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
estimatedCycleTime = cycleElapsed * max / variants.index;
|
|
131
|
+
}
|
|
132
|
+
log += `/${max} (${formatDuration(cycleElapsed)}/${formatDuration(estimatedCycleTime)})`;
|
|
133
|
+
}
|
|
152
134
|
else {
|
|
153
|
-
log += `
|
|
135
|
+
log += ` (${formatDuration(cycleElapsed)})`;
|
|
154
136
|
}
|
|
155
|
-
log += `, total: ${iterations}`;
|
|
156
|
-
console.log(log);
|
|
157
|
-
prevLogTime = now;
|
|
158
137
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
138
|
+
else {
|
|
139
|
+
log += `variant: ${variants.index} (${formatDuration(cycleElapsed)})`;
|
|
140
|
+
}
|
|
141
|
+
log += `, total: ${iterations} (${formatDuration(totalElapsed)})`;
|
|
142
|
+
console.log(log);
|
|
143
|
+
prevLogTime = now;
|
|
144
|
+
}
|
|
145
|
+
if (GC_Iterations && iterations - prevGC_Iterations >= GC_Iterations
|
|
146
|
+
|| GC_IterationsAsync && iterationsAsync - prevGC_IterationsAsync >= GC_IterationsAsync
|
|
147
|
+
|| GC_Interval && now - prevGC_Time >= GC_Interval) {
|
|
148
|
+
prevGC_Iterations = iterations;
|
|
149
|
+
prevGC_IterationsAsync = iterationsAsync;
|
|
150
|
+
prevGC_Time = now;
|
|
151
|
+
yield garbageCollect(1);
|
|
152
|
+
}
|
|
153
|
+
if (abortSignalExternal === null || abortSignalExternal === void 0 ? void 0 : abortSignalExternal.aborted) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!pool || abortSignalParallel.aborted) {
|
|
157
|
+
try {
|
|
158
|
+
let promiseOrIterations = testRun(_args, _index, abortSignalParallel);
|
|
159
|
+
if (isPromiseLike(promiseOrIterations)) {
|
|
160
|
+
promiseOrIterations = yield promiseOrIterations;
|
|
161
|
+
}
|
|
162
|
+
if (!promiseOrIterations) {
|
|
163
|
+
debug = true;
|
|
164
|
+
abortControllerParallel.abort();
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const { iterationsAsync: _iterationsAsync, iterationsSync: _iterationsSync } = promiseOrIterations;
|
|
168
|
+
iterationsAsync += _iterationsAsync;
|
|
169
|
+
iterations += _iterationsSync + _iterationsAsync;
|
|
166
170
|
}
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
catch (err) {
|
|
172
|
+
if (errorVariantFilePath) {
|
|
173
|
+
yield saveErrorVariantFile(_args, errorVariantFilePath, saveErrorVariants.argsToJson);
|
|
174
|
+
}
|
|
175
|
+
if (findBestError) {
|
|
176
|
+
variants.addLimit({ error: err });
|
|
177
|
+
debug = false;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
169
182
|
}
|
|
170
|
-
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
if (!pool.hold(1)) {
|
|
186
|
+
yield pool.holdWait(1);
|
|
187
|
+
}
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
189
|
+
void (() => __awaiter(this, void 0, void 0, function* () {
|
|
171
190
|
try {
|
|
191
|
+
if (abortSignalParallel === null || abortSignalParallel === void 0 ? void 0 : abortSignalParallel.aborted) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
172
194
|
let promiseOrIterations = testRun(_args, _index, abortSignalParallel);
|
|
173
195
|
if (isPromiseLike(promiseOrIterations)) {
|
|
174
196
|
promiseOrIterations = yield promiseOrIterations;
|
|
@@ -176,7 +198,7 @@ function testVariantsRun(testRun, variants, options = {}) {
|
|
|
176
198
|
if (!promiseOrIterations) {
|
|
177
199
|
debug = true;
|
|
178
200
|
abortControllerParallel.abort();
|
|
179
|
-
|
|
201
|
+
return;
|
|
180
202
|
}
|
|
181
203
|
const { iterationsAsync: _iterationsAsync, iterationsSync: _iterationsSync } = promiseOrIterations;
|
|
182
204
|
iterationsAsync += _iterationsAsync;
|
|
@@ -187,78 +209,44 @@ function testVariantsRun(testRun, variants, options = {}) {
|
|
|
187
209
|
yield saveErrorVariantFile(_args, errorVariantFilePath, saveErrorVariants.argsToJson);
|
|
188
210
|
}
|
|
189
211
|
if (findBestError) {
|
|
190
|
-
|
|
191
|
-
error: err,
|
|
192
|
-
args: _args,
|
|
193
|
-
index: _index,
|
|
194
|
-
};
|
|
212
|
+
variants.addLimit({ error: err });
|
|
195
213
|
debug = false;
|
|
196
214
|
}
|
|
197
215
|
else {
|
|
198
216
|
throw err;
|
|
199
217
|
}
|
|
200
218
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (!pool.hold(1)) {
|
|
204
|
-
yield pool.holdWait(1);
|
|
219
|
+
finally {
|
|
220
|
+
void pool.release(1);
|
|
205
221
|
}
|
|
206
|
-
|
|
207
|
-
void (() => __awaiter(this, void 0, void 0, function* () {
|
|
208
|
-
try {
|
|
209
|
-
if (abortSignalParallel === null || abortSignalParallel === void 0 ? void 0 : abortSignalParallel.aborted) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
let promiseOrIterations = testRun(_args, _index, abortSignalParallel);
|
|
213
|
-
if (isPromiseLike(promiseOrIterations)) {
|
|
214
|
-
promiseOrIterations = yield promiseOrIterations;
|
|
215
|
-
}
|
|
216
|
-
if (!promiseOrIterations) {
|
|
217
|
-
debug = true;
|
|
218
|
-
abortControllerParallel.abort();
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
const { iterationsAsync: _iterationsAsync, iterationsSync: _iterationsSync } = promiseOrIterations;
|
|
222
|
-
iterationsAsync += _iterationsAsync;
|
|
223
|
-
iterations += _iterationsSync + _iterationsAsync;
|
|
224
|
-
}
|
|
225
|
-
catch (err) {
|
|
226
|
-
if (errorVariantFilePath) {
|
|
227
|
-
yield saveErrorVariantFile(_args, errorVariantFilePath, saveErrorVariants.argsToJson);
|
|
228
|
-
}
|
|
229
|
-
if (findBestError) {
|
|
230
|
-
bestError = {
|
|
231
|
-
error: err,
|
|
232
|
-
args: _args,
|
|
233
|
-
index: _index,
|
|
234
|
-
};
|
|
235
|
-
debug = false;
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
throw err;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
finally {
|
|
242
|
-
void pool.release(1);
|
|
243
|
-
}
|
|
244
|
-
}))();
|
|
245
|
-
}
|
|
222
|
+
}))();
|
|
246
223
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
});
|
|
224
|
+
}
|
|
225
|
+
// Track cycle metrics for logging
|
|
226
|
+
prevCycleVariantsCount = variants.count;
|
|
227
|
+
prevCycleDuration = Date.now() - cycleStartTime;
|
|
228
|
+
cycleStartTime = Date.now();
|
|
229
|
+
variants.start();
|
|
230
|
+
}
|
|
231
|
+
if (pool) {
|
|
232
|
+
yield pool.holdWait(parallel);
|
|
233
|
+
void pool.release(parallel);
|
|
258
234
|
}
|
|
259
|
-
|
|
235
|
+
if (abortSignalAll === null || abortSignalAll === void 0 ? void 0 : abortSignalAll.aborted) {
|
|
236
|
+
throw abortSignalAll.reason;
|
|
237
|
+
}
|
|
238
|
+
onCompleted();
|
|
239
|
+
yield garbageCollect(1);
|
|
240
|
+
// Construct bestError from iterator state
|
|
241
|
+
const bestError = variants.limit
|
|
242
|
+
? {
|
|
243
|
+
error: variants.limit.error,
|
|
244
|
+
args: variants.limit.args,
|
|
245
|
+
index: variants.count,
|
|
246
|
+
}
|
|
247
|
+
: null;
|
|
260
248
|
return {
|
|
261
|
-
iterations
|
|
249
|
+
iterations,
|
|
262
250
|
bestError,
|
|
263
251
|
};
|
|
264
252
|
});
|
|
@@ -15,4 +15,6 @@ export declare type SaveErrorVariantsOptions<Args, SavedArgs = Args> = {
|
|
|
15
15
|
argsToJson?: null | ((args: Args) => string | SavedArgs);
|
|
16
16
|
/** Transform parsed JSON back to args */
|
|
17
17
|
jsonToArgs?: null | ((json: SavedArgs) => Args);
|
|
18
|
+
/** Use saved errors to set findBestError limits instead of throwing on replay */
|
|
19
|
+
useToFindBestError?: null | boolean;
|
|
18
20
|
};
|