@flemist/test-variants 2.0.5 → 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.
@@ -24,23 +24,11 @@ function testVariantsRun(testRun, variants, options = {}) {
24
24
  return __awaiter(this, void 0, void 0, function* () {
25
25
  const saveErrorVariants = options.saveErrorVariants;
26
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;
27
28
  const sessionDate = new Date();
28
29
  const errorVariantFilePath = saveErrorVariants
29
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 }))
30
31
  : null;
31
- // Replay phase: run previously saved error variants before normal iteration
32
- if (saveErrorVariants) {
33
- const files = yield readErrorVariantFiles(saveErrorVariants.dir);
34
- for (const filePath of files) {
35
- const args = yield parseErrorVariantFile(filePath, saveErrorVariants.jsonToArgs);
36
- for (let retry = 0; retry < retriesPerVariant; retry++) {
37
- const promiseOrResult = testRun(args, -1, null);
38
- if (isPromiseLike(promiseOrResult)) {
39
- yield promiseOrResult;
40
- }
41
- }
42
- }
43
- }
44
32
  const GC_Iterations = (_d = options.GC_Iterations) !== null && _d !== void 0 ? _d : 1000000;
45
33
  const GC_IterationsAsync = (_e = options.GC_IterationsAsync) !== null && _e !== void 0 ? _e : 10000;
46
34
  const GC_Interval = (_f = options.GC_Interval) !== null && _f !== void 0 ? _f : 1000;
