@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
package/dist/lib/index.cjs
CHANGED
|
@@ -4,10 +4,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var testVariants_saveErrorVariants = require('./test-variants/saveErrorVariants.cjs');
|
|
6
6
|
var testVariants_createTestVariants = require('./test-variants/createTestVariants.cjs');
|
|
7
|
+
var testVariants_testVariantsIterator = require('./test-variants/testVariantsIterator.cjs');
|
|
7
8
|
require('tslib');
|
|
8
9
|
require('fs');
|
|
9
10
|
require('path');
|
|
10
|
-
require('./test-variants/testVariantsIterable.cjs');
|
|
11
11
|
require('./test-variants/testVariantsCreateTestRun.cjs');
|
|
12
12
|
require('@flemist/async-utils');
|
|
13
13
|
require('./test-variants/argsToString.cjs');
|
|
@@ -20,3 +20,4 @@ require('./garbage-collect/garbageCollect.cjs');
|
|
|
20
20
|
|
|
21
21
|
exports.generateErrorVariantFilePath = testVariants_saveErrorVariants.generateErrorVariantFilePath;
|
|
22
22
|
exports.createTestVariants = testVariants_createTestVariants.createTestVariants;
|
|
23
|
+
exports.testVariantsIterator = testVariants_testVariantsIterator.testVariantsIterator;
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export { type Obj, type GenerateErrorVariantFilePathOptions, type SaveErrorVaria
|
|
|
2
2
|
export { generateErrorVariantFilePath, } from "./test-variants/saveErrorVariants";
|
|
3
3
|
export { type TestVariantsTemplate, type TestVariantsTemplates, type TestVariantsTemplatesExt, } from "./test-variants/testVariantsIterable";
|
|
4
4
|
export { type ErrorEvent, type OnErrorCallback, type TestVariantsTest, type TestVariantsTestResult, type TestVariantsCreateTestRunOptions, type TestVariantsTestRun, type TestVariantsTestRunResult, } from "./test-variants/testVariantsCreateTestRun";
|
|
5
|
-
export { type
|
|
5
|
+
export { type TestVariantsFindBestErrorOptions, type TestVariantsRunOptions, type TestVariantsBestError, type TestVariantsRunResult, } from "./test-variants/testVariantsRun";
|
|
6
6
|
export { type TestVariantsSetArgs, type TestVariantsCall, createTestVariants, } from './test-variants/createTestVariants';
|
|
7
|
+
export { type GetSeedParams, type LimitArgOnErrorOptions, type LimitArgOnError, type TestVariantsIteratorOptions, type TestVariantsIteratorLimit, type AddLimitOptions, type TestVariantsIterator, testVariantsIterator, } from "./test-variants/testVariantsIterator";
|
package/dist/lib/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { generateErrorVariantFilePath } from './test-variants/saveErrorVariants.mjs';
|
|
2
2
|
export { createTestVariants } from './test-variants/createTestVariants.mjs';
|
|
3
|
+
export { testVariantsIterator } from './test-variants/testVariantsIterator.mjs';
|
|
3
4
|
import 'tslib';
|
|
4
5
|
import 'fs';
|
|
5
6
|
import 'path';
|
|
6
|
-
import './test-variants/testVariantsIterable.mjs';
|
|
7
7
|
import './test-variants/testVariantsCreateTestRun.mjs';
|
|
8
8
|
import '@flemist/async-utils';
|
|
9
9
|
import './test-variants/argsToString.mjs';
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var tslib = require('tslib');
|
|
6
|
-
var testVariants_testVariantsIterable = require('./testVariantsIterable.cjs');
|
|
7
6
|
var testVariants_testVariantsCreateTestRun = require('./testVariantsCreateTestRun.cjs');
|
|
8
7
|
var testVariants_testVariantsRun = require('./testVariantsRun.cjs');
|
|
8
|
+
var testVariants_testVariantsIterator = require('./testVariantsIterator.cjs');
|
|
9
9
|
require('@flemist/async-utils');
|
|
10
10
|
require('./argsToString.cjs');
|
|
11
11
|
require('@flemist/abort-controller-fast');
|
|
@@ -18,12 +18,15 @@ require('path');
|
|
|
18
18
|
function createTestVariants(test) {
|
|
19
19
|
return function testVariantsArgs(args) {
|
|
20
20
|
return function testVariantsCall(options) {
|
|
21
|
+
var _a, _b;
|
|
21
22
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
22
23
|
const testRun = testVariants_testVariantsCreateTestRun.testVariantsCreateTestRun(test, {
|
|
23
24
|
onError: options === null || options === void 0 ? void 0 : options.onError,
|
|
24
25
|
});
|
|
25
|
-
const variants =
|
|
26
|
+
const variants = testVariants_testVariantsIterator.testVariantsIterator({
|
|
26
27
|
argsTemplates: args,
|
|
28
|
+
getSeed: (_a = options === null || options === void 0 ? void 0 : options.findBestError) === null || _a === void 0 ? void 0 : _a.getSeed,
|
|
29
|
+
repeatsPerVariant: (_b = options === null || options === void 0 ? void 0 : options.findBestError) === null || _b === void 0 ? void 0 : _b.repeatsPerVariant,
|
|
27
30
|
});
|
|
28
31
|
return testVariants_testVariantsRun.testVariantsRun(testRun, variants, options);
|
|
29
32
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
|
-
import { testVariantsIterable } from './testVariantsIterable.mjs';
|
|
3
2
|
import { testVariantsCreateTestRun } from './testVariantsCreateTestRun.mjs';
|
|
4
3
|
import { testVariantsRun } from './testVariantsRun.mjs';
|
|
4
|
+
import { testVariantsIterator } from './testVariantsIterator.mjs';
|
|
5
5
|
import '@flemist/async-utils';
|
|
6
6
|
import './argsToString.mjs';
|
|
7
7
|
import '@flemist/abort-controller-fast';
|
|
@@ -14,12 +14,15 @@ import 'path';
|
|
|
14
14
|
function createTestVariants(test) {
|
|
15
15
|
return function testVariantsArgs(args) {
|
|
16
16
|
return function testVariantsCall(options) {
|
|
17
|
+
var _a, _b;
|
|
17
18
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18
19
|
const testRun = testVariantsCreateTestRun(test, {
|
|
19
20
|
onError: options === null || options === void 0 ? void 0 : options.onError,
|
|
20
21
|
});
|
|
21
|
-
const variants =
|
|
22
|
+
const variants = testVariantsIterator({
|
|
22
23
|
argsTemplates: args,
|
|
24
|
+
getSeed: (_a = options === null || options === void 0 ? void 0 : options.findBestError) === null || _a === void 0 ? void 0 : _a.getSeed,
|
|
25
|
+
repeatsPerVariant: (_b = options === null || options === void 0 ? void 0 : options.findBestError) === null || _b === void 0 ? void 0 : _b.repeatsPerVariant,
|
|
23
26
|
});
|
|
24
27
|
return testVariantsRun(testRun, variants, options);
|
|
25
28
|
});
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
var tslib = require('tslib');
|
|
4
4
|
var node = require('rdtsc/node');
|
|
5
5
|
var testVariants_createTestVariants = require('./createTestVariants.cjs');
|
|
6
|
-
require('./testVariantsIterable.cjs');
|
|
7
6
|
require('./testVariantsCreateTestRun.cjs');
|
|
8
7
|
require('@flemist/async-utils');
|
|
9
8
|
require('./argsToString.cjs');
|
|
@@ -14,6 +13,7 @@ require('../garbage-collect/garbageCollect.cjs');
|
|
|
14
13
|
require('./saveErrorVariants.cjs');
|
|
15
14
|
require('fs');
|
|
16
15
|
require('path');
|
|
16
|
+
require('./testVariantsIterator.cjs');
|
|
17
17
|
|
|
18
18
|
describe('test > testVariants perf', function () {
|
|
19
19
|
this.timeout(300000);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
2
|
import { calcPerformance } from 'rdtsc/node';
|
|
3
3
|
import { createTestVariants } from './createTestVariants.mjs';
|
|
4
|
-
import './testVariantsIterable.mjs';
|
|
5
4
|
import './testVariantsCreateTestRun.mjs';
|
|
6
5
|
import '@flemist/async-utils';
|
|
7
6
|
import './argsToString.mjs';
|
|
@@ -12,6 +11,7 @@ import '../garbage-collect/garbageCollect.mjs';
|
|
|
12
11
|
import './saveErrorVariants.mjs';
|
|
13
12
|
import 'fs';
|
|
14
13
|
import 'path';
|
|
14
|
+
import './testVariantsIterator.mjs';
|
|
15
15
|
|
|
16
16
|
describe('test > testVariants perf', function () {
|
|
17
17
|
this.timeout(300000);
|
|
@@ -9,4 +9,7 @@ export declare type TestVariantsTemplatesExt<Args extends Obj, ArgsExtra extends
|
|
|
9
9
|
export declare type TestVariantsIterableOptions<Args extends Obj, ArgsExtra extends Obj> = {
|
|
10
10
|
argsTemplates: TestVariantsTemplatesExt<Args, ArgsExtra>;
|
|
11
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* @deprecated Use testVariantsIterator instead
|
|
14
|
+
*/
|
|
12
15
|
export declare function testVariantsIterable<Args extends Obj, ArgsExtra extends Obj>({ argsTemplates, }: TestVariantsIterableOptions<Args, ArgsExtra>): Iterable<Args>;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/** Find last index of value in array; uses custom equals or strict equality */
|
|
6
|
+
function findLastIndex(values, value, equals) {
|
|
7
|
+
if (equals) {
|
|
8
|
+
for (let i = values.length - 1; i >= 0; i--) {
|
|
9
|
+
if (equals(values[i], value)) {
|
|
10
|
+
return i;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return -1;
|
|
14
|
+
}
|
|
15
|
+
for (let i = values.length - 1; i >= 0; i--) {
|
|
16
|
+
if (values[i] === value) {
|
|
17
|
+
return i;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return -1;
|
|
21
|
+
}
|
|
22
|
+
/** Calculate template values for given key index */
|
|
23
|
+
function calcTemplateValues(templates, args, keyIndex) {
|
|
24
|
+
const template = templates[keyIndex];
|
|
25
|
+
if (typeof template === 'function') {
|
|
26
|
+
return template(args);
|
|
27
|
+
}
|
|
28
|
+
return template;
|
|
29
|
+
}
|
|
30
|
+
/** Reset iterator state for new cycle */
|
|
31
|
+
function resetIteratorState(state, templates, keysCount) {
|
|
32
|
+
state.index = -1;
|
|
33
|
+
state.repeatIndex = 0;
|
|
34
|
+
for (let i = 0; i < keysCount; i++) {
|
|
35
|
+
state.indexes[i] = -1;
|
|
36
|
+
state.variants[i] = [];
|
|
37
|
+
}
|
|
38
|
+
if (keysCount > 0) {
|
|
39
|
+
state.variants[0] = calcTemplateValues(templates, state.args, 0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Get effective max index for an arg (considering argLimit) */
|
|
43
|
+
function getMaxIndex(state, keyIndex) {
|
|
44
|
+
const variantsLen = state.variants[keyIndex].length;
|
|
45
|
+
const argLimit = state.argLimits[keyIndex];
|
|
46
|
+
if (argLimit == null) {
|
|
47
|
+
return variantsLen;
|
|
48
|
+
}
|
|
49
|
+
return argLimit < variantsLen ? argLimit : variantsLen;
|
|
50
|
+
}
|
|
51
|
+
/** Advance to next variant in cartesian product; returns true if successful */
|
|
52
|
+
function advanceVariant(state, templates, keys, keysCount) {
|
|
53
|
+
var _a;
|
|
54
|
+
for (let keyIndex = keysCount - 1; keyIndex >= 0; keyIndex--) {
|
|
55
|
+
const valueIndex = state.indexes[keyIndex] + 1;
|
|
56
|
+
const maxIndex = getMaxIndex(state, keyIndex);
|
|
57
|
+
if (valueIndex < maxIndex) {
|
|
58
|
+
const key = keys[keyIndex];
|
|
59
|
+
const value = state.variants[keyIndex][valueIndex];
|
|
60
|
+
state.indexes[keyIndex] = valueIndex;
|
|
61
|
+
state.args[key] = value;
|
|
62
|
+
for (keyIndex++; keyIndex < keysCount; keyIndex++) {
|
|
63
|
+
const keyVariants = calcTemplateValues(templates, state.args, keyIndex);
|
|
64
|
+
const keyMaxIndex = (_a = state.argLimits[keyIndex]) !== null && _a !== void 0 ? _a : keyVariants.length;
|
|
65
|
+
if (keyVariants.length === 0 || keyMaxIndex <= 0) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
state.indexes[keyIndex] = 0;
|
|
69
|
+
state.variants[keyIndex] = keyVariants;
|
|
70
|
+
const key = keys[keyIndex];
|
|
71
|
+
const value = keyVariants[0];
|
|
72
|
+
state.args[key] = value;
|
|
73
|
+
}
|
|
74
|
+
if (keyIndex >= keysCount) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
/** Validate saved args keys match iterator's arg names (ignoring "seed" key) */
|
|
82
|
+
function validateArgsKeys(savedArgs, keysSet, keysCount) {
|
|
83
|
+
const savedKeys = Object.keys(savedArgs).filter(k => k !== 'seed');
|
|
84
|
+
if (savedKeys.length !== keysCount) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
for (const key of savedKeys) {
|
|
88
|
+
if (!keysSet.has(key)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
/** For static templates, verify arg value exists in template values */
|
|
95
|
+
function validateStaticArgsValues(savedArgs, templates, keys, keysCount, equals) {
|
|
96
|
+
for (let i = 0; i < keysCount; i++) {
|
|
97
|
+
const template = templates[i];
|
|
98
|
+
if (typeof template !== 'function') {
|
|
99
|
+
const value = savedArgs[keys[i]];
|
|
100
|
+
if (findLastIndex(template, value, equals) < 0) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
/** Check if current position >= pending args position; returns false if current < pending or all args skipped */
|
|
108
|
+
function isPositionReached(state, pendingArgs, keys, keysCount, equals) {
|
|
109
|
+
let anyCompared = false;
|
|
110
|
+
for (let i = 0; i < keysCount; i++) {
|
|
111
|
+
const currentValueIndex = state.indexes[i];
|
|
112
|
+
const pendingValue = pendingArgs[keys[i]];
|
|
113
|
+
const pendingValueIndex = findLastIndex(state.variants[i], pendingValue, equals);
|
|
114
|
+
// Dynamic template value not found - skip this arg from comparison
|
|
115
|
+
if (pendingValueIndex < 0) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
anyCompared = true;
|
|
119
|
+
if (currentValueIndex < pendingValueIndex) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (currentValueIndex > pendingValueIndex) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// All compared args are equal - position reached; or all args skipped - keep pending
|
|
127
|
+
return anyCompared;
|
|
128
|
+
}
|
|
129
|
+
/** Update per-arg limits from args values */
|
|
130
|
+
function updateArgLimits(state, limitArgs, templates, keys, keysCount, equals, limitArgOnError) {
|
|
131
|
+
if (!limitArgOnError) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
for (let i = 0; i < keysCount; i++) {
|
|
135
|
+
const key = keys[i];
|
|
136
|
+
const value = limitArgs[key];
|
|
137
|
+
const values = state.variants[i].length > 0
|
|
138
|
+
? state.variants[i]
|
|
139
|
+
: calcTemplateValues(templates, state.args, i);
|
|
140
|
+
const valueIndex = findLastIndex(values, value, equals);
|
|
141
|
+
// Skip if value not found or already at index 0
|
|
142
|
+
if (valueIndex <= 0) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// Check callback if provided
|
|
146
|
+
if (typeof limitArgOnError === 'function') {
|
|
147
|
+
const shouldLimit = limitArgOnError({
|
|
148
|
+
name: key,
|
|
149
|
+
valueIndex,
|
|
150
|
+
values,
|
|
151
|
+
maxValueIndex: state.argLimits[i],
|
|
152
|
+
});
|
|
153
|
+
if (!shouldLimit) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Update limit: argLimit = min(current argLimit, valueIndex)
|
|
158
|
+
const currentLimit = state.argLimits[i];
|
|
159
|
+
if (currentLimit == null || valueIndex < currentLimit) {
|
|
160
|
+
state.argLimits[i] = valueIndex;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Process pending limits; returns true if any limit was applied */
|
|
165
|
+
function processPendingLimits(state, templates, keys, keysCount, equals, limitArgOnError) {
|
|
166
|
+
let applied = false;
|
|
167
|
+
for (let i = state.pendingLimits.length - 1; i >= 0; i--) {
|
|
168
|
+
const pending = state.pendingLimits[i];
|
|
169
|
+
if (isPositionReached(state, pending.args, keys, keysCount, equals)) {
|
|
170
|
+
// Current position >= pending position: apply limit
|
|
171
|
+
if (state.count == null || state.index < state.count) {
|
|
172
|
+
state.count = state.index;
|
|
173
|
+
state.limit = typeof pending.error !== 'undefined'
|
|
174
|
+
? { args: pending.args, error: pending.error }
|
|
175
|
+
: { args: pending.args };
|
|
176
|
+
updateArgLimits(state, pending.args, templates, keys, keysCount, equals, limitArgOnError);
|
|
177
|
+
applied = true;
|
|
178
|
+
}
|
|
179
|
+
// Remove from pending
|
|
180
|
+
state.pendingLimits.splice(i, 1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return applied;
|
|
184
|
+
}
|
|
185
|
+
/** Creates test variants iterator with limiting capabilities */
|
|
186
|
+
function testVariantsIterator(options) {
|
|
187
|
+
const { argsTemplates, getSeed, repeatsPerVariant: _repeatsPerVariant, equals, limitArgOnError } = options;
|
|
188
|
+
const repeatsPerVariant = _repeatsPerVariant !== null && _repeatsPerVariant !== void 0 ? _repeatsPerVariant : 1;
|
|
189
|
+
const keys = Object.keys(argsTemplates);
|
|
190
|
+
const templates = Object.values(argsTemplates);
|
|
191
|
+
const keysCount = keys.length;
|
|
192
|
+
const keysSet = new Set(keys);
|
|
193
|
+
// Initialize state
|
|
194
|
+
const indexes = [];
|
|
195
|
+
const variants = [];
|
|
196
|
+
const argLimits = [];
|
|
197
|
+
for (let i = 0; i < keysCount; i++) {
|
|
198
|
+
indexes[i] = -1;
|
|
199
|
+
variants[i] = [];
|
|
200
|
+
argLimits[i] = null;
|
|
201
|
+
}
|
|
202
|
+
const state = {
|
|
203
|
+
args: {},
|
|
204
|
+
indexes,
|
|
205
|
+
variants,
|
|
206
|
+
argLimits,
|
|
207
|
+
index: -1,
|
|
208
|
+
cycleIndex: -1,
|
|
209
|
+
repeatIndex: 0,
|
|
210
|
+
count: null,
|
|
211
|
+
limit: null,
|
|
212
|
+
started: false,
|
|
213
|
+
currentArgs: null,
|
|
214
|
+
pendingLimits: [],
|
|
215
|
+
};
|
|
216
|
+
const iterator = {
|
|
217
|
+
get index() {
|
|
218
|
+
return state.index;
|
|
219
|
+
},
|
|
220
|
+
get cycleIndex() {
|
|
221
|
+
return state.cycleIndex;
|
|
222
|
+
},
|
|
223
|
+
get count() {
|
|
224
|
+
return state.count;
|
|
225
|
+
},
|
|
226
|
+
get limit() {
|
|
227
|
+
return state.limit;
|
|
228
|
+
},
|
|
229
|
+
addLimit(_options) {
|
|
230
|
+
const hasArgs = typeof (_options === null || _options === void 0 ? void 0 : _options.args) !== 'undefined' && _options.args !== null;
|
|
231
|
+
const hasIndex = (_options === null || _options === void 0 ? void 0 : _options.index) != null;
|
|
232
|
+
// addLimit() or addLimit({error}) - uses current args and index
|
|
233
|
+
if (!hasArgs && !hasIndex) {
|
|
234
|
+
if (state.index < 0) {
|
|
235
|
+
throw new Error('[testVariantsIterator] addLimit() requires at least one next() call');
|
|
236
|
+
}
|
|
237
|
+
if (state.count == null || state.index < state.count) {
|
|
238
|
+
state.count = state.index;
|
|
239
|
+
state.limit = typeof (_options === null || _options === void 0 ? void 0 : _options.error) !== 'undefined'
|
|
240
|
+
? { args: state.currentArgs, error: _options.error }
|
|
241
|
+
: { args: state.currentArgs };
|
|
242
|
+
updateArgLimits(state, state.args, templates, keys, keysCount, equals, limitArgOnError);
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// addLimit({index}) - only index limiting, no args
|
|
247
|
+
if (hasIndex && !hasArgs) {
|
|
248
|
+
if (state.count == null || _options.index < state.count) {
|
|
249
|
+
state.count = _options.index;
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// addLimit({args}) or addLimit({args, error}) - pending limit
|
|
254
|
+
if (hasArgs && !hasIndex) {
|
|
255
|
+
// Validate args keys match iterator's arg names
|
|
256
|
+
if (!validateArgsKeys(_options.args, keysSet, keysCount)) {
|
|
257
|
+
return; // Discard - unreproducible (templates changed)
|
|
258
|
+
}
|
|
259
|
+
// For static templates, verify arg value exists
|
|
260
|
+
if (!validateStaticArgsValues(_options.args, templates, keys, keysCount, equals)) {
|
|
261
|
+
return; // Discard - unreproducible (value not in template)
|
|
262
|
+
}
|
|
263
|
+
// Store as pending limit
|
|
264
|
+
const pending = typeof _options.error !== 'undefined'
|
|
265
|
+
? { args: _options.args, error: _options.error }
|
|
266
|
+
: { args: _options.args };
|
|
267
|
+
state.pendingLimits.push(pending);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// addLimit({args, index}) or addLimit({args, index, error}) - immediate index + pending args
|
|
271
|
+
if (hasArgs && hasIndex) {
|
|
272
|
+
// Check if this is earliest (before potentially updating count)
|
|
273
|
+
const isEarliest = state.count == null || _options.index < state.count;
|
|
274
|
+
// Always apply index limit
|
|
275
|
+
if (isEarliest) {
|
|
276
|
+
state.count = _options.index;
|
|
277
|
+
}
|
|
278
|
+
// Validate args for limit property update
|
|
279
|
+
if (!validateArgsKeys(_options.args, keysSet, keysCount)) {
|
|
280
|
+
return; // Skip per-arg limits and limit property update
|
|
281
|
+
}
|
|
282
|
+
if (!validateStaticArgsValues(_options.args, templates, keys, keysCount, equals)) {
|
|
283
|
+
return; // Skip per-arg limits and limit property update
|
|
284
|
+
}
|
|
285
|
+
// Update limit if this is earliest
|
|
286
|
+
if (isEarliest) {
|
|
287
|
+
state.limit = typeof _options.error !== 'undefined'
|
|
288
|
+
? { args: _options.args, error: _options.error }
|
|
289
|
+
: { args: _options.args };
|
|
290
|
+
updateArgLimits(state, _options.args, templates, keys, keysCount, equals, limitArgOnError);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
start() {
|
|
295
|
+
state.cycleIndex++;
|
|
296
|
+
resetIteratorState(state, templates, keysCount);
|
|
297
|
+
state.started = true;
|
|
298
|
+
},
|
|
299
|
+
next() {
|
|
300
|
+
if (!state.started) {
|
|
301
|
+
throw new Error('[testVariantsIterator] start() must be called before next()');
|
|
302
|
+
}
|
|
303
|
+
// Try next repeat for current variant
|
|
304
|
+
if (state.index >= 0 && state.repeatIndex + 1 < repeatsPerVariant) {
|
|
305
|
+
// Check if current variant is still within limit
|
|
306
|
+
if (state.count == null || state.index < state.count) {
|
|
307
|
+
state.repeatIndex++;
|
|
308
|
+
if (getSeed) {
|
|
309
|
+
const seed = getSeed({
|
|
310
|
+
variantIndex: state.index,
|
|
311
|
+
cycleIndex: state.cycleIndex,
|
|
312
|
+
repeatIndex: state.repeatIndex,
|
|
313
|
+
});
|
|
314
|
+
state.currentArgs = Object.assign(Object.assign({}, state.args), { seed });
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
state.currentArgs = Object.assign({}, state.args);
|
|
318
|
+
}
|
|
319
|
+
return state.currentArgs;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Move to next variant
|
|
323
|
+
state.repeatIndex = 0;
|
|
324
|
+
if (!advanceVariant(state, templates, keys, keysCount)) {
|
|
325
|
+
// First complete cycle sets count to total variant count
|
|
326
|
+
if (state.count == null) {
|
|
327
|
+
state.count = state.index + 1;
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
state.index++;
|
|
332
|
+
// Process pending limits at new position
|
|
333
|
+
if (state.pendingLimits.length > 0) {
|
|
334
|
+
processPendingLimits(state, templates, keys, keysCount, equals, limitArgOnError);
|
|
335
|
+
}
|
|
336
|
+
// Check count limit (may have been updated by pending limit)
|
|
337
|
+
if (state.count != null && state.index >= state.count) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
if (getSeed) {
|
|
341
|
+
const seed = getSeed({
|
|
342
|
+
variantIndex: state.index,
|
|
343
|
+
cycleIndex: state.cycleIndex,
|
|
344
|
+
repeatIndex: state.repeatIndex,
|
|
345
|
+
});
|
|
346
|
+
state.currentArgs = Object.assign(Object.assign({}, state.args), { seed });
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
state.currentArgs = Object.assign({}, state.args);
|
|
350
|
+
}
|
|
351
|
+
return state.currentArgs;
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
return iterator;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
exports.testVariantsIterator = testVariantsIterator;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Obj } from "./types";
|
|
2
|
+
import type { TestVariantsTemplate, TestVariantsTemplates } from "./testVariantsIterable";
|
|
3
|
+
/** Parameters passed to getSeed function for generating test seeds */
|
|
4
|
+
export declare type GetSeedParams = {
|
|
5
|
+
/** Index of current variant/parameter-combination being tested */
|
|
6
|
+
variantIndex: number;
|
|
7
|
+
/** Index of current cycle - full pass through all variants (0..cycles-1) */
|
|
8
|
+
cycleIndex: number;
|
|
9
|
+
/** Index of repeat for current variant within this cycle (0..repeatsPerVariant-1) */
|
|
10
|
+
repeatIndex: number;
|
|
11
|
+
};
|
|
12
|
+
/** Options for limiting per-arg indexes on error */
|
|
13
|
+
export declare type LimitArgOnErrorOptions = {
|
|
14
|
+
/** Arg name */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Arg value's index in template values array */
|
|
17
|
+
valueIndex: number;
|
|
18
|
+
/** Current template values array for this arg */
|
|
19
|
+
values: any[];
|
|
20
|
+
/** Current max value index limit for this arg; null if no limit */
|
|
21
|
+
maxValueIndex: number | null;
|
|
22
|
+
};
|
|
23
|
+
/** Callback to decide whether to apply limit for specific arg */
|
|
24
|
+
export declare type LimitArgOnError = (options: LimitArgOnErrorOptions) => boolean;
|
|
25
|
+
/** Options for creating test variants iterator */
|
|
26
|
+
export declare type TestVariantsIteratorOptions<Args extends Obj> = {
|
|
27
|
+
argsTemplates: TestVariantsTemplates<Args>;
|
|
28
|
+
/** Custom equality for comparing arg values when finding indexes */
|
|
29
|
+
equals?: null | ((a: any, b: any) => boolean);
|
|
30
|
+
/** Limit per-arg indexes on error; boolean enables/disables, function for custom per-arg logic */
|
|
31
|
+
limitArgOnError?: null | boolean | LimitArgOnError;
|
|
32
|
+
/** Seed generator for findBestError; if provided, seed is added to args returned by next() */
|
|
33
|
+
getSeed?: null | ((params: GetSeedParams) => any);
|
|
34
|
+
/** Number of repeat tests per variant within each cycle */
|
|
35
|
+
repeatsPerVariant?: null | number;
|
|
36
|
+
};
|
|
37
|
+
/** Limit information with args and optional error */
|
|
38
|
+
export declare type TestVariantsIteratorLimit<Args> = {
|
|
39
|
+
args: Args;
|
|
40
|
+
error?: unknown;
|
|
41
|
+
};
|
|
42
|
+
/** Options for addLimit method */
|
|
43
|
+
export declare type AddLimitOptions<Args> = {
|
|
44
|
+
args?: null | Args;
|
|
45
|
+
index?: null | number;
|
|
46
|
+
error?: unknown;
|
|
47
|
+
};
|
|
48
|
+
/** Test variants iterator with limiting capabilities */
|
|
49
|
+
export declare type TestVariantsIterator<Args extends Obj> = {
|
|
50
|
+
/** Current variant index; -1 before first next() */
|
|
51
|
+
readonly index: number;
|
|
52
|
+
/** Current cycle index; -1 before first start(), 0 after first start(), incremented by each start() call */
|
|
53
|
+
readonly cycleIndex: number;
|
|
54
|
+
/** Limit or max count; variants with index >= count not yielded; null initially, set when first next() returns null or via addLimit({index}) */
|
|
55
|
+
readonly count: number | null;
|
|
56
|
+
/** Last applied limit's args and error; null if no limit applied yet or limit was index-only */
|
|
57
|
+
readonly limit: TestVariantsIteratorLimit<Args> | null;
|
|
58
|
+
/** Add or tighten limit based on args and/or index */
|
|
59
|
+
addLimit(options?: null | AddLimitOptions<Args>): void;
|
|
60
|
+
/** Reset to beginning of iteration; increments cycleIndex */
|
|
61
|
+
start(): void;
|
|
62
|
+
/** Get next variant or null when done */
|
|
63
|
+
next(): Args | null;
|
|
64
|
+
};
|
|
65
|
+
export { type TestVariantsTemplate, type TestVariantsTemplates, };
|
|
66
|
+
/** Creates test variants iterator with limiting capabilities */
|
|
67
|
+
export declare function testVariantsIterator<Args extends Obj>(options: TestVariantsIteratorOptions<Args>): TestVariantsIterator<Args>;
|