From a4d49f92a966ceef8cb96b5af22062288f076e14 Mon Sep 17 00:00:00 2001 From: Max Levytskyi Date: Sun, 22 Oct 2023 22:22:39 +0300 Subject: [PATCH 1/3] feat: statistics --- package-lock.json | 4 +- src/api/native/index.ts | 87 +++++++------- src/api/native/parse.ts | 22 ++++ src/api/native/utils.ts | 17 +++ src/api/pict/index.ts | 133 ++++++++++++++------- src/api/pict/model.ts | 2 +- src/api/strings/index.ts | 243 +++++++++++++++++++++++++-------------- src/common/pict.ts | 5 + src/common/statistics.ts | 63 ++++++++++ src/common/types.ts | 8 ++ test/native.spec.ts | 42 +++++++ test/pict.spec.ts | 24 ++++ test/strings.spec.ts | 35 ++++++ 13 files changed, 513 insertions(+), 172 deletions(-) create mode 100644 src/api/native/parse.ts create mode 100644 src/api/native/utils.ts create mode 100644 src/common/statistics.ts diff --git a/package-lock.json b/package-lock.json index 54e5b26..18c972d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pict-node", - "version": "1.2.0", + "version": "1.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pict-node", - "version": "1.2.0", + "version": "1.2.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/src/api/native/index.ts b/src/api/native/index.ts index 79fd95e..158fd60 100644 --- a/src/api/native/index.ts +++ b/src/api/native/index.ts @@ -1,5 +1,4 @@ -import fsp from "fs/promises"; -import { isString, isUndefined, isBoolean, isRecord } from "tsguarder"; +import { isUndefined, isBoolean, isRecord } from "tsguarder"; import { ModelSource, isModelSource } from "./types"; import type { CallPictOptions } from "./../../common/pict"; import { isPositiveNumber } from "./../../common/utils"; @@ -7,15 +6,25 @@ import { PictCliOptions, isRandomOption, isModelSeparator, + PictNodeStatistics, } from "../../common/types"; -import { callPict, pictEntries } from "../../common/pict"; +import { callPict } from "../../common/pict"; +import { getModelFromSource } from "./utils"; +import { parseResult } from "./parse"; +import { performance } from "perf_hooks"; +import { parseStatistics } from "../../common/statistics"; interface NativeOptions { model: ModelSource; seed?: ModelSource; - options?: Partial>; + options?: Partial>; } +type Native = { + (options: NativeOptions): Promise[]>; + stats: (options: NativeOptions) => Promise; +}; + function validate(options: NativeOptions) { isRecord.assert(options, "the first argument"); isModelSource.assert(options.model, '"model"'); @@ -63,63 +72,55 @@ function validate(options: NativeOptions) { } } -function parseResult(result: string): Array> { - const cases: any[] = []; - - for (const item of pictEntries(result)) { - let currentCase; +async function prepare(options: NativeOptions) { + validate(options); - if (item.valueIndex === 0) { - currentCase = {}; - cases.push(currentCase); - } else { - currentCase = cases[cases.length - 1]; - } + const callPictOptions: CallPictOptions = { + modelText: await getModelFromSource(options.model), + options: {}, + }; - currentCase[item.rowName as string] = item.value; + if (!isUndefined(options.seed)) { + callPictOptions.seedText = await getModelFromSource(options.seed); + } - cases[cases.length - 1] = currentCase; + if (!isUndefined(options.options)) { + callPictOptions.options = options.options; } - return cases; + return callPictOptions; } -async function getModelFromSource(source: ModelSource) { - if (isString(source)) { - return source; - } - +export const native: Native = async function native(options: NativeOptions) { try { - const fileContent = await fsp.readFile(source.file); - return fileContent.toString(); + const callPictOptions = await prepare(options); + + callPictOptions.options.statistics = false; + + const result = await callPict(callPictOptions); + + return parseResult(result); } catch (error) { - console.error(`Error while reading model file. ${source.file}`); + console.error('Error while calling "native" function.'); throw error; } -} +}; -export async function native(options: NativeOptions) { +native.stats = async function native(options: NativeOptions) { try { - validate(options); + const start = performance.now(); - const callPictOptions: CallPictOptions = { - modelText: await getModelFromSource(options.model), - options: {}, - }; + const callPictOptions = await prepare(options); - if (!isUndefined(options.seed)) { - callPictOptions.seedText = await getModelFromSource(options.seed); - } - - if (!isUndefined(options.options)) { - callPictOptions.options = options.options; - } + callPictOptions.options.statistics = true; const result = await callPict(callPictOptions); - return parseResult(result); + const end = performance.now() - start; + + return parseStatistics(result, end); } catch (error) { - console.error('Error while calling "native" function.'); + console.error('Error while calling "native.stats" function.'); throw error; } -} +}; diff --git a/src/api/native/parse.ts b/src/api/native/parse.ts new file mode 100644 index 0000000..c64c10a --- /dev/null +++ b/src/api/native/parse.ts @@ -0,0 +1,22 @@ +import { pictEntries } from "../../common/pict"; + +export function parseResult(result: string): Array> { + const cases: any[] = []; + + for (const item of pictEntries(result)) { + let currentCase; + + if (item.valueIndex === 0) { + currentCase = {}; + cases.push(currentCase); + } else { + currentCase = cases[cases.length - 1]; + } + + currentCase[item.rowName as string] = item.value; + + cases[cases.length - 1] = currentCase; + } + + return cases; +} diff --git a/src/api/native/utils.ts b/src/api/native/utils.ts new file mode 100644 index 0000000..dd2d574 --- /dev/null +++ b/src/api/native/utils.ts @@ -0,0 +1,17 @@ +import type { ModelSource } from "./types"; +import { isString } from "tsguarder"; +import fsp from "fs/promises"; + +export async function getModelFromSource(source: ModelSource) { + if (isString(source)) { + return source; + } + + try { + const fileContent = await fsp.readFile(source.file); + return fileContent.toString(); + } catch (error) { + console.error(`Error while reading model file. ${source.file}`); + throw error; + } +} diff --git a/src/api/pict/index.ts b/src/api/pict/index.ts index 7d7df22..2579ba9 100644 --- a/src/api/pict/index.ts +++ b/src/api/pict/index.ts @@ -6,6 +6,7 @@ import { InputSeed, InputSubModel, RandomOption, + PictNodeStatistics, } from "../../common/types"; import type { PictTypedModel } from "./types"; import { callPict, CallPictOptions } from "../../common/pict"; @@ -13,67 +14,95 @@ import { isPositiveNumber } from "../../common/utils"; import { createModel } from "./model"; import { createSeed } from "./seed"; import { parseResult } from "./parse"; +import { parseStatistics } from "../../common/statistics"; +import { performance } from "perf_hooks"; -interface AllOptions { +interface PictOptions { order?: number; random?: RandomOption; } -export async function pict>( +type Pict = { + ( + model: { model: M; sub?: readonly InputSubModel[]; seed?: InputSeed }, + options?: PictOptions + ): Promise[]>; + stats: ( + model: { model: M; sub?: readonly InputSubModel[]; seed?: InputSeed }, + options?: PictOptions + ) => Promise; +}; + +export function prepare>( model: { model: M; sub?: ReadonlyArray>; seed?: InputSeed; }, - options?: AllOptions + options?: PictOptions ) { - try { - isRecord.assert(model, "the first argument"); - isArray.assert(model.model, '"model"'); + isRecord.assert(model, "the first argument"); + isArray.assert(model.model, '"model"'); - if (model.model.length < 1) { - throw new Error('"model" must contain at least 1 item'); - } + if (model.model.length < 1) { + throw new Error('"model" must contain at least 1 item'); + } - if (!isUndefined(model.sub)) { - isArray.assert(model.sub, '"sub"'); - } + if (!isUndefined(model.sub)) { + isArray.assert(model.sub, '"sub"'); + } - let validatedOptions: AllOptions = {}; + let validatedOptions: PictOptions = {}; - if (!isUndefined(options)) { - isRecord.assert(options, "the second argument"); - validatedOptions = options; - } + if (!isUndefined(options)) { + isRecord.assert(options, "the second argument"); + validatedOptions = options; + } - const defaultOrder = Math.min(2, model.model.length); + const defaultOrder = Math.min(2, model.model.length); - const { order = defaultOrder, random } = validatedOptions; + const { order = defaultOrder, random } = validatedOptions; - isPositiveNumber.assert(order, '"order"'); + isPositiveNumber.assert(order, '"order"'); - if (order > model.model.length) { - throw new Error('"order" cannot be larger than number of parameters'); - } + if (order > model.model.length) { + throw new Error('"order" cannot be larger than number of parameters'); + } - const { modelText, valuesIdMap } = createModel(model.model, model.sub); + const { modelText, valuesIdMap } = createModel(model.model, model.sub); - const callPictOptions: CallPictOptions = { - modelText, - options: { - order, - }, - }; + const callPictOptions: CallPictOptions = { + modelText, + options: { + order, + }, + }; + + if (!isUndefined(random)) { + isRandomOption.assert(random, "options.random"); + callPictOptions.options.random = random; + } + + if (!isUndefined(model.seed)) { + isInputSeed.assert(model.seed, '"seed"'); + callPictOptions.seedText = createSeed(model.seed, valuesIdMap); + } - if (!isUndefined(random)) { - isRandomOption.assert(random, "options.random"); - callPictOptions.options.random = random; - } + return { callPictOptions, valuesIdMap }; +} - if (!isUndefined(model.seed)) { - isInputSeed.assert(model.seed, '"seed"'); - callPictOptions.seedText = createSeed(model.seed, valuesIdMap); - } +export const pict: Pict = async function pict< + M extends ReadonlyArray +>( + model: { + model: M; + sub?: ReadonlyArray>; + seed?: InputSeed; + }, + options?: PictOptions +) { + try { + const { callPictOptions, valuesIdMap } = prepare(model, options); const result = await callPict(callPictOptions); @@ -82,4 +111,30 @@ export async function pict>( console.error('Error while calling "pict" function.'); throw error; } -} +}; + +pict.stats = async function stats>( + model: { + model: M; + sub?: ReadonlyArray>; + seed?: InputSeed; + }, + options?: PictOptions +) { + try { + const start = performance.now(); + + const { callPictOptions, valuesIdMap } = prepare(model, options); + + callPictOptions.options.statistics = true; + + const result = await callPict(callPictOptions); + + const end = performance.now() - start; + + return parseStatistics(result, end); + } catch (error) { + console.error('Error while calling "pict.stats" function.'); + throw error; + } +}; diff --git a/src/api/pict/model.ts b/src/api/pict/model.ts index 60cc4d6..4f262e7 100644 --- a/src/api/pict/model.ts +++ b/src/api/pict/model.ts @@ -8,7 +8,7 @@ import { isNegativeOperator } from "../../common/operators/negative"; import { isWeightOperator } from "../../common/operators/weight"; /** - * @todo may brake down this function into smaller ones + * Creates PICT model from user's input */ export function createModel>( models: M, diff --git a/src/api/strings/index.ts b/src/api/strings/index.ts index e6a3b48..69dc7fe 100644 --- a/src/api/strings/index.ts +++ b/src/api/strings/index.ts @@ -8,6 +8,7 @@ import { InputPictModelToRecord, InputSeed, InputSubModel, + PictNodeStatistics, } from "../../common/types"; import { callPict, CallPictOptions } from "../../common/pict"; import { isPositiveNumber } from "../../common/utils"; @@ -15,6 +16,8 @@ import { createStringModel } from "./model"; import { createSeed } from "./seed"; import { parseResult } from "./parse"; import { PictStringModel, InputConstraints, isInputConstraints } from "./types"; +import { performance } from "perf_hooks"; +import { parseStatistics } from "../../common/statistics"; export interface StringsOptions { order?: number; @@ -25,7 +28,127 @@ export interface StringsOptions { caseSensitive?: boolean; } -export async function strings>( +type Strings = { + >( + model: { + model: M; + sub?: ReadonlyArray>; + seed?: InputSeed; + constraints?: InputConstraints; + }, + options?: StringsOptions + ): Promise>>; + stats: >( + model: { + model: M; + sub?: ReadonlyArray>; + seed?: InputSeed; + constraints?: InputConstraints; + }, + options?: StringsOptions + ) => Promise; +}; + +function prepare>( + model: { + model: M; + sub?: ReadonlyArray>; + seed?: InputSeed; + constraints?: InputConstraints; + }, + options?: StringsOptions +) { + isRecord.assert(model, "the first argument"); + isArray.assert(model.model, '"model"'); + + if (model.model.length < 1) { + throw new Error('"model" must contain at least 1 item'); + } + + if (!isUndefined(model.sub)) { + isArray.assert(model.sub, '"sub"'); + } + + if (!isUndefined(model.constraints)) { + isInputConstraints.assert(model.constraints, '"constraints"'); + } + + let validatedOptions: StringsOptions = {}; + + if (!isUndefined(options)) { + isRecord.assert(options, "the second argument"); + validatedOptions = options; + } + + const defaultOrder = Math.min(2, model.model.length); + + const { order = defaultOrder, random } = validatedOptions; + + isPositiveNumber.assert(order, '"order"'); + + if (order > model.model.length) { + throw new Error('"order" cannot be larger than number of parameters'); + } + + if (!isUndefined(validatedOptions.aliasSeparator)) { + isModelSeparator.assert( + validatedOptions.aliasSeparator, + '"aliasSeparator"' + ); + } + + if (!isUndefined(validatedOptions.valueSeparator)) { + isModelSeparator.assert( + validatedOptions.valueSeparator, + '"valueSeparator"' + ); + } + + if (!isUndefined(validatedOptions.negativePrefix)) { + isModelSeparator.assert( + validatedOptions.negativePrefix, + '"negativePrefix"' + ); + } + + const { modelText, separators } = createStringModel({ + models: model.model, + subModels: model.sub, + constraints: model.constraints, + aliasSeparator: validatedOptions?.aliasSeparator, + valueSeparator: validatedOptions?.valueSeparator, + negativePrefix: validatedOptions?.negativePrefix, + }); + + const callPictOptions: CallPictOptions = { + modelText, + options: { + order, + ...separators, + }, + }; + + if (!isUndefined(validatedOptions.caseSensitive)) { + isBoolean.assert(validatedOptions.caseSensitive, '"caseSensitive"'); + callPictOptions.options.caseSensitive = validatedOptions.caseSensitive; + } + + if (!isUndefined(random)) { + isRandomOption.assert(random, "options.random"); + callPictOptions.options.random = random; + } + + if (!isUndefined(model.seed)) { + isInputSeed.assert(model.seed, '"seed"'); + callPictOptions.seedText = createSeed(model.seed, model.model); + } + + return callPictOptions; +} + +export const strings: Strings = async function strings< + M extends ReadonlyArray +>( model: { model: M; sub?: ReadonlyArray>; @@ -35,96 +158,42 @@ export async function strings>( options?: StringsOptions ) { try { - isRecord.assert(model, "the first argument"); - isArray.assert(model.model, '"model"'); - - if (model.model.length < 1) { - throw new Error('"model" must contain at least 1 item'); - } - - if (!isUndefined(model.sub)) { - isArray.assert(model.sub, '"sub"'); - } - - if (!isUndefined(model.constraints)) { - isInputConstraints.assert(model.constraints, '"constraints"'); - } - - let validatedOptions: StringsOptions = {}; - - if (!isUndefined(options)) { - isRecord.assert(options, "the second argument"); - validatedOptions = options; - } - - const defaultOrder = Math.min(2, model.model.length); - - const { order = defaultOrder, random } = validatedOptions; - - isPositiveNumber.assert(order, '"order"'); - - if (order > model.model.length) { - throw new Error('"order" cannot be larger than number of parameters'); - } - - if (!isUndefined(validatedOptions.aliasSeparator)) { - isModelSeparator.assert( - validatedOptions.aliasSeparator, - '"aliasSeparator"' - ); - } - - if (!isUndefined(validatedOptions.valueSeparator)) { - isModelSeparator.assert( - validatedOptions.valueSeparator, - '"valueSeparator"' - ); - } - - if (!isUndefined(validatedOptions.negativePrefix)) { - isModelSeparator.assert( - validatedOptions.negativePrefix, - '"negativePrefix"' - ); - } - - const { modelText, separators } = createStringModel({ - models: model.model, - subModels: model.sub, - constraints: model.constraints, - aliasSeparator: validatedOptions?.aliasSeparator, - valueSeparator: validatedOptions?.valueSeparator, - negativePrefix: validatedOptions?.negativePrefix, - }); - - const callPictOptions: CallPictOptions = { - modelText, - options: { - order, - ...separators, - }, - }; - - if (!isUndefined(validatedOptions.caseSensitive)) { - isBoolean.assert(validatedOptions.caseSensitive, '"caseSensitive"'); - callPictOptions.options.caseSensitive = validatedOptions.caseSensitive; - } - - if (!isUndefined(random)) { - isRandomOption.assert(random, "options.random"); - callPictOptions.options.random = random; - } - - if (!isUndefined(model.seed)) { - isInputSeed.assert(model.seed, '"seed"'); - callPictOptions.seedText = createSeed(model.seed, model.model); - } + const callPictOptions = prepare(model, options); const result = await callPict(callPictOptions); return parseResult(result) as Array>; } catch (error) { - console.error('Error while calling "pict" function.'); + console.error('Error while calling "strings" function.'); throw error; } -} +}; + +strings.stats = async function strings< + M extends ReadonlyArray +>( + model: { + model: M; + sub?: ReadonlyArray>; + seed?: InputSeed; + constraints?: InputConstraints; + }, + options?: StringsOptions +) { + try { + const start = performance.now(); + + const callPictOptions = prepare(model, options); + + callPictOptions.options.statistics = true; + + const result = await callPict(callPictOptions); + + const end = performance.now() - start; + + return parseStatistics(result, end); + } catch (error) { + console.error('Error while calling "strings.stats" function.'); + throw error; + } +}; diff --git a/src/common/pict.ts b/src/common/pict.ts index 60b3b61..b95e40c 100644 --- a/src/common/pict.ts +++ b/src/common/pict.ts @@ -19,6 +19,7 @@ export interface CallPictOptions { | "valueSeparator" | "negativePrefix" | "caseSensitive" + | "statistics" > >; } @@ -84,6 +85,10 @@ export async function callPictBinary( } } + if (params.statistics === true) { + cliOptions += ` /s`; + } + try { const result = execSync(`${getBinaryPath()} ${modelPath} ${cliOptions}`); return Promise.resolve(result.toString()); diff --git a/src/common/statistics.ts b/src/common/statistics.ts new file mode 100644 index 0000000..cbadf74 --- /dev/null +++ b/src/common/statistics.ts @@ -0,0 +1,63 @@ +import { EOL } from "os"; +import type { PictNodeStatistics } from "./types"; + +/** + * Parsing and formatting native PICT statistics to JS object + */ + +/** + * Normalizes raw string value and formats keys + */ +function normalizeParameter(key: string, value: string) { + switch (key) { + case "Combinations": + return { + combinations: Number.parseInt(value), + } as const; + + case "Generated tests": + return { + generatedTests: Number.parseInt(value), + } as const; + + case "Generation time": + return { + generationTime: value, + } as const; + + default: + throw new Error("Unexpected statistic key"); + } +} + +/** + * Parses a statistic parameter + */ +function parseParameter(raw: string) { + const dividerIndex = raw.indexOf(":"); + + if (dividerIndex === -1) { + throw new Error("Unexpected statistic value"); + } + + const key = raw.substring(0, dividerIndex); + const value = raw.substring(dividerIndex + 1).trim(); + + return normalizeParameter(key, value); +} + +/** + * Receives raw PICT's statistics output and return normalized statistics + */ +export function parseStatistics(raw: string, generationTimeNodeJs: number) { + return raw + .split(EOL) + .filter(Boolean) + .map(parseParameter) + .reduce>( + (stats, item) => { + return { ...stats, ...item } as PictNodeStatistics; + }, + { generationTimeNodeJs } + ) as PictNodeStatistics; +} diff --git a/src/common/types.ts b/src/common/types.ts index 5f8e47a..0faab04 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -20,6 +20,7 @@ export interface PictCliOptions { negativePrefix: string; caseSensitive: boolean; seeds: string; + statistics: boolean; } export type PickPictCliOptions = Pick< @@ -132,3 +133,10 @@ export const isModelSeparator: TypeGuard = createTypeGuard( return isString(value) && value.length === 1; } ); + +export interface PictNodeStatistics { + combinations: number; + generatedTests: number; + generationTime: number; + generationTimeNodeJs: number; +} diff --git a/test/native.spec.ts b/test/native.spec.ts index 5bafbb4..90f770a 100644 --- a/test/native.spec.ts +++ b/test/native.spec.ts @@ -228,6 +228,29 @@ describe("native()", () => { ]); }); + test("Should ignore statistics field", async () => { + const modelPath = path.resolve(__dirname, "./models/model"); + + const result = await native({ + model: { + file: modelPath, + }, + options: { + // @ts-expect-error + statistics: true, + }, + }); + + expect(result).toHaveLength(4); + + expect(result).toIncludeSameMembers([ + { A: "1", B: "4" }, + { A: "1", B: "3" }, + { A: "2", B: "4" }, + { A: "2", B: "3" }, + ]); + }); + test("The simple model with alias operator and symbol key", async () => { const model = await getTestModelContent("model-alias"); @@ -448,4 +471,23 @@ describe("native()", () => { ]); }); }); + + describe("Statistics", () => { + test("Should return statistics", async () => { + const modelPath = path.resolve(__dirname, "./models/model"); + + const result = await native.stats({ + model: { + file: modelPath, + }, + }); + + expect(result).toEqual({ + generationTimeNodeJs: expect.any(Number), + combinations: 4, + generatedTests: 4, + generationTime: expect.any(String), + }); + }); + }); }); diff --git a/test/pict.spec.ts b/test/pict.spec.ts index e08ef83..944ff01 100644 --- a/test/pict.spec.ts +++ b/test/pict.spec.ts @@ -795,4 +795,28 @@ describe("pict()", () => { ]); }); }); + + describe("Statistics", () => { + test("Should return statistics", async () => { + const model = [ + { + key: "Platform", + values: ["x86", "x64", "arm"], + }, + { + key: "CPUS", + values: ["1", "2", "4"], + }, + ] as const; + + const result = await pict.stats({ model }); + + expect(result).toEqual({ + generationTimeNodeJs: expect.any(Number), + combinations: 9, + generatedTests: 9, + generationTime: expect.any(String), + }); + }); + }); }); diff --git a/test/strings.spec.ts b/test/strings.spec.ts index d5b68da..2c665ae 100644 --- a/test/strings.spec.ts +++ b/test/strings.spec.ts @@ -998,4 +998,39 @@ describe("strings()", () => { ]); }); }); + + describe("Statistisc", () => { + test("Should return statistics", async () => { + const model = [ + { + key: "Type", + values: ["Single", "Span", "Stripe"], + }, + { + key: "Size", + values: ["10", "100", "500"], + }, + { + key: "FormatMethod", + values: ["Quick", "Slow", "VerySlow"], + }, + ]; + + const result = await strings.stats( + { + model, + }, + { + order: model.length, + } + ); + + expect(result).toEqual({ + generationTimeNodeJs: expect.any(Number), + combinations: 27, + generatedTests: 27, + generationTime: expect.any(String), + }); + }); + }); }); From 39da61cfe521eb39098d3cdabcd748c20c22b002 Mon Sep 17 00:00:00 2001 From: Max Levytskyi Date: Sun, 22 Oct 2023 22:48:54 +0300 Subject: [PATCH 2/3] feat: statistics docs --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index d8643f1..6848f6d 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,9 @@ PICT will generate the following test cases: In most cases, to generate test cases, you can use the `pict` function. The main features of this function is that you can use any data type for the values of the model. ```js +import { pict } from "pict-node"; +import { createOrder } from "./src"; + // Define test model const model = [ { @@ -476,6 +479,38 @@ const cases = native({ }); ``` +## Statistics + +You can obtain model statistics using the `stats` method. + +This method is accessible through the `pict`, `strings`, and `native` APIs. + +```js +import { pict } from "pict-node"; + +const model = [ + { + key: "platform", + values: ["x86", "x64", "arm"], + }, + { + key: "ram", + values: [1, 4, 64], + }, +]; + +const stats = await pict.stats({ + model, +}); +``` + +The `stats` method returns an object with the following fields: + +- `generationTimeNodeJs` - model generation time (including Node.js processing time) +- `generationTime` - model generation time (excluding Node.js processing time) +- `combinations` - number of combinations +- `generatedTests` - number of generated tests + ## License [MIT](LICENSE) From d4a388f18c3841529a234cfe08ea92b89909a1f3 Mon Sep 17 00:00:00 2001 From: Max Levytskyi Date: Sun, 22 Oct 2023 22:53:58 +0300 Subject: [PATCH 3/3] v1.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82338dd..1fa40ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pict-node", - "version": "1.2.1", + "version": "1.3.0", "description": "Combinatorial Test Case Generation", "keywords": [ "pict",