@@ -48,85 +36,47 @@ function testVariantsRun(testRun, variants, options = {}) {
48
36
  const logCompleted = (_h = options.logCompleted) !== null && _h !== void 0 ? _h : true;
49
37
  const abortSignalExternal = options.abortSignal;
50
38
  const findBestError = options.findBestError;
39
+ const cycles = (_j = findBestError === null || findBestError === void 0 ? void 0 : findBestError.cycles) !== null && _j !== void 0 ? _j : 1;
51
40
  const parallel = options.parallel === true
52
41
  ? Math.pow(2, 31)
53
42
  : !options.parallel || options.parallel <= 0
54
43
  ? 1
55
44
  : options.parallel;
56
- const limitVariantsCount = (_j = options.limitVariantsCount) !== null && _j !== void 0 ? _j : null;
57
- let prevCycleVariantsCount = null;
58
- let prevCycleDuration = null;
59
- let cycleIndex = 0;
60
- let repeatIndex = 0;
61
- const startTime = Date.now();
62
- let cycleStartTime = startTime;
63
- let seed = void 0;
64
- let bestError = null;
65
- let index = -1;
66
- let args = {};
67
- let variantsIterator = variants[Symbol.iterator]();
68
- function getLimitVariantsCount() {
69
- if (limitVariantsCount != null && bestError != null) {
70
- return Math.min(limitVariantsCount, bestError.index);
71
- }
72
- if (limitVariantsCount != null) {
73
- return limitVariantsCount;
74
- }
75
- if (bestError != null) {
76
- return bestError.index;
77
- }
78
- return null;
45
+ // Apply initial limits
46
+ if (options.limitVariantsCount != null) {
47
+ variants.addLimit({ index: options.limitVariantsCount });
79
48
  }
80
- function nextVariant() {
81
- while (true) {
82
- // Try next repeat for current variant
83
- if (findBestError && index >= 0 && (bestError == null || index < bestError.index)) {
84
- repeatIndex++;
85
- if (repeatIndex < findBestError.repeatsPerVariant) {
86
- seed = findBestError.getSeed({
87
- variantIndex: index,
88
- cycleIndex,
89
- repeatIndex,
90
- totalIndex: cycleIndex * findBestError.repeatsPerVariant + repeatIndex,
91
- });
92
- 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
+ }
93
60
  }
94
- }
95
- repeatIndex = 0;
96
- index++;
97
- if (findBestError && cycleIndex >= findBestError.cycles) {
98
- return false;
99
- }
100
- const _limitVariantsCount = getLimitVariantsCount();
101
- if (_limitVariantsCount == null || index < _limitVariantsCount) {
102
- const result = variantsIterator.next();
103
- if (!result.done) {
104
- args = result.value;
105
- if (findBestError) {
106
- seed = findBestError.getSeed({
107
- variantIndex: index,
108
- cycleIndex,
109
- repeatIndex,
110
- totalIndex: cycleIndex * findBestError.repeatsPerVariant + repeatIndex,
111
- });
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;
112
69
  }
113
- return true;
114
70
  }
115
71
  }
116
- if (!findBestError) {
117
- return false;
118
- }
119
- prevCycleVariantsCount = index;
120
- prevCycleDuration = Date.now() - cycleStartTime;
121
- cycleIndex++;
122
- cycleStartTime = Date.now();
123
- if (cycleIndex >= findBestError.cycles) {
124
- return false;
125
- }
126
- index = -1;
127
- 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
128
74
  }
129
75
  }
76
+ let prevCycleVariantsCount = null;
77
+ let prevCycleDuration = null;
78
+ const startTime = Date.now();
79
+ let cycleStartTime = startTime;
130
80
  const abortControllerParallel = new AbortControllerFast();
131
81
  const abortSignalParallel = combineAbortSignals(abortSignalExternal, abortControllerParallel.signal);
132
82
  const abortSignalAll = abortSignalParallel;
@@ -142,67 +92,105 @@ function testVariantsRun(testRun, variants, options = {}) {
142
92
  : new Pool(parallel);
143
93
  function onCompleted() {
144
94
  if (logCompleted) {
145
- console.log(`[test-variants] variants: ${index}, iterations: ${iterations}, async: ${iterationsAsync}`);
95
+ console.log(`[test-variants] variants: ${variants.index}, iterations: ${iterations}, async: ${iterationsAsync}`);
146
96
  }
147
97
  }
148
- function next() {
149
- return __awaiter(this, void 0, void 0, function* () {
150
- while (!(abortSignalExternal === null || abortSignalExternal === void 0 ? void 0 : abortSignalExternal.aborted) && (debug || nextVariant())) {
151
- const _index = index;
152
- const _args = Object.assign(Object.assign({}, args), { seed });
153
- const now = (logInterval || GC_Interval) && Date.now();
154
- if (logInterval && now - prevLogTime >= logInterval) {
155
- // the log is required to prevent the karma browserNoActivityTimeout
156
- let log = '';
157
- const cycleElapsed = now - cycleStartTime;
158
- const totalElapsed = now - startTime;
159
- if (findBestError) {
160
- log += `cycle: ${cycleIndex}, variant: ${index}`;
161
- let max = getLimitVariantsCount();
162
- if (max != null) {
163
- if (prevCycleVariantsCount != null && prevCycleVariantsCount < max) {
164
- max = prevCycleVariantsCount;
165
- }
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;
166
117
  }
167
- if (max != null && index > 0) {
168
- let estimatedCycleTime;
169
- if (prevCycleDuration != null && prevCycleVariantsCount != null
170
- && index < prevCycleVariantsCount && cycleElapsed < prevCycleDuration) {
171
- const adjustedDuration = prevCycleDuration - cycleElapsed;
172
- const adjustedCount = prevCycleVariantsCount - index;
173
- const speedForRemaining = adjustedDuration / adjustedCount;
174
- const remainingTime = (max - index) * speedForRemaining;
175
- estimatedCycleTime = cycleElapsed + remainingTime;
176
- }
177
- else {
178
- estimatedCycleTime = cycleElapsed * max / index;
179
- }
180
- log += `/${max} (${formatDuration(cycleElapsed)}/${formatDuration(estimatedCycleTime)})`;
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;
181
128
  }
182
129
  else {
183
- log += ` (${formatDuration(cycleElapsed)})`;
130
+ estimatedCycleTime = cycleElapsed * max / variants.index;
184
131
  }
132
+ log += `/${max} (${formatDuration(cycleElapsed)}/${formatDuration(estimatedCycleTime)})`;
185
133
  }
186
134
  else {
187
- log += `variant: ${index} (${formatDuration(cycleElapsed)})`;
135
+ log += ` (${formatDuration(cycleElapsed)})`;
136
+ }
137
+ }
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;
188
166
  }
189
- log += `, total: ${iterations} (${formatDuration(totalElapsed)})`;
190
- console.log(log);
191
- prevLogTime = now;
167
+ const { iterationsAsync: _iterationsAsync, iterationsSync: _iterationsSync } = promiseOrIterations;
168
+ iterationsAsync += _iterationsAsync;
169
+ iterations += _iterationsSync + _iterationsAsync;
192
170
  }
193
- if (GC_Iterations && iterations - prevGC_Iterations >= GC_Iterations
194
- || GC_IterationsAsync && iterationsAsync - prevGC_IterationsAsync >= GC_IterationsAsync
195
- || GC_Interval && now - prevGC_Time >= GC_Interval) {
196
- prevGC_Iterations = iterations;
197
- prevGC_IterationsAsync = iterationsAsync;
198
- prevGC_Time = now;
199
- yield garbageCollect(1);
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
+ }
200
182
  }
201
- if (abortSignalExternal === null || abortSignalExternal === void 0 ? void 0 : abortSignalExternal.aborted) {
202
- continue;
183
+ }
184
+ else {
185
+ if (!pool.hold(1)) {
186
+ yield pool.holdWait(1);
203
187
  }
204
- if (!pool || abortSignalParallel.aborted) {
188
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
189
+ void (() => __awaiter(this, void 0, void 0, function* () {
205
190
  try {
191
+ if (abortSignalParallel === null || abortSignalParallel === void 0 ? void 0 : abortSignalParallel.aborted) {
192
+ return;
193
+ }
206
194
  let promiseOrIterations = testRun(_args, _index, abortSignalParallel);
207
195
  if (isPromiseLike(promiseOrIterations)) {
208
196
  promiseOrIterations = yield promiseOrIterations;
@@ -210,7 +198,7 @@ function testVariantsRun(testRun, variants, options = {}) {
210
198
  if (!promiseOrIterations) {
211
199
  debug = true;
212
200
  abortControllerParallel.abort();
213
- continue;
201
+ return;
214
202
  }
215
203
  const { iterationsAsync: _iterationsAsync, iterationsSync: _iterationsSync } = promiseOrIterations;
216
204
  iterationsAsync += _iterationsAsync;
@@ -221,78 +209,44 @@ function testVariantsRun(testRun, variants, options = {}) {
221
209
  yield saveErrorVariantFile(_args, errorVariantFilePath, saveErrorVariants.argsToJson);
222
210
  }
223
211
  if (findBestError) {
224
- bestError = {
225
- error: err,
226
- args: _args,
227
- index: _index,
228
- };
212
+ variants.addLimit({ error: err });
229
213
  debug = false;
230
214
  }
231
215
  else {
232
216
  throw err;
233
217
  }
234
218
  }
235
- }
236
- else {
237
- if (!pool.hold(1)) {
238
- yield pool.holdWait(1);
219
+ finally {
220
+ void pool.release(1);
239
221
  }
240
- // eslint-disable-next-line @typescript-eslint/no-loop-func
241
- void (() => __awaiter(this, void 0, void 0, function* () {
242
- try {
243
- if (abortSignalParallel === null || abortSignalParallel === void 0 ? void 0 : abortSignalParallel.aborted) {
244
- return;
245
- }
246
- let promiseOrIterations = testRun(_args, _index, abortSignalParallel);
247
- if (isPromiseLike(promiseOrIterations)) {
248
- promiseOrIterations = yield promiseOrIterations;
249
- }
250
- if (!promiseOrIterations) {
251
- debug = true;
252
- abortControllerParallel.abort();
253
- return;
254
- }
255
- const { iterationsAsync: _iterationsAsync, iterationsSync: _iterationsSync } = promiseOrIterations;
256
- iterationsAsync += _iterationsAsync;
257
- iterations += _iterationsSync + _iterationsAsync;
258
- }
259
- catch (err) {
260
- if (errorVariantFilePath) {
261
- yield saveErrorVariantFile(_args, errorVariantFilePath, saveErrorVariants.argsToJson);
262
- }
263
- if (findBestError) {
264
- bestError = {
265
- error: err,
266
- args: _args,
267
- index: _index,
268
- };
269
- debug = false;
270
- }
271
- else {
272
- throw err;
273
- }
274
- }
275
- finally {
276
- void pool.release(1);
277
- }
278
- }))();
279
- }
280
- }
281
- if (pool) {
282
- yield pool.holdWait(parallel);
283
- void pool.release(parallel);
222
+ }))();
284
223
  }
285
- if (abortSignalAll === null || abortSignalAll === void 0 ? void 0 : abortSignalAll.aborted) {
286
- throw abortSignalAll.reason;
287
- }
288
- onCompleted();
289
- yield garbageCollect(1);
290
- return iterations;
291
- });
224
+ }
225
+ // Track cycle metrics for logging
226
+ prevCycleVariantsCount = variants.count;
227
+ prevCycleDuration = Date.now() - cycleStartTime;
228
+ cycleStartTime = Date.now();
229
+ variants.start();
292
230
  }
293
- const result = yield next();
231
+ if (pool) {
232
+ yield pool.holdWait(parallel);
233
+ void pool.release(parallel);
234
+ }
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;
294
248
  return {
295
- iterations: result,
249
+ iterations,
296
250
  bestError,
297
251
  };
298
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flemist/test-variants",
3
- "version": "2.0.5",
3
+ "version": "3.0.0",
4
4
  "description": "Runs a test function with all possible combinations of its parameters.",
5
5
  "main": "dist/lib/index.cjs",
6
6
  "module": "dist/lib/index.mjs",