@flemist/test-variants 1.0.7 → 2.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.
Files changed (34) hide show
  1. package/dist/bundle/browser.js +437 -212
  2. package/dist/lib/index.cjs +10 -2
  3. package/dist/lib/index.d.ts +6 -2
  4. package/dist/lib/index.mjs +9 -2
  5. package/dist/lib/test-variants/argsToString.cjs +17 -0
  6. package/dist/lib/test-variants/argsToString.d.ts +2 -0
  7. package/dist/lib/test-variants/argsToString.mjs +13 -0
  8. package/dist/lib/test-variants/createTestVariants.cjs +71 -188
  9. package/dist/lib/test-variants/createTestVariants.d.ts +8 -30
  10. package/dist/lib/test-variants/createTestVariants.mjs +71 -188
  11. package/dist/lib/test-variants/createTestVariants.perf.cjs +32 -20
  12. package/dist/lib/test-variants/createTestVariants.perf.mjs +32 -20
  13. package/dist/lib/test-variants/prime.cjs +65 -0
  14. package/dist/lib/test-variants/prime.d.ts +3 -0
  15. package/dist/lib/test-variants/prime.mjs +59 -0
  16. package/dist/lib/test-variants/prime.perf.cjs +30 -0
  17. package/dist/lib/test-variants/prime.perf.d.ts +1 -0
  18. package/dist/lib/test-variants/prime.perf.mjs +28 -0
  19. package/dist/lib/test-variants/saveErrorVariants.cjs +97 -0
  20. package/dist/lib/test-variants/saveErrorVariants.d.ts +9 -0
  21. package/dist/lib/test-variants/saveErrorVariants.mjs +69 -0
  22. package/dist/lib/test-variants/testVariantsCreateTestRun.cjs +80 -0
  23. package/dist/lib/test-variants/testVariantsCreateTestRun.d.ts +22 -0
  24. package/dist/lib/test-variants/testVariantsCreateTestRun.mjs +76 -0
  25. package/dist/lib/test-variants/testVariantsIterable.cjs +67 -0
  26. package/dist/lib/test-variants/testVariantsIterable.d.ts +12 -0
  27. package/dist/lib/test-variants/testVariantsIterable.mjs +63 -0
  28. package/dist/lib/test-variants/testVariantsRun.cjs +235 -0
  29. package/dist/lib/test-variants/testVariantsRun.d.ts +33 -0
  30. package/dist/lib/test-variants/testVariantsRun.mjs +211 -0
  31. package/dist/lib/test-variants/types.cjs +2 -0
  32. package/dist/lib/test-variants/types.d.ts +18 -0
  33. package/dist/lib/test-variants/types.mjs +1 -0
  34. package/package.json +12 -9
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tslib = require('tslib');
6
+ var fs = require('fs');
7
+ var path = require('path');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n["default"] = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
28
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
29
+
30
+ /** Reads saved error variant files from directory, sorted by filename descending (newest first) */
31
+ function readErrorVariantFiles(dir) {
32
+ return tslib.__awaiter(this, void 0, void 0, function* () {
33
+ const stat = yield fs__namespace.promises.stat(dir).catch(() => null);
34
+ if (stat == null) {
35
+ return [];
36
+ }
37
+ if (!stat.isDirectory()) {
38
+ throw new Error(`[saveErrorVariants] path is not a directory: ${dir}`);
39
+ }
40
+ const files = yield fs__namespace.promises.readdir(dir);
41
+ const jsonFiles = files
42
+ .filter(file => file.endsWith('.json'))
43
+ .sort((a, b) => a > b ? -1 : a < b ? 1 : 0);
44
+ return jsonFiles.map(file => path__namespace.join(dir, file));
45
+ });
46
+ }
47
+ /** Parses saved error variant file and transforms JSON to args */
48
+ function parseErrorVariantFile(filePath, jsonToArgs) {
49
+ return tslib.__awaiter(this, void 0, void 0, function* () {
50
+ const content = yield fs__namespace.promises.readFile(filePath, 'utf-8');
51
+ let json;
52
+ try {
53
+ json = JSON.parse(content);
54
+ }
55
+ catch (err) {
56
+ throw new Error(`[saveErrorVariants] invalid JSON in file: ${filePath}`);
57
+ }
58
+ if (jsonToArgs) {
59
+ try {
60
+ return jsonToArgs(json);
61
+ }
62
+ catch (err) {
63
+ throw new Error(`[saveErrorVariants] jsonToArgs failed for file: ${filePath}`);
64
+ }
65
+ }
66
+ return json;
67
+ });
68
+ }
69
+ /** Generates default error variant file path: YYYY-MM-DD_HH-mm-ss.json (UTC) */
70
+ function generateErrorVariantFilePath(options) {
71
+ return options.sessionDate.toISOString().substring(0, 19).replace('T', '_').replaceAll(':', '-') + '.json';
72
+ }
73
+ /** Saves error-causing args to a JSON file, overwrites if file exists */
74
+ function saveErrorVariantFile(args, filePath, argsToJson) {
75
+ return tslib.__awaiter(this, void 0, void 0, function* () {
76
+ let content;
77
+ if (argsToJson) {
78
+ const result = argsToJson(args);
79
+ if (typeof result === 'string') {
80
+ content = result;
81
+ }
82
+ else {
83
+ content = JSON.stringify(result, null, 2);
84
+ }
85
+ }
86
+ else {
87
+ content = JSON.stringify(args, null, 2);
88
+ }
89
+ yield fs__namespace.promises.mkdir(path__namespace.dirname(filePath), { recursive: true });
90
+ yield fs__namespace.promises.writeFile(filePath, content, 'utf-8');
91
+ });
92
+ }
93
+
94
+ exports.generateErrorVariantFilePath = generateErrorVariantFilePath;
95
+ exports.parseErrorVariantFile = parseErrorVariantFile;
96
+ exports.readErrorVariantFiles = readErrorVariantFiles;
97
+ exports.saveErrorVariantFile = saveErrorVariantFile;
@@ -0,0 +1,9 @@
1
+ import { type GenerateErrorVariantFilePathOptions, type Obj } from "./types";
2
+ /** Reads saved error variant files from directory, sorted by filename descending (newest first) */
3
+ export declare function readErrorVariantFiles(dir: string): Promise<string[]>;
4
+ /** Parses saved error variant file and transforms JSON to args */
5
+ export declare function parseErrorVariantFile<Args extends Obj, SavedArgs>(filePath: string, jsonToArgs?: null | ((json: SavedArgs) => Args)): Promise<Args>;
6
+ /** Generates default error variant file path: YYYY-MM-DD_HH-mm-ss.json (UTC) */
7
+ export declare function generateErrorVariantFilePath(options: GenerateErrorVariantFilePathOptions): string;
8
+ /** Saves error-causing args to a JSON file, overwrites if file exists */
9
+ export declare function saveErrorVariantFile<Args extends Obj, SavedArgs>(args: Args, filePath: string, argsToJson?: null | ((args: Args) => string | SavedArgs)): Promise<void>;
@@ -0,0 +1,69 @@
1
+ import { __awaiter } from 'tslib';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ /** Reads saved error variant files from directory, sorted by filename descending (newest first) */
6
+ function readErrorVariantFiles(dir) {
7
+ return __awaiter(this, void 0, void 0, function* () {
8
+ const stat = yield fs.promises.stat(dir).catch(() => null);
9
+ if (stat == null) {
10
+ return [];
11
+ }
12
+ if (!stat.isDirectory()) {
13
+ throw new Error(`[saveErrorVariants] path is not a directory: ${dir}`);
14
+ }
15
+ const files = yield fs.promises.readdir(dir);
16
+ const jsonFiles = files
17
+ .filter(file => file.endsWith('.json'))
18
+ .sort((a, b) => a > b ? -1 : a < b ? 1 : 0);
19
+ return jsonFiles.map(file => path.join(dir, file));
20
+ });
21
+ }
22
+ /** Parses saved error variant file and transforms JSON to args */
23
+ function parseErrorVariantFile(filePath, jsonToArgs) {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ const content = yield fs.promises.readFile(filePath, 'utf-8');
26
+ let json;
27
+ try {
28
+ json = JSON.parse(content);
29
+ }
30
+ catch (err) {
31
+ throw new Error(`[saveErrorVariants] invalid JSON in file: ${filePath}`);
32
+ }
33
+ if (jsonToArgs) {
34
+ try {
35
+ return jsonToArgs(json);
36
+ }
37
+ catch (err) {
38
+ throw new Error(`[saveErrorVariants] jsonToArgs failed for file: ${filePath}`);
39
+ }
40
+ }
41
+ return json;
42
+ });
43
+ }
44
+ /** Generates default error variant file path: YYYY-MM-DD_HH-mm-ss.json (UTC) */
45
+ function generateErrorVariantFilePath(options) {
46
+ return options.sessionDate.toISOString().substring(0, 19).replace('T', '_').replaceAll(':', '-') + '.json';
47
+ }
48
+ /** Saves error-causing args to a JSON file, overwrites if file exists */
49
+ function saveErrorVariantFile(args, filePath, argsToJson) {
50
+ return __awaiter(this, void 0, void 0, function* () {
51
+ let content;
52
+ if (argsToJson) {
53
+ const result = argsToJson(args);
54
+ if (typeof result === 'string') {
55
+ content = result;
56
+ }
57
+ else {
58
+ content = JSON.stringify(result, null, 2);
59
+ }
60
+ }
61
+ else {
62
+ content = JSON.stringify(args, null, 2);
63
+ }
64
+ yield fs.promises.mkdir(path.dirname(filePath), { recursive: true });
65
+ yield fs.promises.writeFile(filePath, content, 'utf-8');
66
+ });
67
+ }
68
+
69
+ export { generateErrorVariantFilePath, parseErrorVariantFile, readErrorVariantFiles, saveErrorVariantFile };
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tslib = require('tslib');
6
+ var asyncUtils = require('@flemist/async-utils');
7
+ var testVariants_argsToString = require('./argsToString.cjs');
8
+
9
+ function testVariantsCreateTestRun(test, options) {
10
+ let debugIteration = 0;
11
+ let errorEvent = null;
12
+ function onError(error, args, index) {
13
+ return tslib.__awaiter(this, void 0, void 0, function* () {
14
+ errorEvent = {
15
+ error,
16
+ args,
17
+ index: index,
18
+ };
19
+ console.error(`[test-variants] error variant: ${index}\n${testVariants_argsToString.argsToString(args)}`);
20
+ console.error(error);
21
+ // Rerun failed variant 5 times for debug
22
+ const time0 = Date.now();
23
+ // Will stop execution right before next error iteration for step-by-step debugging
24
+ // eslint-disable-next-line no-debugger
25
+ debugger;
26
+ if (Date.now() - time0 > 50 && debugIteration < 5) {
27
+ console.log('[test-variants] DEBUG ITERATION: ' + debugIteration);
28
+ debugIteration++;
29
+ return;
30
+ }
31
+ if (options.onError) {
32
+ yield options.onError(errorEvent);
33
+ }
34
+ throw errorEvent.error;
35
+ });
36
+ }
37
+ return function testRun(args, index, abortSignal) {
38
+ try {
39
+ const promiseOrIterations = test(args, abortSignal);
40
+ if (asyncUtils.isPromiseLike(promiseOrIterations)) {
41
+ return promiseOrIterations.then(value => {
42
+ if (typeof value === 'number') {
43
+ return {
44
+ iterationsAsync: value,
45
+ iterationsSync: 0,
46
+ };
47
+ }
48
+ if (value !== null && typeof value === 'object') {
49
+ return value;
50
+ }
51
+ return {
52
+ iterationsAsync: 1,
53
+ iterationsSync: 0,
54
+ };
55
+ }, err => {
56
+ return onError(err, args, index);
57
+ });
58
+ }
59
+ const value = promiseOrIterations;
60
+ if (typeof value === 'number') {
61
+ return {
62
+ iterationsAsync: 0,
63
+ iterationsSync: value,
64
+ };
65
+ }
66
+ if (value !== null && typeof value === 'object') {
67
+ return value;
68
+ }
69
+ return {
70
+ iterationsAsync: 0,
71
+ iterationsSync: 1,
72
+ };
73
+ }
74
+ catch (err) {
75
+ return onError(err, args, index);
76
+ }
77
+ };
78
+ }
79
+
80
+ exports.testVariantsCreateTestRun = testVariantsCreateTestRun;
@@ -0,0 +1,22 @@
1
+ import { type IAbortSignalFast } from '@flemist/abort-controller-fast';
2
+ import { type PromiseOrValue } from '@flemist/async-utils';
3
+ import { Obj } from "./types";
4
+ export declare type ErrorEvent<Args extends Obj> = {
5
+ error: any;
6
+ args: Args;
7
+ index: number;
8
+ };
9
+ export declare type OnErrorCallback<Args extends Obj> = (event: ErrorEvent<Args>) => PromiseOrValue<void>;
10
+ export declare type TestVariantsCreateTestRunOptions<Args extends Obj> = {
11
+ onError?: null | OnErrorCallback<Args>;
12
+ };
13
+ export declare type TestVariantsTestRunResult = void | {
14
+ iterationsAsync: number;
15
+ iterationsSync: number;
16
+ };
17
+ export declare type TestVariantsTestRun<Args extends Obj> = (args: Args, index: number, abortSignal: IAbortSignalFast) => PromiseOrValue<TestVariantsTestRunResult>;
18
+ export declare type TestVariantsTestResult = number | void | TestVariantsTestRunResult;
19
+ export declare type TestVariantsTest<Args extends Obj> = (args: Args & {
20
+ seed?: null | number;
21
+ }, abortSignal: IAbortSignalFast) => PromiseOrValue<TestVariantsTestResult>;
22
+ export declare function testVariantsCreateTestRun<Args extends Obj>(test: TestVariantsTest<Args>, options?: null | TestVariantsCreateTestRunOptions<Args>): TestVariantsTestRun<Args>;
@@ -0,0 +1,76 @@
1
+ import { __awaiter } from 'tslib';
2
+ import { isPromiseLike } from '@flemist/async-utils';
3
+ import { argsToString } from './argsToString.mjs';
4
+
5
+ function testVariantsCreateTestRun(test, options) {
6
+ let debugIteration = 0;
7
+ let errorEvent = null;
8
+ function onError(error, args, index) {
9
+ return __awaiter(this, void 0, void 0, function* () {
10
+ errorEvent = {
11
+ error,
12
+ args,
13
+ index: index,
14
+ };
15
+ console.error(`[test-variants] error variant: ${index}\n${argsToString(args)}`);
16
+ console.error(error);
17
+ // Rerun failed variant 5 times for debug
18
+ const time0 = Date.now();
19
+ // Will stop execution right before next error iteration for step-by-step debugging
20
+ // eslint-disable-next-line no-debugger
21
+ debugger;
22
+ if (Date.now() - time0 > 50 && debugIteration < 5) {
23
+ console.log('[test-variants] DEBUG ITERATION: ' + debugIteration);
24
+ debugIteration++;
25
+ return;
26
+ }
27
+ if (options.onError) {
28
+ yield options.onError(errorEvent);
29
+ }
30
+ throw errorEvent.error;
31
+ });
32
+ }
33
+ return function testRun(args, index, abortSignal) {
34
+ try {
35
+ const promiseOrIterations = test(args, abortSignal);
36
+ if (isPromiseLike(promiseOrIterations)) {
37
+ return promiseOrIterations.then(value => {
38
+ if (typeof value === 'number') {
39
+ return {
40
+ iterationsAsync: value,
41
+ iterationsSync: 0,
42
+ };
43
+ }
44
+ if (value !== null && typeof value === 'object') {
45
+ return value;
46
+ }
47
+ return {
48
+ iterationsAsync: 1,
49
+ iterationsSync: 0,
50
+ };
51
+ }, err => {
52
+ return onError(err, args, index);
53
+ });
54
+ }
55
+ const value = promiseOrIterations;
56
+ if (typeof value === 'number') {
57
+ return {
58
+ iterationsAsync: 0,
59
+ iterationsSync: value,
60
+ };
61
+ }
62
+ if (value !== null && typeof value === 'object') {
63
+ return value;
64
+ }
65
+ return {
66
+ iterationsAsync: 0,
67
+ iterationsSync: 1,
68
+ };
69
+ }
70
+ catch (err) {
71
+ return onError(err, args, index);
72
+ }
73
+ };
74
+ }
75
+
76
+ export { testVariantsCreateTestRun };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ function testVariantsIterable({ argsTemplates, }) {
6
+ return {
7
+ [Symbol.iterator]() {
8
+ const keys = Object.keys(argsTemplates);
9
+ const templates = Object.values(argsTemplates);
10
+ const keysCount = keys.length;
11
+ const args = {};
12
+ function calcVariants(keyIndex) {
13
+ let template = templates[keyIndex];
14
+ if (typeof template === 'function') {
15
+ template = template(args);
16
+ }
17
+ return template;
18
+ }
19
+ const indexes = [];
20
+ const variants = [];
21
+ for (let nArg = 0; nArg < keysCount; nArg++) {
22
+ indexes[nArg] = -1;
23
+ variants[nArg] = [];
24
+ }
25
+ variants[0] = calcVariants(0);
26
+ function nextVariant() {
27
+ for (let keyIndex = keysCount - 1; keyIndex >= 0; keyIndex--) {
28
+ const valueIndex = indexes[keyIndex] + 1;
29
+ if (valueIndex < variants[keyIndex].length) {
30
+ const key = keys[keyIndex];
31
+ const value = variants[keyIndex][valueIndex];
32
+ indexes[keyIndex] = valueIndex;
33
+ args[key] = value;
34
+ for (keyIndex++; keyIndex < keysCount; keyIndex++) {
35
+ const keyVariants = calcVariants(keyIndex);
36
+ if (keyVariants.length === 0) {
37
+ break;
38
+ }
39
+ indexes[keyIndex] = 0;
40
+ variants[keyIndex] = keyVariants;
41
+ const key = keys[keyIndex];
42
+ const value = keyVariants[0];
43
+ args[key] = value;
44
+ }
45
+ if (keyIndex >= keysCount) {
46
+ return true;
47
+ }
48
+ }
49
+ }
50
+ return false;
51
+ }
52
+ return {
53
+ next() {
54
+ if (nextVariant()) {
55
+ return {
56
+ done: false,
57
+ value: Object.assign({}, args),
58
+ };
59
+ }
60
+ return { done: true, value: null };
61
+ },
62
+ };
63
+ },
64
+ };
65
+ }
66
+
67
+ exports.testVariantsIterable = testVariantsIterable;
@@ -0,0 +1,12 @@
1
+ import { Obj } from "./types";
2
+ export declare type TestVariantsTemplate<Args extends Obj, Value> = Value[] | ((args: Args) => Value[]);
3
+ export declare type TestVariantsTemplates<Args extends Obj> = {
4
+ [key in keyof Args]: TestVariantsTemplate<Args, Args[key]>;
5
+ };
6
+ export declare type TestVariantsTemplatesExt<Args extends Obj, ArgsExtra extends Obj> = TestVariantsTemplates<{
7
+ [key in (keyof ArgsExtra | keyof Args)]: key extends keyof Args ? Args[key] : key extends keyof ArgsExtra ? ArgsExtra[key] : never;
8
+ }>;
9
+ export declare type TestVariantsIterableOptions<Args extends Obj, ArgsExtra extends Obj> = {
10
+ argsTemplates: TestVariantsTemplatesExt<Args, ArgsExtra>;
11
+ };
12
+ export declare function testVariantsIterable<Args extends Obj, ArgsExtra extends Obj>({ argsTemplates, }: TestVariantsIterableOptions<Args, ArgsExtra>): Iterable<Args>;
@@ -0,0 +1,63 @@
1
+ function testVariantsIterable({ argsTemplates, }) {
2
+ return {
3
+ [Symbol.iterator]() {
4
+ const keys = Object.keys(argsTemplates);
5
+ const templates = Object.values(argsTemplates);
6
+ const keysCount = keys.length;
7
+ const args = {};
8
+ function calcVariants(keyIndex) {
9
+ let template = templates[keyIndex];
10
+ if (typeof template === 'function') {
11
+ template = template(args);
12
+ }
13
+ return template;
14
+ }
15
+ const indexes = [];
16
+ const variants = [];
17
+ for (let nArg = 0; nArg < keysCount; nArg++) {
18
+ indexes[nArg] = -1;
19
+ variants[nArg] = [];
20
+ }
21
+ variants[0] = calcVariants(0);
22
+ function nextVariant() {
23
+ for (let keyIndex = keysCount - 1; keyIndex >= 0; keyIndex--) {
24
+ const valueIndex = indexes[keyIndex] + 1;
25
+ if (valueIndex < variants[keyIndex].length) {
26
+ const key = keys[keyIndex];
27
+ const value = variants[keyIndex][valueIndex];
28
+ indexes[keyIndex] = valueIndex;
29
+ args[key] = value;
30
+ for (keyIndex++; keyIndex < keysCount; keyIndex++) {
31
+ const keyVariants = calcVariants(keyIndex);
32
+ if (keyVariants.length === 0) {
33
+ break;
34
+ }
35
+ indexes[keyIndex] = 0;
36
+ variants[keyIndex] = keyVariants;
37
+ const key = keys[keyIndex];
38
+ const value = keyVariants[0];
39
+ args[key] = value;
40
+ }
41
+ if (keyIndex >= keysCount) {
42
+ return true;
43
+ }
44
+ }
45
+ }
46
+ return false;
47
+ }
48
+ return {
49
+ next() {
50
+ if (nextVariant()) {
51
+ return {
52
+ done: false,
53
+ value: Object.assign({}, args),
54
+ };
55
+ }
56
+ return { done: true, value: null };
57
+ },
58
+ };
59
+ },
60
+ };
61
+ }
62
+
63
+ export { testVariantsIterable };