@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.
@@ -48,23 +48,11 @@ function testVariantsRun(testRun, variants, options = {}) {
48
48
  return tslib.__awaiter(this, void 0, void 0, function* () {
49
49
  const saveErrorVariants = options.saveErrorVariants;
50
50
  const retriesPerVariant = (_a = saveErrorVariants === null || saveErrorVariants === void 0 ? void 0 : saveErrorVariants.retriesPerVariant) !== null && _a !== void 0 ? _a : 1;
51
+ const useToFindBestError = saveErrorVariants === null || saveErrorVariants === void 0 ? void 0 : saveErrorVariants.useToFindBestError;
51
52
  const sessionDate = new Date();
52
53
  const errorVariantFilePath = saveErrorVariants
53
54
  ? path__namespace.resolve(saveErrorVariants.dir, (_c = (_b = saveErrorVariants.getFilePath) === null || _b === void 0 ? void 0 : _b.call(saveErrorVariants, { sessionDate })) !== null && _c !== void 0 ? _c : testVariants_saveErrorVariants.generateErrorVariantFilePath({ sessionDate }))
54
55
  : null;
55
- // Replay phase: run previously saved error variants before normal iteration
56
- if (saveErrorVariants) {
57
- const files = yield testVariants_saveErrorVariants.readErrorVariantFiles(saveErrorVariants.dir);
58
- for (const filePath of files) {
59
- const args = yield testVariants_saveErrorVariants.parseErrorVariantFile(filePath, saveErrorVariants.jsonToArgs);
60
- for (let retry = 0; retry < retriesPerVariant; retry++) {
61
- const promiseOrResult = testRun(args, -1, null);
62
- if (asyncUtils.isPromiseLike(promiseOrResult)) {
63
- yield promiseOrResult;
64
- }
65
- }
66
- }
67
- }
68
56
  const GC_Iterations = (_d = options.GC_Iterations) !== null && _d !== void 0 ? _d : 1000000;
69
57
  const GC_IterationsAsync = (_e = options.GC_IterationsAsync) !== null && _e !== void 0 ? _e : 10000;
70
58
  const GC_Interval = (_f = options.GC_Interval) !== null && _f !== void 0 ? _f : 1000;
@@ -72,85 +60,47 @@ function testVariantsRun(testRun, variants, options = {}) {
72
60
  const logCompleted = (_h = options.logCompleted) !== null && _h !== void 0 ? _h : true;
73
61
  const abortSignalExternal = options.abortSignal;
74
62
  const findBestError = options.findBestError;
63
+ const cycles = (_j = findBestError === null || findBestError === void 0 ? void 0 : findBestError.cycles) !== null && _j !== void 0 ? _j : 1;
75
64
  const parallel = options.parallel === true
76
65
  ? Math.pow(2, 31)
77
66
  : !options.parallel || options.parallel <= 0
78
67
  ? 1
79
68
  : options.parallel;
80
- const limitVariantsCount = (_j = options.limitVariantsCount) !== null && _j !== void 0 ? _j : null;
81
- let prevCycleVariantsCount = null;
82
- let prevCycleDuration = null;
83
- let cycleIndex = 0;
84
- let repeatIndex = 0;
85
- const startTime = Date.now();
86
- let cycleStartTime = startTime;
87
- let seed = void 0;
88
- let bestError = null;
89
- let index = -1;
90
- let args = {};
91
- let variantsIterator = variants[Symbol.iterator]();
92
- function getLimitVariantsCount() {
93
- if (limitVariantsCount != null && bestError != null) {
94
- return Math.min(limitVariantsCount, bestError.index);
95
- }
96
- if (limitVariantsCount != null) {
97
- return limitVariantsCount;
98
- }
99
- if (bestError != null) {
100
- return bestError.index;
101
- }
102
- return null;
69
+ // Apply initial limits
70
+ if (options.limitVariantsCount != null) {
71
+ variants.addLimit({ index: options.limitVariantsCount });
103
72
  }
104
- function nextVariant() {
105
- while (true) {
106
- // Try next repeat for current variant
107
- if (findBestError && index >= 0 && (bestError == null || index < bestError.index)) {
108
- repeatIndex++;
109
- if (repeatIndex < findBestError.repeatsPerVariant) {
110
- seed = findBestError.getSeed({
111
- variantIndex: index,
112
- cycleIndex,
113
- repeatIndex,
114
- totalIndex: cycleIndex * findBestError.repeatsPerVariant + repeatIndex,
115
- });
116
- return true;
73
+ // Replay phase: run previously saved error variants before normal iteration
74
+ if (saveErrorVariants) {
75
+ const files = yield testVariants_saveErrorVariants.readErrorVariantFiles(saveErrorVariants.dir);
76
+ for (const filePath of files) {
77
+ const args = yield testVariants_saveErrorVariants.parseErrorVariantFile(filePath, saveErrorVariants.jsonToArgs);
78
+ for (let retry = 0; retry < retriesPerVariant; retry++) {
79
+ try {
80
+ const promiseOrResult = testRun(args, -1, null);
81
+ if (asyncUtils.isPromiseLike(promiseOrResult)) {
82
+ yield promiseOrResult;
83
+ }
117
84
  }
118
- }
119
- repeatIndex = 0;
120
- index++;
121
- if (findBestError && cycleIndex >= findBestError.cycles) {
122
- return false;
123
- }
124
- const _limitVariantsCount = getLimitVariantsCount();
125
- if (_limitVariantsCount == null || index < _limitVariantsCount) {
126
- const result = variantsIterator.next();
127
- if (!result.done) {
128
- args = result.value;
129
- if (findBestError) {
130
- seed = findBestError.getSeed({
131
- variantIndex: index,
132
- cycleIndex,
133
- repeatIndex,
134
- totalIndex: cycleIndex * findBestError.repeatsPerVariant + repeatIndex,
135
- });
85
+ catch (error) {
86
+ if (useToFindBestError && findBestError) {
87
+ // Store as pending limit for findBestError cycle
88
+ variants.addLimit({ args, error });
89
+ break; // Exit retry loop, continue to next file
90
+ }
91
+ else {
92
+ throw error;
136
93
  }
137
- return true;
138
94
  }
139
95
  }
140
- if (!findBestError) {
141
- return false;
142
- }
143
- prevCycleVariantsCount = index;
144
- prevCycleDuration = Date.now() - cycleStartTime;
145
- cycleIndex++;
146
- cycleStartTime = Date.now();
147
- if (cycleIndex >= findBestError.cycles) {
148
- return false;
149
- }
150
- index = -1;
151
- variantsIterator = variants[Symbol.iterator]();
96
+ // If no error occurred during replays, the saved variant is no longer reproducible
97
+ // (templates may have changed) - silently skip
152
98
  }
153
99
  }
100
+ let prevCycleVariantsCount = null;
101
+ let prevCycleDuration = null;
102
+ const startTime = Date.now();
103
+ let cycleStartTime = startTime;
154
104
  const abortControllerParallel = new abortControllerFast.AbortControllerFast();
155
105
  const abortSignalParallel = asyncUtils.combineAbortSignals(abortSignalExternal, abortControllerParallel.signal);
156
106
  const abortSignalAll = abortSignalParallel;
@@ -166,67 +116,105 @@ function testVariantsRun(testRun, variants, options = {}) {
166
116
  : new timeLimits.Pool(parallel);
167
117
  function onCompleted() {
168
118
  if (logCompleted) {
169
- console.log(`[test-variants] variants: ${index}, iterations: ${iterations}, async: ${iterationsAsync}`);
119
+ console.log(`[test-variants] variants: ${variants.index}, iterations: ${iterations}, async: ${iterationsAsync}`);
170
120
  }
171
121
  }
172
- function next() {
173
- return tslib.__awaiter(this, void 0, void 0, function* () {
174
- while (!(abortSignalExternal === null || abortSignalExternal === void 0 ? void 0 : abortSignalExternal.aborted) && (debug || nextVariant())) {
175
- const _index = index;
176
- const _args = Object.assign(Object.assign({}, args), { seed });
177
- const now = (logInterval || GC_Interval) && Date.now();
178
- if (logInterval && now - prevLogTime >= logInterval) {
179
- // the log is required to prevent the karma browserNoActivityTimeout
180
- let log = '';
181
- const cycleElapsed = now - cycleStartTime;
182
- const totalElapsed = now - startTime;
183
- if (findBestError) {
184
- log += `cycle: ${cycleIndex}, variant: ${index}`;
185
- let max = getLimitVariantsCount();
186
- if (max != null) {
187
- if (prevCycleVariantsCount != null && prevCycleVariantsCount < max) {
188
- max = prevCycleVariantsCount;
189
- }
122
+ // Main iteration using iterator
123
+ variants.start();
124
+ while (variants.cycleIndex < cycles) {
125
+ let args;
126
+ while (!(abortSignalExternal === null || abortSignalExternal === void 0 ? void 0 : abortSignalExternal.aborted) && (debug || (args = variants.next()) != null)) {
127
+ const _index = variants.index;
128
+ const _args = args;
129
+ const now = (logInterval || GC_Interval) && Date.now();
130
+ if (logInterval && now - prevLogTime >= logInterval) {
131
+ // the log is required to prevent the karma browserNoActivityTimeout
132
+ let log = '';
133
+ const cycleElapsed = now - cycleStartTime;
134
+ const totalElapsed = now - startTime;
135
+ if (findBestError) {
136
+ log += `cycle: ${variants.cycleIndex}, variant: ${variants.index}`;
137
+ let max = variants.count;
138
+ if (max != null) {
139
+ if (prevCycleVariantsCount != null && prevCycleVariantsCount < max) {
140
+ max = prevCycleVariantsCount;
190
141
  }
191
- if (max != null && index > 0) {
192
- let estimatedCycleTime;
193
- if (prevCycleDuration != null && prevCycleVariantsCount != null
194
- && index < prevCycleVariantsCount && cycleElapsed < prevCycleDuration) {
195
- const adjustedDuration = prevCycleDuration - cycleElapsed;
196
- const adjustedCount = prevCycleVariantsCount - index;
197
- const speedForRemaining = adjustedDuration / adjustedCount;
198
- const remainingTime = (max - index) * speedForRemaining;
199
- estimatedCycleTime = cycleElapsed + remainingTime;
200
- }
201
- else {
202
- estimatedCycleTime = cycleElapsed * max / index;
203
- }
204
- log += `/${max} (${formatDuration(cycleElapsed)}/${formatDuration(estimatedCycleTime)})`;
142
+ }
143
+ if (max != null && variants.index > 0) {
144
+ let estimatedCycleTime;
145
+ if (prevCycleDuration != null && prevCycleVariantsCount != null
146
+ && variants.index < prevCycleVariantsCount && cycleElapsed < prevCycleDuration) {
147
+ const adjustedDuration = prevCycleDuration - cycleElapsed;
148
+ const adjustedCount = prevCycleVariantsCount - variants.index;
149
+ const speedForRemaining = adjustedDuration / adjustedCount;
150
+ const remainingTime = (max - variants.index) * speedForRemaining;
151
+ estimatedCycleTime = cycleElapsed + remainingTime;
205
152
  }
206
153
  else {
207
- log += ` (${formatDuration(cycleElapsed)})`;
154
+ estimatedCycleTime = cycleElapsed * max / variants.index;
208
155
  }
156
+ log += `/${max} (${formatDuration(cycleElapsed)}/${formatDuration(estimatedCycleTime)})`;
209
157
  }
210
158
  else {
211
- log += `variant: ${index} (${formatDuration(cycleElapsed)})`;
159
+ log += ` (${formatDuration(cycleElapsed)})`;
160
+ }
161
+ }
162
+ else {
163
+ log += `variant: ${variants.index} (${formatDuration(cycleElapsed)})`;
164
+ }
165
+ log += `, total: ${iterations} (${formatDuration(totalElapsed)})`;
166
+ console.log(log);
167
+ prevLogTime = now;
168
+ }
169
+ if (GC_Iterations && iterations - prevGC_Iterations >= GC_Iterations
170
+ || GC_IterationsAsync && iterationsAsync - prevGC_IterationsAsync >= GC_IterationsAsync
171
+ || GC_Interval && now - prevGC_Time >= GC_Interval) {
172
+ prevGC_Iterations = iterations;
173
+ prevGC_IterationsAsync = iterationsAsync;
174
+ prevGC_Time = now;
175
+ yield garbageCollect_garbageCollect.garbageCollect(1);
176
+ }
177
+ if (abortSignalExternal === null || abortSignalExternal === void 0 ? void 0 : abortSignalExternal.aborted) {
178
+ continue;
179
+ }
180
+ if (!pool || abortSignalParallel.aborted) {
181
+ try {
182
+ let promiseOrIterations = testRun(_args, _index, abortSignalParallel);
183
+ if (asyncUtils.isPromiseLike(promiseOrIterations)) {
184
+ promiseOrIterations = yield promiseOrIterations;
185
+ }
186
+ if (!promiseOrIterations) {
187
+ debug = true;
188
+ abortControllerParallel.abort();
189
+ continue;
212
190
  }
213
- log += `, total: ${iterations} (${formatDuration(totalElapsed)})`;
214
- console.log(log);
215
- prevLogTime = now;
191
+ const { iterationsAsync: _iterationsAsync, iterationsSync: _iterationsSync } = promiseOrIterations;
192
+ iterationsAsync += _iterationsAsync;
193
+ iterations += _iterationsSync + _iterationsAsync;
216
194
  }
217
- if (GC_Iterations && iterations - prevGC_Iterations >= GC_Iterations
218
- || GC_IterationsAsync && iterationsAsync - prevGC_IterationsAsync >= GC_IterationsAsync
219
- || GC_Interval && now - prevGC_Time >= GC_Interval) {
220
- prevGC_Iterations = iterations;
221
- prevGC_IterationsAsync = iterationsAsync;
222
- prevGC_Time = now;
223
- yield garbageCollect_garbageCollect.garbageCollect(1);
195
+ catch (err) {
196
+ if (errorVariantFilePath) {
197
+ yield testVariants_saveErrorVariants.saveErrorVariantFile(_args, errorVariantFilePath, saveErrorVariants.argsToJson);
198
+ }
199
+ if (findBestError) {
200
+ variants.addLimit({ error: err });
201
+ debug = false;
202
+ }
203
+ else {
204
+ throw err;
205
+ }
224
206
  }
225
- if (abortSignalExternal === null || abortSignalExternal === void 0 ? void 0 : abortSignalExternal.aborted) {
226
- continue;
207
+ }
208
+ else {
209
+ if (!pool.hold(1)) {
210
+ yield pool.holdWait(1);
227
211
  }
228
- if (!pool || abortSignalParallel.aborted) {
212
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
213
+ void (() => tslib.__awaiter(this, void 0, void 0, function* () {
229
214
  try {
215
+ if (abortSignalParallel === null || abortSignalParallel === void 0 ? void 0 : abortSignalParallel.aborted) {
216
+ return;
217
+ }
230
218
  let promiseOrIterations = testRun(_args, _index, abortSignalParallel);
231
219
  if (asyncUtils.isPromiseLike(promiseOrIterations)) {
232
220
  promiseOrIterations = yield promiseOrIterations;
@@ -234,7 +222,7 @@ function testVariantsRun(testRun, variants, options = {}) {
234
222
  if (!promiseOrIterations) {
235
223
  debug = true;
236
224
  abortControllerParallel.abort();
237
- continue;
225
+ return;
238
226
  }
239
227
  const { iterationsAsync: _iterationsAsync, iterationsSync: _iterationsSync } = promiseOrIterations;
240
228
  iterationsAsync += _iterationsAsync;
@@ -245,78 +233,44 @@ function testVariantsRun(testRun, variants, options = {}) {
245
233
  yield testVariants_saveErrorVariants.saveErrorVariantFile(_args, errorVariantFilePath, saveErrorVariants.argsToJson);
246
234
  }
247
235
  if (findBestError) {
248
- bestError = {
249
- error: err,
250
- args: _args,
251
- index: _index,
252
- };
236
+ variants.addLimit({ error: err });
253
237
  debug = false;
254
238
  }
255
239
  else {
256
240
  throw err;
257
241
  }
258
242
  }
259
- }
260
- else {
261
- if (!pool.hold(1)) {
262
- yield pool.holdWait(1);
243
+ finally {
244
+ void pool.release(1);
263
245
  }
264
- // eslint-disable-next-line @typescript-eslint/no-loop-func
265
- void (() => tslib.__awaiter(this, void 0, void 0, function* () {
266
- try {
267
- if (abortSignalParallel === null || abortSignalParallel === void 0 ? void 0 : abortSignalParallel.aborted) {
268
- return;
269
- }
270
- let promiseOrIterations = testRun(_args, _index, abortSignalParallel);
271
- if (asyncUtils.isPromiseLike(promiseOrIterations)) {
272
- promiseOrIterations = yield promiseOrIterations;
273
- }
274
- if (!promiseOrIterations) {
275
- debug = true;
276
- abortControllerParallel.abort();
277
- return;
278
- }
279
- const { iterationsAsync: _iterationsAsync, iterationsSync: _iterationsSync } = promiseOrIterations;
280
- iterationsAsync += _iterationsAsync;
281
- iterations += _iterationsSync + _iterationsAsync;
282
- }
283
- catch (err) {
284
- if (errorVariantFilePath) {
285
- yield testVariants_saveErrorVariants.saveErrorVariantFile(_args, errorVariantFilePath, saveErrorVariants.argsToJson);
286
- }
287
- if (findBestError) {
288
- bestError = {
289
- error: err,
290
- args: _args,
291
- index: _index,
292
- };
293
- debug = false;
294
- }
295
- else {
296
- throw err;
297
- }
298
- }
299
- finally {
300
- void pool.release(1);
301
- }
302
- }))();
303
- }
304
- }
305
- if (pool) {
306
- yield pool.holdWait(parallel);
307
- void pool.release(parallel);
246
+ }))();
308
247
  }
309
- if (abortSignalAll === null || abortSignalAll === void 0 ? void 0 : abortSignalAll.aborted) {
310
- throw abortSignalAll.reason;
311
- }
312
- onCompleted();
313
- yield garbageCollect_garbageCollect.garbageCollect(1);
314
- return iterations;
315
- });
248
+ }
249
+ // Track cycle metrics for logging
250
+ prevCycleVariantsCount = variants.count;
251
+ prevCycleDuration = Date.now() - cycleStartTime;
252
+ cycleStartTime = Date.now();
253
+ variants.start();
316
254
  }
317
- const result = yield next();
255
+ if (pool) {
256
+ yield pool.holdWait(parallel);
257
+ void pool.release(parallel);
258
+ }
259
+ if (abortSignalAll === null || abortSignalAll === void 0 ? void 0 : abortSignalAll.aborted) {
260
+ throw abortSignalAll.reason;
261
+ }
262
+ onCompleted();
263
+ yield garbageCollect_garbageCollect.garbageCollect(1);
264
+ // Construct bestError from iterator state
265
+ const bestError = variants.limit
266
+ ? {
267
+ error: variants.limit.error,
268
+ args: variants.limit.args,
269
+ index: variants.count,
270
+ }
271
+ : null;
318
272
  return {
319
- iterations: result,
273
+ iterations,
320
274
  bestError,
321
275
  };
322
276
  });
@@ -1,25 +1,15 @@
1
1
  import { TestVariantsTestRun } from './testVariantsCreateTestRun';
2
2
  import { type IAbortSignalFast } from '@flemist/abort-controller-fast';
3
3
  import { Obj, type SaveErrorVariantsOptions } from "./types";
4
- /** Parameters passed to getSeed function for generating test seeds */
5
- export declare type GetSeedParams = {
6
- /** Index of current variant/parameter-combination being tested */
7
- variantIndex: number;
8
- /** Index of current cycle - full pass through all variants (0..cycles-1) */
9
- cycleIndex: number;
10
- /** Index of repeat for current variant within this cycle (0..repeatsPerVariant-1) */
11
- repeatIndex: number;
12
- /** Total index across all cycles: cycleIndex × repeatsPerVariant + repeatIndex */
13
- totalIndex: number;
14
- };
4
+ import { TestVariantsIterator, type GetSeedParams } from './testVariantsIterator';
15
5
  /** Options for finding the earliest failing variant across multiple test runs */
16
6
  export declare type TestVariantsFindBestErrorOptions = {
17
- /** Function to generate seed based on current iteration state */
18
- getSeed: (params: GetSeedParams) => any;
19
7
  /** Number of full passes through all variants */
20
8
  cycles: number;
21
- /** Number of repeat tests per variant within each cycle */
22
- repeatsPerVariant: number;
9
+ /** Function to generate seed based on current iteration state (passed to iterator) */
10
+ getSeed?: null | ((params: GetSeedParams) => any);
11
+ /** Number of repeat tests per variant within each cycle (passed to iterator) */
12
+ repeatsPerVariant?: null | number;
23
13
  };
24
14
  export declare type TestVariantsRunOptions<Args extends Obj = Obj, SavedArgs = Args> = {
25
15
  /** Wait for garbage collection after iterations */
@@ -49,4 +39,4 @@ export declare type TestVariantsRunResult<Arg extends Obj> = {
49
39
  iterations: number;
50
40
  bestError: null | TestVariantsBestError<Arg>;
51
41
  };
52
- export declare function testVariantsRun<Args extends Obj, SavedArgs = Args>(testRun: TestVariantsTestRun<Args>, variants: Iterable<Args>, options?: TestVariantsRunOptions<Args, SavedArgs>): Promise<TestVariantsRunResult<Args>>;
42
+ export declare function testVariantsRun<Args extends Obj, SavedArgs = Args>(testRun: TestVariantsTestRun<Args>, variants: TestVariantsIterator<Args>, options?: TestVariantsRunOptions<Args, SavedArgs>): Promise<TestVariantsRunResult<Args>>;