diff --git a/.eslintrc.json b/.eslintrc.json index 915d1a42cc907d01b1e35bb2d47219144436f34f..f5ca99a3bea9c559bd4d9cdd685d35552822eb90 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -42,7 +42,6 @@ "@typescript-eslint/semi": ["error", "always"], "@typescript-eslint/indent": ["error", 2], "@typescript-eslint/method-signature-style": ["error", "method"], - "@typescript-eslint/naming-convention": "error", "@typescript-eslint/no-duplicate-enum-values": "error", "@typescript-eslint/restrict-plus-operands": "error", "@typescript-eslint/typedef": [ diff --git a/k6_modules/SummaryData.d.ts b/k6_modules/SummaryData.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..423da1ab45edf8efa86e24dca4f889149f7870c8 --- /dev/null +++ b/k6_modules/SummaryData.d.ts @@ -0,0 +1,112 @@ +declare module "k6/data" { + type Options = { + noColor: boolean; + summaryTrendStats: string[]; + summaryTimeUnit: string; + }; + + type State = { + isStdOutTTY: boolean; + isStdErrTTY: boolean; + testRunDurationMs: number; + }; + + type Metrics = { + requests_per_second: { + contains: string; + values: { + rate: number; + passes: number; + fails: number; + }; + type: string; + }; + iterations: { + type: string; + contains: string; + values: { + rate: number; + count: number; + }; + }; + data_sent: { + type: string; + contains: string; + values: { + count: number; + rate: number; + }; + }; + data_received: { + type: string; + contains: string; + values: { + count: number; + rate: number; + }; + }; + iteration_duration: { + type: string; + contains: string; + values: { + med: number; + max: number; + p90: number; + p95: number; + avg: number; + min: number; + }; + }; + response_time: { + type: string; + contains: string; + values: { + min: number; + med: number; + max: number; + p90: number; + p95: number; + avg: number; + }; + }; + total_requests: { + type: string; + contains: string; + values: { + rate: number; + count: number; + }; + }; + vus: { + min: number; + max: number; + value: number; + }; + vus_max: { + value: number; + min: number; + max: number; + }; + }; + + type Group = { + name: string; + path: string; + id: string; + groups: Group[]; + }; + + type Root = { + name: string; + path: string; + id: string; + groups: Group[]; + }; + + export type SummaryData = { + options: Options; + state: State; + metrics: Metrics; + root_group: Root; + }; +} diff --git a/k6_modules/k6Utils.d.ts b/k6_modules/k6Utils.d.ts deleted file mode 100644 index 838205170d76499cd63774469463bfe31dfcc652..0000000000000000000000000000000000000000 --- a/k6_modules/k6Utils.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Declaring modules like this allows to declare types for k6 libraries -declare module "https://jslib.k6.io/k6-utils/1.4.0/index.js" { - interface K6Utils { - randomItem<T>(array: Array<T>): T; - } - - const package: K6Utils; - - export = package; -} diff --git a/src/App.ts b/src/App.ts index a9712d09b4eb3fd276ac83a7c9d38d77c52e1e30..1887cc58d8e579164eaa9129d2f56487667b17f3 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,5 +1,4 @@ -/* eslint-disable import/no-unresolved */ -/* eslint-disable @typescript-eslint/typedef */ +import { SummaryData } from "k6/data"; import IPROTO, { TarantoolConnection } from "k6/x/tarantool"; import Metrics from "../src/Metrics"; @@ -26,7 +25,7 @@ if (CLIENTS === undefined) { // Connection pool of clients const connections: TarantoolConnection[] = CLIENTS.map((host: string) => IPROTO.connect([host])); // Parsed and validated scenario.yml -const scenario: Scenario = new ScenarioParser(SCENARIO_PATH).parse(); +const scenario: Scenario = new ScenarioParser(open(SCENARIO_PATH)).parse(); // Generated SQL queue from scenario.yml const queryQueue: SQLQueue = new SQLQueue(SEED, scenario.queries, QUERY_REQUEST_AMOUNT); // Metrics object, that counts requests and measures responce time @@ -34,11 +33,7 @@ const metrics: Metrics = new Metrics(); // Array of generated data for each column const columnsGeneratedData: Array<Column> = []; -export const setup = () => { - console.info(`SCENARIO: ${scenario.name}`); - console.info(`SEED: ${SEED}`); - //console.log(queryQueue.queue); - +export const setup: () => void = () => { // Itterate through array of Table classes for (const table of scenario.tables) { table.create(getClient(connections)); @@ -69,10 +64,42 @@ export default () => { } }; -export const teardown = () => { +export const teardown: () => void = () => { // Itterate through array of Table classes for (const table of scenario.tables) { // Drop table table.drop(getClient(connections)); } }; + +export function handleSummary(data: SummaryData) { + console.log("--- Test Summary ---"); + const summary: object = { + seed: SEED, + reqsSent: QUERY_REQUEST_AMOUNT, + reqsActual: data.metrics.total_requests.values.count, + totalReqs: { + ratePerSec: data.metrics.total_requests.values.rate, + count: data.metrics.total_requests.values.count + }, + reqs: { + pass: data.metrics.requests_per_second.values.passes, + fail: data.metrics.requests_per_second.values.fails, + rate: data.metrics.requests_per_second.values.rate + }, + responceTime: { + min: data.metrics.response_time.values.min, + max: data.metrics.response_time.values.max, + avg: data.metrics.response_time.values.avg.toFixed(2) + }, + iterationDurationAvg: data.metrics.iteration_duration.values.avg.toFixed(2) + }; + return { + stdout: JSON.stringify(summary, null, 2) + .replace(/"/g, "") + .replace(/[{}]/g, "") + .replace(/,/g, "") + .replace(/(\n\s*\n)+/g, "\n"), + [`summary/summary_${SEED}.json`]: JSON.stringify(summary, null, 2) + }; +} diff --git a/src/Column.ts b/src/Column.ts index 6a5c4a8630e0e33edcc56c42b1a06b7c59330962..c07664221471a5c4ea7348ec5f686b7445eb6c87 100644 --- a/src/Column.ts +++ b/src/Column.ts @@ -1,6 +1,7 @@ -import { IDistribution } from "./distributions/IDistribution"; +import { DistributionOpts, IDistribution } from "./distributions/IDistribution"; import NormalDistribution from "./distributions/NormalDistribution"; import UniformDistribution from "./distributions/UniformDistribution"; +import ZipfDistribution from "./distributions/ZipfDistribution"; import { BooleanType } from "./types/Boolean"; import { DecimalType } from "./types/Decimal"; import { SignedIntType } from "./types/SignedInt"; @@ -34,6 +35,8 @@ export abstract class AbstractColumn<T> implements IColumn<T> { return useSingleton ? NormalDistribution.getInstance(seed, this.type) : new NormalDistribution(seed); case "UNIFORM": return useSingleton ? UniformDistribution.getInstance(seed, this.type) : new UniformDistribution(seed); + case "ZIPF": + return useSingleton ? ZipfDistribution.getInstance(seed, this.type) : new ZipfDistribution(seed); default: throw new Error("Column : getDistribution: Distribution not supported"); } @@ -45,22 +48,22 @@ export abstract class AbstractColumn<T> implements IColumn<T> { export class StringColumn extends AbstractColumn<string> { readonly range: [number, number]; readonly alphabet: string[]; - readonly unique: boolean; + readonly distributionOpts: DistributionOpts; constructor( name: string, type: string, distribution: string, + distributionOpts: DistributionOpts, range: [number, number], alphabet: string[], isNullable: boolean, - nullPercentage: number, - unique: boolean + nullPercentage: number ) { super(name, type, distribution, isNullable, nullPercentage); this.range = range; this.alphabet = alphabet; - this.unique = unique; + this.distributionOpts = distributionOpts; } public generateData(seed: number, size: number, useSingleton: boolean): Array<string | null> { @@ -70,29 +73,29 @@ export class StringColumn extends AbstractColumn<string> { : new StringType(distribution, this.range, this.alphabet); if (this.isNullable === true) { - return typeInstance.generateArrayWithNull(size, this.nullPercentage, this.unique); + return typeInstance.generateArrayWithNull(size, this.nullPercentage, this.distributionOpts); } else { - return typeInstance.generateArrayWithoutNull(size, this.unique); + return typeInstance.generateArrayWithoutNull(size, this.distributionOpts); } } } export class UnsignedColumn extends AbstractColumn<number> { readonly range: [number, number]; - readonly unique: boolean; + readonly distributionOpts: DistributionOpts; constructor( name: string, type: string, distribution: string, + distributionOpts: DistributionOpts, range: [number, number], isNullable: boolean, - nullPercentage: number, - unique: boolean + nullPercentage: number ) { super(name, type, distribution, isNullable, nullPercentage); this.range = range; - this.unique = unique; + this.distributionOpts = distributionOpts; } public generateData(seed: number, size: number, useSingleton: boolean): Array<number | null> { @@ -102,9 +105,9 @@ export class UnsignedColumn extends AbstractColumn<number> { : new UnsignedIntType(distribution, this.range); if (this.isNullable === true) { - return typeInstance.generateArrayWithNull(size, this.nullPercentage, this.unique); + return typeInstance.generateArrayWithNull(size, this.nullPercentage, this.distributionOpts); } else { - return typeInstance.generateArrayWithoutNull(size, this.unique); + return typeInstance.generateArrayWithoutNull(size, this.distributionOpts); } } } @@ -114,12 +117,12 @@ export class IntegerColumn extends UnsignedColumn { name: string, type: string, distribution: string, + distributionOpts: DistributionOpts, range: [number, number], isNullable: boolean, - nullPercentage: number, - unique: boolean + nullPercentage: number ) { - super(name, type, distribution, range, isNullable, nullPercentage, unique); + super(name, type, distribution, distributionOpts, range, isNullable, nullPercentage); } public generateData(seed: number, size: number, useSingleton: boolean): Array<number | null> { @@ -129,9 +132,9 @@ export class IntegerColumn extends UnsignedColumn { : new SignedIntType(distribution, this.range); if (this.isNullable === true) { - return typeInstance.generateArrayWithNull(size, this.nullPercentage, this.unique); + return typeInstance.generateArrayWithNull(size, this.nullPercentage, this.distributionOpts); } else { - return typeInstance.generateArrayWithoutNull(size, this.unique); + return typeInstance.generateArrayWithoutNull(size, this.distributionOpts); } } } @@ -141,12 +144,12 @@ export class DecimalColumn extends IntegerColumn { name: string, type: string, distribution: string, + distributionOpts: DistributionOpts, range: [number, number], isNullable: boolean, - nullPercentage: number, - unique: boolean + nullPercentage: number ) { - super(name, type, distribution, range, isNullable, nullPercentage, unique); + super(name, type, distribution, distributionOpts, range, isNullable, nullPercentage); } public generateData(seed: number, size: number, useSingleton: boolean): Array<number | null> { @@ -156,16 +159,25 @@ export class DecimalColumn extends IntegerColumn { : new DecimalType(distribution, this.range); if (this.isNullable === true) { - return typeInstance.generateArrayWithNull(size, this.nullPercentage, this.unique); + return typeInstance.generateArrayWithNull(size, this.nullPercentage, this.distributionOpts); } else { - return typeInstance.generateArrayWithoutNull(size, this.unique); + return typeInstance.generateArrayWithoutNull(size, this.distributionOpts); } } } export class BooleanColumn extends AbstractColumn<boolean> { - constructor(name: string, type: string, distribution: string, isNullable: boolean, nullPercentage: number) { + readonly distributionOpts: DistributionOpts; + constructor( + name: string, + type: string, + distribution: string, + distributionOpts: DistributionOpts, + isNullable: boolean, + nullPercentage: number + ) { super(name, type, distribution, isNullable, nullPercentage); + this.distributionOpts = distributionOpts; } public generateData(seed: number, size: number, useSingleton: boolean): Array<boolean | null> { @@ -175,9 +187,9 @@ export class BooleanColumn extends AbstractColumn<boolean> { : new BooleanType(distribution); if (this.isNullable === true) { - return typeInstance.generateArrayWithNull(size, this.nullPercentage); + return typeInstance.generateArrayWithNull(size, this.nullPercentage, this.distributionOpts); } else { - return typeInstance.generateArrayWithoutNull(size); + return typeInstance.generateArrayWithoutNull(size, this.distributionOpts); } } } diff --git a/src/Cutter.ts b/src/Cutter.ts index 5388b928a1f21574e372587428a50a883fda2d17..ce73409258282db67f885926f69237b232985194 100644 --- a/src/Cutter.ts +++ b/src/Cutter.ts @@ -1,7 +1,7 @@ import { ITape } from "./Tape"; export interface ICutter { - generateWords(amount: number, uniqueWords: boolean): string[]; + generateWords(amount: number, uniqueWords?: boolean): string[]; } /** diff --git a/src/ScenarioParser.ts b/src/ScenarioParser.ts index 12e34053903311a1b27f62bc4b13eac3410d63e6..fc50f8db6ceb64ea06faf2f31a22cf89dddd6c49 100644 --- a/src/ScenarioParser.ts +++ b/src/ScenarioParser.ts @@ -10,6 +10,7 @@ import YAML from "k6/x/yaml"; import { AbstractColumn, BooleanColumn, DecimalColumn, IntegerColumn, StringColumn, UnsignedColumn } from "./Column"; import { Query } from "./Query"; import { Table } from "./Table"; +import { PrimitiveTypes } from "./types/IType"; export type Scenario = { name: string; @@ -23,126 +24,155 @@ function newScenario(name: string, tables: Table[], queries: Query[]): Scenario export class ScenarioParser { private _parsedYAML: string; - constructor(scenarioPath: string) { - this._parsedYAML = YAML.parse(open(scenarioPath)); + constructor(yaml: string) { + this._parsedYAML = YAML.parse(yaml); this._validateScenario(); } private _validateTag(tagName: string): void { - if (this._parsedYAML[tagName] !== undefined) { - if (this._parsedYAML[tagName] === null) throw new Error(`Missing ${tagName} tag value`); - } else { - throw new Error(`Missing ${tagName} tag`); - } + if (this._parsedYAML[tagName] === undefined) throw new Error(`Missing ${tagName} tag`); + if (this._parsedYAML[tagName] === null) throw new Error(`Missing ${tagName} tag value`); } - private _validateTableField(i: number, fieldName: string): void { - if (this._parsedYAML["tables_schema"][i][fieldName] !== undefined) { - if (this._parsedYAML["tables_schema"][i][fieldName] === null) - throw new Error(`Table ${i + 1}: Missing ${fieldName} value`); - } else { + private _validateTableField(i: number, fieldName: string, fieldType: string): void { + if (this._parsedYAML["tables_schema"][i][fieldName] === undefined) { throw new Error(`Table ${i + 1}: Missing ${fieldName} field`); } + if (this._parsedYAML["tables_schema"][i][fieldName] === null) { + throw new Error(`Table ${i + 1}: Missing ${fieldName} value`); + } + if (typeof this._parsedYAML["tables_schema"][i][fieldName] !== fieldType) { + throw new Error(`Table ${i + 1}: ${fieldName} field is not a ${fieldType}`); + } } - private _validateTableColumnField(i: number, j: number, columnName: string): void { - if (this._parsedYAML["tables_schema"][i]["columns"][j][columnName] !== undefined) { - if (this._parsedYAML["tables_schema"][i]["columns"][j][columnName] === null) - throw new Error(`Table ${i + 1}, Column ${j + 1}: Missing ${columnName} value`); - } else { + private _validateTableColumnField(i: number, j: number, columnName: string, columnType: string): void { + if (this._parsedYAML["tables_schema"][i]["columns"][j][columnName] === undefined) { throw new Error(`Table ${i + 1}, Column ${j + 1}: Missing ${columnName} field`); } + if (this._parsedYAML["tables_schema"][i]["columns"][j][columnName] === null) { + throw new Error(`Table ${i + 1}, Column ${j + 1}: Missing ${columnName} value`); + } + if (typeof this._parsedYAML["tables_schema"][i]["columns"][j][columnName] !== columnType) { + throw new Error(`Table ${i + 1}, Column ${j + 1}: ${columnName} field is not a ${columnType}`); + } } - private _validateQueriesField(i: number, fieldName: string): void { - if (this._parsedYAML["queries"][i][fieldName] !== undefined) { - if (this._parsedYAML["queries"][i][fieldName] === null) - throw new Error(`Query ${i + 1}: Missing ${fieldName} value`); - } else { + private _validateQueriesField(i: number, fieldName: string, fieldType: string): void { + if (this._parsedYAML["queries"][i][fieldName] === undefined) { throw new Error(`Query ${i + 1}: Missing ${fieldName} field`); } + if (this._parsedYAML["queries"][i][fieldName] === null) { + throw new Error(`Query ${i + 1}: Missing ${fieldName} value`); + } + if (typeof this._parsedYAML["queries"][i][fieldName] !== fieldType) { + throw new Error(`Query ${i + 1}: ${fieldName} field is not a ${fieldType}`); + } } - private _validateQueriesParamsTypesFields(i: number, j: number, fieldname: string): void { - if (this._parsedYAML["queries"][i]["params"][j][fieldname] !== undefined) { - if (this._parsedYAML["queries"][i]["params"][j][fieldname] === null) { - throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Missing ${fieldname} value`); - } - } else { - throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Missing ${fieldname} field`); + private _validateQueriesParamsTypesFields(i: number, j: number, fieldName: string, fieldType: string): void { + if (this._parsedYAML["queries"][i]["params"][j][fieldName] === undefined) { + throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Missing ${fieldName} field`); + } + if (this._parsedYAML["queries"][i]["params"][j][fieldName] === null) { + throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Missing ${fieldName} value`); + } + if (typeof this._parsedYAML["queries"][i]["params"][j][fieldName] !== fieldType) { + throw new Error(`Query ${i + 1}, Parameter ${j + 1}: ${fieldName} field is not a ${fieldType}`); } } private _validateQueriesParamsField(i: number, j: number): void { - if (this._parsedYAML["queries"][i]["params"][j] !== undefined) { - if (this._parsedYAML["queries"][i]["params"][j] !== null) { - if (this._parsedYAML["queries"][i]["params"][j]["value"] !== undefined) { - return; - } else if (this._parsedYAML["queries"][i]["params"][j]["type"] !== undefined) { - if (this._parsedYAML["queries"][i]["params"][j]["type"] !== null) { - this._validateQueriesParamsTypesFields(i, j, "distribution"); - switch (this._parsedYAML["queries"][i]["params"][j]["type"].toUpperCase()) { - case "STRING": - this._validateQueriesParamsTypesFields(i, j, "range"); - this._validateQueriesParamsTypesFields(i, j, "alphabet"); - break; - case "UNSIGNED": - case "INTEGER": - case "DECIMAL": - this._validateQueriesParamsTypesFields(i, j, "range"); - break; - case "BOOLEAN": - break; - default: - throw new Error(`Query ${i + 1}, Parameter ${j + 1}:: Unknown parameter type`); - } - this._validateQueriesParamsTypesFields(i, j, "is_nullable"); - this._validateQueriesParamsTypesFields(i, j, "null_percentage"); - } else { - throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Missing type value`); - } - } else { - throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Missing type field`); - } - } else { - throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Missing value`); - } - } else { + if (this._parsedYAML["queries"][i]["params"][j] === undefined) { throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Missing field`); } + if (this._parsedYAML["queries"][i]["params"][j] === null) { + throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Missing value`); + } + //if element is predefined + if (this._parsedYAML["queries"][i]["params"][j]["value"] !== undefined) { + return; + } + //validate type class fields + this._validateQueriesParamsTypesFields(i, j, "type", "string"); + switch (this._parsedYAML["queries"][i]["params"][j]["type"].toUpperCase()) { + case "STRING": + this._validateQueriesParamsTypesFields(i, j, "range", "object"); + this._validateQueriesParamsTypesFields(i, j, "alphabet", "object"); + break; + case "UNSIGNED": + case "INTEGER": + case "DECIMAL": + this._validateQueriesParamsTypesFields(i, j, "range", "object"); + break; + case "BOOLEAN": + break; + default: + throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Unsupported type`); + } + this._validateQueriesParamsTypesFields(i, j, "is_nullable", "boolean"); + this._validateQueriesParamsTypesFields(i, j, "null_percentage", "number"); + this._validateQueriesParamsTypesFields(i, j, "distribution", "string"); + switch (this._parsedYAML["queries"][i]["params"][j]["distribution"].toUpperCase()) { + case "NORMAL": + case "UNIFORM": + break; + case "ZIPF": + this._validateQueriesParamsTypesFields(i, j, "distribution_param", "number"); + break; + default: + throw new Error(`Query ${i + 1}, Parameter ${j + 1}: Unsupported distribution`); + } } private _validateScenario(): void { + //****************************************VALIDATE*SCENARIO************************************************* let primaryKeyColumnName: string; // validate scenario_name tag this._validateTag("scenario_name"); // validate tables_schema tag this._validateTag("tables_schema"); - // validate tables_schema fields + //****************************************VALIDATE*TABLES************************************************** for (let i: number = 0; i < this._parsedYAML["tables_schema"].length; i++) { - this._validateTableField(i, "name"); - this._validateTableField(i, "row_count"); - this._validateTableField(i, "columns"); - this._validateTableField(i, "primary_key"); + this._validateTableField(i, "name", "string"); + this._validateTableField(i, "row_count", "number"); + this._validateTableField(i, "columns", "object"); + this._validateTableField(i, "primary_key", "string"); primaryKeyColumnName = this._parsedYAML["tables_schema"][i]["primary_key"]; - this._validateTableField(i, "distribution_key"); - this._validateTableField(i, "timeout"); - // validate table[i] columns + this._validateTableField(i, "distribution_key", "string"); + this._validateTableField(i, "timeout", "number"); + //************************************END*VALIDATE*TABLES************************************************** + + //****************************************VALIDATE*COLUMNS************************************************* for (let j: number = 0; j < this._parsedYAML["tables_schema"][i]["columns"].length; j++) { - this._validateTableColumnField(i, j, "name"); - this._validateTableColumnField(i, j, "type"); + this._validateTableColumnField(i, j, "name", "string"); + this._validateTableColumnField(i, j, "type", "string"); // each type may own its unique fields switch (this._parsedYAML["tables_schema"][i]["columns"][j]["type"].toUpperCase()) { case "STRING": - this._validateTableColumnField(i, j, "range"); - this._validateTableColumnField(i, j, "alphabet"); - this._validateTableColumnField(i, j, "unique"); + this._validateTableColumnField(i, j, "range", "object"); + this._validateTableColumnField(i, j, "alphabet", "object"); break; case "UNSIGNED": case "INTEGER": case "DECIMAL": - this._validateTableColumnField(i, j, "range"); - this._validateTableColumnField(i, j, "unique"); + this._validateTableColumnField(i, j, "range", "object"); + break; + // Boolean type doesn't have unique fields + case "BOOLEAN": + break; + default: + throw new Error(`Table ${i + 1}, Column ${j + 1}: Unknown column type`); + } + //****************************************VALIDATE*DISTRIBUTIONS************************************************** + this._validateTableColumnField(i, j, "distribution", "string"); + switch (this._parsedYAML["tables_schema"][i]["columns"][j]["distribution"].toUpperCase()) { + case "NORMAL": + case "UNIFORM": + if (this._parsedYAML["tables_schema"][i]["columns"][j]["type"].toUpperCase() === "BOOLEAN") { + break; + } + this._validateTableColumnField(i, j, "unique", "boolean"); // Check if row_count is less or equal to items count from specified range if ( this._parsedYAML["tables_schema"][i]["columns"][j]["unique"] === true && @@ -159,15 +189,15 @@ export class ScenarioParser { ); } break; - // Boolean type doesn't have unique fields - case "BOOLEAN": + case "ZIPF": + this._validateTableColumnField(i, j, "distribution_param", "number"); break; default: - throw new Error(`Table ${i + 1}, Column ${j + 1}: Unknown column type`); + throw new Error(`Table ${i + 1}, Column ${j + 1}: Unknown distribution name`); } - this._validateTableColumnField(i, j, "distribution"); - this._validateTableColumnField(i, j, "is_nullable"); - this._validateTableColumnField(i, j, "null_percentage"); + //************************************END*VALIDATE*DISTRIBUTIONS************************************************** + this._validateTableColumnField(i, j, "is_nullable", "boolean"); + this._validateTableColumnField(i, j, "null_percentage", "number"); // Check if null_percentage equals zero if is_nullable is false if ( this._parsedYAML["tables_schema"][i]["columns"][j]["is_nullable"] === false && @@ -185,29 +215,36 @@ export class ScenarioParser { } } } - // validate queries tag + //************************************END*VALIDATE*COLUMNS************************************************* + + //****************************************VALIDATE*QUERIES************************************************* this._validateTag("queries"); // validate queries fields for (let i: number = 0; i < this._parsedYAML["queries"].length; i++) { - this._validateQueriesField(i, "sql"); - this._validateQueriesField(i, "params"); - this._validateQueriesField(i, "weight"); - if (typeof this._parsedYAML["queries"][i]["params"] === "object") { - if (this._parsedYAML["queries"][i]["params"].length === 0) { - continue; - } else if (this._parsedYAML["queries"][i]["params"].length > 0) { - for (let j: number = 0; j < this._parsedYAML["queries"][i]["params"].length; j++) { - this._validateQueriesParamsField(i, j); - } + this._validateQueriesField(i, "sql", "string"); + this._validateQueriesField(i, "params", "object"); + this._validateQueriesField(i, "weight", "number"); + if (this._parsedYAML["queries"][i]["params"].length === 0) { + continue; + } else if ( + (this._parsedYAML["queries"][i]["params"].length > 0 && + this._parsedYAML["queries"][i]["params"][0]["type"] !== undefined) || + this._parsedYAML["queries"][i]["params"][0]["value"] !== undefined + ) { + for (let j: number = 0; j < this._parsedYAML["queries"][i]["params"].length; j++) { + this._validateQueriesParamsField(i, j); } } else { - throw new Error(`Query ${i + 1}: Unexpected params content`); + throw new Error(`Query ${i + 1}: params field must contain predefined value or type fields`); } } + //************************************END*VALIDATE*QUERIES************************************************* } + //************************************END*VALIDATE*SCENARIO************************************************* } - private _parseColumns(i: number): AbstractColumn<string | number | boolean | null>[] { - const columns: AbstractColumn<string | number | boolean | null>[] = []; + + private _parseColumns(i: number): AbstractColumn<PrimitiveTypes>[] { + const columns: AbstractColumn<PrimitiveTypes>[] = []; const tableColumns: string = this._parsedYAML["tables_schema"][i]["columns"]; @@ -219,11 +256,11 @@ export class ScenarioParser { tableColumns[j]["name"], tableColumns[j]["type"], tableColumns[j]["distribution"], + { unique: tableColumns[j]["unique"], parameter: tableColumns[j]["distribution_param"] }, tableColumns[j]["range"], tableColumns[j]["alphabet"], tableColumns[j]["is_nullable"], - tableColumns[j]["null_percentage"], - tableColumns[j]["unique"] + tableColumns[j]["null_percentage"] ) ); break; @@ -233,10 +270,10 @@ export class ScenarioParser { tableColumns[j]["name"], tableColumns[j]["type"], tableColumns[j]["distribution"], + { unique: tableColumns[j]["unique"], parameter: tableColumns[j]["distribution_param"] }, tableColumns[j]["range"], tableColumns[j]["is_nullable"], - tableColumns[j]["null_percentage"], - tableColumns[j]["unique"] + tableColumns[j]["null_percentage"] ) ); break; @@ -246,10 +283,10 @@ export class ScenarioParser { tableColumns[j]["name"], tableColumns[j]["type"], tableColumns[j]["distribution"], + { unique: tableColumns[j]["unique"], parameter: tableColumns[j]["distribution_param"] }, tableColumns[j]["range"], tableColumns[j]["is_nullable"], - tableColumns[j]["null_percentage"], - tableColumns[j]["unique"] + tableColumns[j]["null_percentage"] ) ); break; @@ -259,10 +296,10 @@ export class ScenarioParser { tableColumns[j]["name"], tableColumns[j]["type"], tableColumns[j]["distribution"], + { unique: tableColumns[j]["unique"], parameter: tableColumns[j]["distribution_param"] }, tableColumns[j]["range"], tableColumns[j]["is_nullable"], - tableColumns[j]["null_percentage"], - tableColumns[j]["unique"] + tableColumns[j]["null_percentage"] ) ); break; @@ -272,6 +309,7 @@ export class ScenarioParser { tableColumns[j]["name"], tableColumns[j]["type"], tableColumns[j]["distribution"], + { parameter: tableColumns[j]["distribution_param"] }, tableColumns[j]["is_nullable"], tableColumns[j]["null_percentage"] ) @@ -306,7 +344,7 @@ export class ScenarioParser { if (query["weight"] < 0) { throw new Error(`Parser: Query ${query["sql"]} weight must be greater or equal zero`); } - const params: ((string | number | boolean | null) | AbstractColumn<string | number | boolean | null>)[] = []; + const params: (PrimitiveTypes | AbstractColumn<PrimitiveTypes>)[] = []; for (const param of query["params"]) { if (param["value"] !== undefined) params.push(param["value"]); else if (param["type"]) { @@ -317,11 +355,11 @@ export class ScenarioParser { "", param["type"], param["distribution"], + { unique: false, parameter: param["distribution_param"] }, param["range"], param["alphabet"], param["is_nullable"], - param["null_percentage"], - false + param["null_percentage"] ) ); break; @@ -331,10 +369,10 @@ export class ScenarioParser { "", param["type"], param["distribution"], + { unique: false, parameter: param["distribution_param"] }, param["range"], param["is_nullable"], - param["null_percentage"], - false + param["null_percentage"] ) ); break; @@ -344,10 +382,10 @@ export class ScenarioParser { "", param["type"], param["distribution"], + { unique: false, parameter: param["distribution_param"] }, param["range"], param["is_nullable"], - param["null_percentage"], - false + param["null_percentage"] ) ); break; @@ -357,10 +395,10 @@ export class ScenarioParser { "", param["type"], param["distribution"], + { unique: false, parameter: param["distribution_param"] }, param["range"], param["is_nullable"], - param["null_percentage"], - false + param["null_percentage"] ) ); break; @@ -370,6 +408,7 @@ export class ScenarioParser { "", param["type"], param["distribution"], + { parameter: param["distribution_param"] }, param["is_nullable"], param["null_percentage"] ) diff --git a/src/distributions/IDistribution.ts b/src/distributions/IDistribution.ts index e660c60b9e3e5ec73eb55301305931bc1180d262..e4acdb4d7ed3610efd9e9cb2568321e3312c26f4 100644 --- a/src/distributions/IDistribution.ts +++ b/src/distributions/IDistribution.ts @@ -2,6 +2,11 @@ import Taus113PRNG from "../../third_party/randomjs/Taus113PRNG"; import { IPRNG } from "../prng/IPRNG"; import { IRange } from "../Range"; +export type DistributionOpts = { + unique?: boolean; + parameter?: number; +}; + // Interface + Abstract class tandem is used to provide // single contract for all future Distribution classes. // Interfaces contains essential methods. @@ -9,10 +14,10 @@ import { IRange } from "../Range"; // that are common for each future child classes export interface IDistribution { readonly seed: number; - randomInt(range: IRange<number>): number; - randomFloat(range: IRange<number>): number; - randomIntArray(size: number, range: IRange<number>, unique: boolean): number[]; - randomFloatArray(size: number, range: IRange<number>, unique: boolean): number[]; + randomInt(range: IRange<number>, opts?: DistributionOpts): number; + randomFloat(range: IRange<number>, opts?: DistributionOpts): number; + randomIntArray(size: number, range: IRange<number>, opts?: DistributionOpts): number[]; + randomFloatArray(size: number, range: IRange<number>, opts?: DistributionOpts): number[]; } export abstract class AbstractDistribution implements IDistribution { @@ -25,8 +30,8 @@ export abstract class AbstractDistribution implements IDistribution { } abstract get seed(): number; - abstract randomInt(range: IRange<number>): number; - abstract randomFloat(range: IRange<number>): number; - abstract randomIntArray(size: number, range: IRange<number>, unique: boolean): number[]; - abstract randomFloatArray(size: number, range: IRange<number>, unique: boolean): number[]; + abstract randomInt(range: IRange<number>, opts?: DistributionOpts): number; + abstract randomFloat(range: IRange<number>, opts?: DistributionOpts): number; + abstract randomIntArray(size: number, range: IRange<number>, opts?: DistributionOpts): number[]; + abstract randomFloatArray(size: number, range: IRange<number>, opts?: DistributionOpts): number[]; } diff --git a/src/distributions/NormalDistribution.ts b/src/distributions/NormalDistribution.ts index b32253ececfa771ed3ddab3bed93c6ab931dd55d..793328a66e90c9e6580f2fea96a5bd9bd2fb852a 100644 --- a/src/distributions/NormalDistribution.ts +++ b/src/distributions/NormalDistribution.ts @@ -1,5 +1,5 @@ import { IRange } from "../Range"; -import { AbstractDistribution } from "./IDistribution"; +import { AbstractDistribution, DistributionOpts } from "./IDistribution"; /** * Class generates normally distributed numbers in N(0,1) or within specified range. @@ -76,7 +76,7 @@ export default class NormalDistribution extends AbstractDistribution { return Math.round(this.randomFloat(range)); } - public randomFloatArray(size: number, range: IRange<number>, unique: boolean): number[] { + public randomFloatArray(size: number, range: IRange<number>, { unique }: DistributionOpts): number[] { if (unique) { const resultSet: Set<number> = new Set(); while (resultSet.size < size) resultSet.add(this.randomFloat(range)); @@ -85,7 +85,7 @@ export default class NormalDistribution extends AbstractDistribution { return Array.from({ length: size }, () => this.randomFloat(range)); } - public randomIntArray(size: number, range: IRange<number>, unique: boolean): number[] { + public randomIntArray(size: number, range: IRange<number>, { unique }: DistributionOpts): number[] { if (unique) { const resultSet: Set<number> = new Set(); while (resultSet.size < size) { diff --git a/src/distributions/UniformDistribution.ts b/src/distributions/UniformDistribution.ts index f0630739aa8218008412f9bc3098c23e94d08a54..97dae186ec79444a7e559158f735cb58a45bff18 100644 --- a/src/distributions/UniformDistribution.ts +++ b/src/distributions/UniformDistribution.ts @@ -1,5 +1,5 @@ import { IRange } from "../Range"; -import { AbstractDistribution } from "./IDistribution"; +import { AbstractDistribution, DistributionOpts } from "./IDistribution"; /** * Class generates uniformly distributed numbers in N(0,1) or within specified range. @@ -36,7 +36,7 @@ export default class UniformDistribution extends AbstractDistribution { return Math.round(this.randomFloat(range)); } - public randomFloatArray(size: number, range: IRange<number>, unique: boolean): number[] { + public randomFloatArray(size: number, range: IRange<number>, { unique }: DistributionOpts): number[] { if (unique) { const resultSet: Set<number> = new Set(); while (resultSet.size < size) resultSet.add(this.randomFloat(range)); @@ -45,7 +45,7 @@ export default class UniformDistribution extends AbstractDistribution { return Array.from({ length: size }, () => this.randomFloat(range)); } - public randomIntArray(size: number, range: IRange<number>, unique: boolean): number[] { + public randomIntArray(size: number, range: IRange<number>, { unique }: DistributionOpts): number[] { if (unique) { const resultSet: Set<number> = new Set(); while (resultSet.size < size) resultSet.add(this.randomInt(range)); diff --git a/src/distributions/ZipfDistribution.ts b/src/distributions/ZipfDistribution.ts new file mode 100644 index 0000000000000000000000000000000000000000..a569836988a88a27b1878b5e503dc999609a45bd --- /dev/null +++ b/src/distributions/ZipfDistribution.ts @@ -0,0 +1,82 @@ +import { IRange } from "../Range"; +import { AbstractDistribution, DistributionOpts } from "./IDistribution"; + +export default class ZipfDistribution extends AbstractDistribution { + // Object, containing type name and pointer to distribution class instance + private static instances: { [name: string]: ZipfDistribution } = {}; + private minZipfian: number = 1.1; + private maxZipfian: number = 1000.0; + + constructor(seed: number) { + super(seed); + } + + // Named singleton + // Each class type will use its own distribution generator + public static getInstance(seed: number, name: string): ZipfDistribution { + if (!ZipfDistribution.instances[name]) { + ZipfDistribution.instances[name] = new ZipfDistribution(seed); + } + return ZipfDistribution.instances[name]; + } + + /* + * Computing zipfian using rejection method, based on + * "Non-Uniform Random Variate Generation", + * Luc Devroye, p. 550-551, Springer 1986. + * Works for s > 1.0, but may perform badly for s very close to 1.0. + */ + private _random(itemcount: number, zipfian: number): number { + const b: number = Math.pow(2.0, zipfian - 1.0); + let x: number = 0; + let t: number = 0; + let u: number = 0; + let v: number = 0; + if (itemcount <= 1) return 1; + + // eslint-disable-next-line no-constant-condition + while (true) { + u = this._prng.next(); + v = this._prng.next(); + // Math.floor converts float to int here + // If you need to gen float numbers -> do not use Math.floor here + // And instead round returned value from this method + x = Math.floor(Math.pow(u, -1.0 / (zipfian - 1.0))); + t = Math.pow(1.0 - 1.0 / x, zipfian - 1.0); + if ((v * x * (t - 1.0)) / (b - 1.0) <= t / b && x <= itemcount) break; + } + return x; + } + + public randomFloat(range: IRange<number>, { parameter }: DistributionOpts): number { + if (parameter) { + const itemcount: number = range.right - range.left + 1; + if (this.minZipfian > parameter || parameter > this.maxZipfian) { + throw new Error("ZipfDistribution : randomFloat: Please ensure, that zipfian value is in range [1.1, 1000.0]"); + } + return range.left - 1 + this._random(itemcount, parameter); + } else { + throw new Error("ZipfDistribution : randomFloat: Please define zipfian parameter"); + } + } + + public randomInt(range: IRange<number>, { parameter }: DistributionOpts): number { + if (parameter) { + return Math.floor(this.randomFloat(range, { parameter })); + } else { + throw new Error("ZipfDistribution : randomInt: Please define zipfian parameter"); + } + } + + public randomFloatArray(size: number, range: IRange<number>, { parameter }: DistributionOpts): number[] { + return Array.from({ length: size }, () => this.randomFloat(range, { parameter })); + } + + public randomIntArray(size: number, range: IRange<number>, { parameter }: DistributionOpts): number[] { + return Array.from({ length: size }, () => this.randomInt(range, { parameter })); + } + + public get seed(): number { + return 1; + } +} diff --git a/src/scenario/template.yml b/src/scenario/template.yml index bfeac1814c0351c01af1eff7ee68f52d0a60cf58..a4c3584964676ab645b7397aa6adbab8be6256fa 100644 --- a/src/scenario/template.yml +++ b/src/scenario/template.yml @@ -31,12 +31,19 @@ tables_schema: type: Decimal distribution: Normal range: [-100.5, 100.5] - is_nullable: true - null_percentage: 25 + is_nullable: false + null_percentage: 0 unique: false - name: stock type: Boolean distribution: Normal + is_nullable: true + null_percentage: 50 + - name: frequencies + type: Unsigned + distribution: Zipf + distribution_param: 1.3 + range: [0, 10] is_nullable: false null_percentage: 0 primary_key: id @@ -45,7 +52,8 @@ tables_schema: # Template SQL queries with parameters and weights queries: - - sql: select sum("id") from "cars" + # bug: select sum("id") from "cars" + - sql: select "id" from "cars" params: [] weight: 33 - sql: select count("id") from "cars" where "id" = ? @@ -57,7 +65,7 @@ queries: - value: 5 - value: true weight: 60 - - sql: select * from "cars" where "score" = ? and "rating" = ? and and "id" = ? and "brand" = ? + - sql: select * from "cars" where "score" = ? and "rating" = ? and and "id" = ? and "brand" = ? and "frequencies" = ? params: - value: null #blank value also interpetiers as null - value: -5 @@ -72,4 +80,10 @@ queries: alphabet: ["en"] is_nullable: false null_percentage: 0 + - type: Unsigned + distribution: Zipf + distribution_param: 1.3 + range: [0, 10] + is_nullable: false + null_percentage: 0 weight: 66 diff --git a/src/types/Boolean.ts b/src/types/Boolean.ts index e142a281083f74e6bca139fca055cf290a0d6799..856cb1a80bce900d988d6b58ded765359252b70e 100644 --- a/src/types/Boolean.ts +++ b/src/types/Boolean.ts @@ -1,4 +1,4 @@ -import { IDistribution } from "../distributions/IDistribution"; +import { DistributionOpts, IDistribution } from "../distributions/IDistribution"; import UniformDistribution from "../distributions/UniformDistribution"; import { IRange, Range } from "../Range"; import Replacer from "../Replacer"; @@ -28,12 +28,12 @@ export class BooleanType extends AbstractTypeArray<boolean> { return BooleanType.instance; } - public generateArrayWithNull(size: number, nullPercentage: number): (boolean | null)[] { + public generateArrayWithNull(size: number, nullPercentage: number, opts: DistributionOpts): (boolean | null)[] { const booleanNullArray: (boolean | null)[] = Array.from({ length: size }, () => - this._distribution.randomInt(this._range) === 1 ? true : false + this._distribution.randomInt(this._range, opts) === 1 ? true : false ); - new Replacer(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue( + new Replacer<boolean, null>(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue( booleanNullArray, null ); @@ -41,7 +41,7 @@ export class BooleanType extends AbstractTypeArray<boolean> { return booleanNullArray; } - public generateArrayWithoutNull(size: number): boolean[] { - return Array.from({ length: size }, () => (this._distribution.randomInt(this._range) === 1 ? true : false)); + public generateArrayWithoutNull(size: number, opts: DistributionOpts): boolean[] { + return Array.from({ length: size }, () => (this._distribution.randomInt(this._range, opts) === 1 ? true : false)); } } diff --git a/src/types/Decimal.ts b/src/types/Decimal.ts index 044eab5844edd08051421d048eb7d4186e1cbb13..b2ebc5e7f34c61b2b74677054014e06e3d54126b 100644 --- a/src/types/Decimal.ts +++ b/src/types/Decimal.ts @@ -1,4 +1,4 @@ -import { IDistribution } from "../distributions/IDistribution"; +import { DistributionOpts, IDistribution } from "../distributions/IDistribution"; import UniformDistribution from "../distributions/UniformDistribution"; import { IRange, Range } from "../Range"; import Replacer from "../Replacer"; @@ -40,14 +40,17 @@ export class DecimalType extends AbstractTypeArray<number> { return DecimalType.instance; } - public generateArrayWithNull(size: number, nullPercentage: number, unique: boolean): (number | null)[] { - const decimalArray: (number | null)[] = this._distribution.randomFloatArray(size, this._range, unique); - new Replacer(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue(decimalArray, null); + public generateArrayWithNull(size: number, nullPercentage: number, opts: DistributionOpts): (number | null)[] { + const decimalArray: (number | null)[] = this._distribution.randomFloatArray(size, this._range, opts); + new Replacer<number, null>(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue( + decimalArray, + null + ); return decimalArray; } - public generateArrayWithoutNull(size: number, unique: boolean): number[] { - return this._distribution.randomFloatArray(size, this._range, unique); + public generateArrayWithoutNull(size: number, opts: DistributionOpts): number[] { + return this._distribution.randomFloatArray(size, this._range, opts); } } diff --git a/src/types/IType.ts b/src/types/IType.ts index ec58f9624e407bc5cd944dcaf71a7e77f166de15..ae7a6b38ce27520654499466e76387c47f028481 100644 --- a/src/types/IType.ts +++ b/src/types/IType.ts @@ -1,10 +1,10 @@ -import { IDistribution } from "../distributions/IDistribution"; +import { DistributionOpts, IDistribution } from "../distributions/IDistribution"; export type PrimitiveTypes = string | number | boolean | null; export interface ITypeArray<T> { - generateArrayWithNull(size: number, nullPercentage: number, unique?: boolean): (T | null)[]; - generateArrayWithoutNull(size: number, unique?: boolean): T[]; + generateArrayWithNull(size: number, nullPercentage: number, opts?: DistributionOpts): (T | null)[]; + generateArrayWithoutNull(size: number, opts?: DistributionOpts): T[]; } export abstract class AbstractTypeArray<T> implements ITypeArray<T> { @@ -13,6 +13,6 @@ export abstract class AbstractTypeArray<T> implements ITypeArray<T> { constructor(distribution: IDistribution) { this._distribution = distribution; } - abstract generateArrayWithNull(size: number, nullPercentage: number, unique?: boolean): (T | null)[]; - abstract generateArrayWithoutNull(size: number, unique?: boolean): T[]; + abstract generateArrayWithNull(size: number, nullPercentage: number, opts?: DistributionOpts): (T | null)[]; + abstract generateArrayWithoutNull(size: number, opts?: DistributionOpts): T[]; } diff --git a/src/types/SignedInt.ts b/src/types/SignedInt.ts index 322a587d870cec379df2fb10f31f89f0a863397e..d1f623299eaac81b5968865f87e2b1871b33d941 100644 --- a/src/types/SignedInt.ts +++ b/src/types/SignedInt.ts @@ -1,4 +1,4 @@ -import { IDistribution } from "../distributions/IDistribution"; +import { DistributionOpts, IDistribution } from "../distributions/IDistribution"; import UniformDistribution from "../distributions/UniformDistribution"; import { IRange, Range } from "../Range"; import Replacer from "../Replacer"; @@ -39,9 +39,9 @@ export class SignedIntType extends AbstractTypeArray<number> { return SignedIntType.instance; } - public generateArrayWithNull(size: number, nullPercentage: number, unique: boolean): (number | null)[] { - const signedNullArray: (number | null)[] = this._distribution.randomIntArray(size, this._range, unique); - new Replacer(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue( + public generateArrayWithNull(size: number, nullPercentage: number, opts: DistributionOpts): (number | null)[] { + const signedNullArray: (number | null)[] = this._distribution.randomIntArray(size, this._range, opts); + new Replacer<number, null>(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue( signedNullArray, null ); @@ -49,7 +49,7 @@ export class SignedIntType extends AbstractTypeArray<number> { return signedNullArray; } - public generateArrayWithoutNull(size: number, unique: boolean): number[] { - return this._distribution.randomIntArray(size, this._range, unique); + public generateArrayWithoutNull(size: number, opts: DistributionOpts): number[] { + return this._distribution.randomIntArray(size, this._range, opts); } } diff --git a/src/types/String.ts b/src/types/String.ts index 6cb19d6dcc1bf0cfc6305840e3ae1c1ea5ff1603..71465e611e8c31145e7e152ea32f07088fa7d7b1 100644 --- a/src/types/String.ts +++ b/src/types/String.ts @@ -1,6 +1,6 @@ import { Alphabet, IAlphabet } from "../Alphabet"; import { Cutter, ICutter } from "../Cutter"; -import { IDistribution } from "../distributions/IDistribution"; +import { DistributionOpts, IDistribution } from "../distributions/IDistribution"; import UniformDistribution from "../distributions/UniformDistribution"; import { IRanges, Ranges } from "../Range"; import Replacer from "../Replacer"; @@ -47,14 +47,17 @@ export class StringType extends AbstractTypeArray<string> { return StringType.instance; } - public generateArrayWithNull(size: number, nullPercentage: number, unique: boolean): (string | null)[] { + public generateArrayWithNull(size: number, nullPercentage: number, { unique }: DistributionOpts): (string | null)[] { const stringArray: (string | null)[] = this._wordCutter.generateWords(size, unique); - new Replacer(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue(stringArray, null); + new Replacer<string, null>(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue( + stringArray, + null + ); return stringArray; } - public generateArrayWithoutNull(size: number, unique: boolean): string[] { + public generateArrayWithoutNull(size: number, { unique }: DistributionOpts): string[] { const stringArray: string[] = this._wordCutter.generateWords(size, unique); return stringArray; diff --git a/src/types/UnsignedInt.ts b/src/types/UnsignedInt.ts index 7f193fa04b337a3569a47633c5fca1fc47ea3747..59f4a65bc5e26f8944a7e98d1a10660e8972e603 100644 --- a/src/types/UnsignedInt.ts +++ b/src/types/UnsignedInt.ts @@ -1,4 +1,4 @@ -import { IDistribution } from "../distributions/IDistribution"; +import { DistributionOpts, IDistribution } from "../distributions/IDistribution"; import UniformDistribution from "../distributions/UniformDistribution"; import { IRange, Range } from "../Range"; import Replacer from "../Replacer"; @@ -36,10 +36,10 @@ export class UnsignedIntType extends AbstractTypeArray<number> { return UnsignedIntType.instance; } - public generateArrayWithNull(size: number, nullPercentage: number, unique: boolean): (number | null)[] { - const unsignedNullArray: (number | null)[] = this._distribution.randomIntArray(size, this._range, unique); + public generateArrayWithNull(size: number, nullPercentage: number, opts: DistributionOpts): (number | null)[] { + const unsignedNullArray: (number | null)[] = this._distribution.randomIntArray(size, this._range, opts); - new Replacer(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue( + new Replacer<number, null>(new UniformDistribution(this._distribution.seed), nullPercentage).replaceWithValue( unsignedNullArray, null ); @@ -47,7 +47,7 @@ export class UnsignedIntType extends AbstractTypeArray<number> { return unsignedNullArray; } - public generateArrayWithoutNull(size: number, unique: boolean): number[] { - return this._distribution.randomIntArray(size, this._range, unique); + public generateArrayWithoutNull(size: number, opts: DistributionOpts): number[] { + return this._distribution.randomIntArray(size, this._range, opts); } } diff --git a/test/integration/ReplaceWithNull.test.ts b/test/integration/ReplaceWithNull.test.ts index 2594b630ecfa69ce2783fe0478c54299bec9d361..8a442704058a80bf1e6af8e8bd0a6f72ebefb738 100644 --- a/test/integration/ReplaceWithNull.test.ts +++ b/test/integration/ReplaceWithNull.test.ts @@ -4,6 +4,7 @@ import { expect } from "chai"; import { suite, test } from "@testdeck/mocha"; +import { DistributionOpts } from "../../src/distributions/IDistribution"; import NormalDistribution from "../../src/distributions/NormalDistribution"; import UniformDistribution from "../../src/distributions/UniformDistribution"; import Replacer from "../../src/Replacer"; @@ -16,20 +17,20 @@ class ReplaceWithNullTest { private seed: number; private arraySize: number; private nullPercentage: number; - private unique: boolean; + private opts: DistributionOpts; before() { this.seed = 12345678; this.arraySize = 100; this.nullPercentage = 50; - this.unique = false; + this.opts = { unique: false }; } @test fillStringArrayOfCertainSizeWithNullElementsByUniformDistribution() { const numberArray: number[] = new UnsignedIntType(new NormalDistribution(this.seed)).generateArrayWithoutNull( this.arraySize, - this.unique + this.opts ); this.replacer = new Replacer(new UniformDistribution(this.seed), this.nullPercentage); this.replacer.replaceWithValue(numberArray as (number | null)[], null); @@ -47,7 +48,7 @@ class ReplaceWithNullTest { fillStringArrayOfCertainSizeWithNullElementsByNormalDistribution() { const numberArray: number[] = new UnsignedIntType(new NormalDistribution(this.seed)).generateArrayWithoutNull( this.arraySize, - this.unique + this.opts ); this.replacer = new Replacer(new NormalDistribution(this.seed), this.nullPercentage); this.replacer.replaceWithValue(numberArray as (number | null)[], null); diff --git a/test/k6/ScenarioParser.test.ts b/test/k6/ScenarioParser.test.ts index 61ad94a367a97ba66fe400c7b550df9b828c3f61..dff31da2c58819eaecca408aad1c535d740ded29 100644 --- a/test/k6/ScenarioParser.test.ts +++ b/test/k6/ScenarioParser.test.ts @@ -3,49 +3,287 @@ import { describe, expect } from "https://jslib.k6.io/k6chaijs/4.3.4.3/index.js" import { Scenario, ScenarioParser } from "../../src/ScenarioParser"; -const parsedScenario: Scenario = new ScenarioParser("../test/k6/mockScenario.yml").parse(); // prettier-ignore export default () => { - const scenarioPath: string = "../test/k6/mockScenario.yml"; - describe("Parse main fields", () => { - expect(parsedScenario.name).to.equal("MOCK"); - expect(parsedScenario.tables.length).to.equal(2); - expect(parsedScenario.queries.length).to.equal(2); + describe("Throw if scenario file is empty", () => { + const yaml: string = ``; + expect(()=>new ScenarioParser(yaml).parse()).to.throw(Error, "Missing scenario_name tag"); }); - describe("Parse tables_schema fields", () => { - expect(parsedScenario.tables[0].name).to.equal("mock_table_1"); - expect(parsedScenario.tables[0].rowCount).to.equal(10); - expect(parsedScenario.tables[0].columns.length).to.equal(3); - expect(parsedScenario.tables[0].primaryKey).to.equal("unsigned_col"); - expect(parsedScenario.tables[0].distributionKey).to.equal("unsigned_col"); - expect(parsedScenario.tables[0].timeout).to.equal(3.0); - expect(parsedScenario.tables[1].name).to.equal("mock_table_2"); - expect(parsedScenario.tables[1].rowCount).to.equal(5); - expect(parsedScenario.tables[1].columns.length).to.equal(1); - expect(parsedScenario.tables[1].primaryKey).to.equal("integer_col"); - expect(parsedScenario.tables[1].distributionKey).to.equal("integer_col"); - expect(parsedScenario.tables[1].timeout).to.equal(3.0); - }); + describe("Validate fields", ()=>{ + describe("Throw: missing field", ()=>{ + const yaml: stirng = ` + scenario_name: MOCK + + tables_schema: + - name: mock_table_1 + row_count: 10 + columns: + - name: unsigned_col + type: Unsigned + distribution: Normal + range: [0, 100] + is_nullable: false + null_percentage: 0 + primary_key: unsigned_col + distribution_key: unsigned_col + timeout: 3.0 + + # Template SQL queries with parameters and weights + queries: + - sql: select * from "mock_table_1" + params: [] + weight: 50` + expect(()=>new ScenarioParser(yaml).parse()).to.throw(Error, "Table 1, Column 1: Missing unique field"); + }); - describe("Parse columns fields", () => { - expect(parsedScenario.tables[0].columns[0].name).to.equal("unsigned_col"); - expect(parsedScenario.tables[0].columns[0].type).to.equal("Unsigned"); - expect(parsedScenario.tables[0].columns[0].distribution).to.equal("Normal"); - expect(parsedScenario.tables[0].columns[0].range).to.deep.equal([0,100]); - expect(parsedScenario.tables[0].columns[0].isNullable).to.equal(false); - expect(parsedScenario.tables[0].columns[0].nullPercentage).to.equal(0); - expect(parsedScenario.tables[0].columns[0].unique).to.equal(true); - expect(parsedScenario.tables[0].columns[1].name).to.equal("string_col"); - expect(parsedScenario.tables[0].columns[1].alphabet).to.deep.equal(["en"]); - }); + describe("Throw: missing field value", ()=>{ + const yaml: stirng = ` + scenario_name: MOCK + + tables_schema: + - name: mock_table_1 + row_count: 10 + columns: + - name: unsigned_col + type: Unsigned + distribution: Normal + range: [0, 100] + is_nullable: false + null_percentage: 0 + unique: + primary_key: unsigned_col + distribution_key: unsigned_col + timeout: 3.0 + + # Template SQL queries with parameters and weights + queries: + - sql: select * from "mock_table_1" + params: [] + weight: 50` + expect(()=>new ScenarioParser(yaml).parse()).to.throw(Error, "Table 1, Column 1: Missing unique value"); + }); - describe("Parse queries fields", () => { - expect(parsedScenario.queries[0].sql).to.equal(`select * from "mock_table_1"`); - expect(parsedScenario.queries[0].params).to.deep.equal([]); - expect(parsedScenario.queries[0].weight).to.equal(50); - expect(parsedScenario.queries[1].sql).to.equal(`select count("unsigned_col") from "mock_table_1" where "boolean_col" = ?`); - expect(parsedScenario.queries[1].params[0]).to.deep.equal(true); - expect(parsedScenario.queries[1].weight).to.equal(100); + describe("Throw: inpropriate field value type", ()=>{ + const yaml: stirng = ` + scenario_name: MOCK + + tables_schema: + - name: mock_table_1 + row_count: 10 + columns: + - name: unsigned_col + type: Unsigned + distribution: Normal + range: [0, 100] + is_nullable: false + null_percentage: 0 + unique: "true" + primary_key: unsigned_col + distribution_key: unsigned_col + timeout: 3.0 + + # Template SQL queries with parameters and weights + queries: + - sql: select * from "mock_table_1" + params: [] + weight: 50` + expect(()=>new ScenarioParser(yaml).parse()).to.throw(Error, "Table 1, Column 1: unique field is not a boolean"); + }); + + describe("Throw: wrong query params field value", ()=>{ + const yaml: stirng = ` + scenario_name: MOCK + + tables_schema: + - name: mock_table_1 + row_count: 10 + columns: + - name: unsigned_col + type: Unsigned + distribution: Normal + range: [0, 100] + is_nullable: false + null_percentage: 0 + unique: true + primary_key: unsigned_col + distribution_key: unsigned_col + timeout: 3.0 + + # Template SQL queries with parameters and weights + queries: + - sql: select * from "mock_table_1" + params: 12 + weight: 50` + expect(()=>new ScenarioParser(yaml).parse()).to.throw(Error, "Query 1: params field is not a object"); + }); + + describe("Throw: wrong use of query params field", ()=>{ + const yaml: stirng = ` + scenario_name: MOCK + + tables_schema: + - name: mock_table_1 + row_count: 10 + columns: + - name: unsigned_col + type: Unsigned + distribution: Normal + range: [0, 100] + is_nullable: false + null_percentage: 0 + unique: true + primary_key: unsigned_col + distribution_key: unsigned_col + timeout: 3.0 + + # Template SQL queries with parameters and weights + queries: + - sql: select * from "mock_table_1" + params: [12] + weight: 50` + expect(()=>new ScenarioParser(yaml).parse()).to.throw(Error, "Query 1: params field must contain predefined value or type fields"); + }); + + describe("Throw: incomplete query params field value", ()=>{ + const yaml: stirng = ` + scenario_name: MOCK + + tables_schema: + - name: mock_table_1 + row_count: 10 + columns: + - name: unsigned_col + type: Unsigned + distribution: Normal + range: [0, 100] + is_nullable: false + null_percentage: 0 + unique: true + primary_key: unsigned_col + distribution_key: unsigned_col + timeout: 3.0 + + # Template SQL queries with parameters and weights + queries: + - sql: select * from "mock_table_1" + params: + - type: Unsigned + weight: 50` + expect(()=>new ScenarioParser(yaml).parse()).to.throw(); + }); }); + + describe("Parse full scenario file", () => { + const yaml: stirng = ` + scenario_name: MOCK + + tables_schema: + - name: mock_table_1 + row_count: 10 + columns: + - name: unsigned_col + type: Unsigned + distribution: Normal + range: [0, 100] + is_nullable: false + null_percentage: 0 + unique: true + - name: string_col + type: String + distribution: Uniform + range: [4, 7] + alphabet: ["en"] + is_nullable: false + null_percentage: 0 + unique: false + - name: boolean_col + type: Boolean + distribution: Zipf + distribution_param: 1.3 + is_nullable: false + null_percentage: 0 + primary_key: unsigned_col + distribution_key: unsigned_col + timeout: 3.0 + - name: mock_table_2 + row_count: 5 + columns: + - name: integer_col + type: Integer + distribution: Normal + range: [0, 100] + is_nullable: false + null_percentage: 0 + unique: true + primary_key: integer_col + distribution_key: integer_col + timeout: 3.0 + + # Template SQL queries with parameters and weights + queries: + - sql: select * from "mock_table_1" + params: [] + weight: 50 + - sql: select count("unsigned_col") from "mock_table_1" where "boolean_col" = ? + params: + - value: true + weight: 100 + - sql: select count("unsigned_col") from "mock_table_1" where "boolean_col" = ? + params: + - type: Unsigned + distribution: Zipf + distribution_param: 1.3 + range: [0, 10] + is_nullable: false + null_percentage: 0 + weight: 150` + const parsedScenario: Scenario = new ScenarioParser(yaml).parse(); + + describe("Parse main fields", () => { + expect(parsedScenario.name).to.equal("MOCK"); + expect(parsedScenario.tables.length).to.equal(2); + expect(parsedScenario.queries.length).to.equal(3); + }); + + describe("Parse tables_schema fields", () => { + expect(parsedScenario.tables[0].name).to.equal("mock_table_1"); + expect(parsedScenario.tables[0].rowCount).to.equal(10); + expect(parsedScenario.tables[0].columns.length).to.equal(3); + expect(parsedScenario.tables[0].primaryKey).to.equal("unsigned_col"); + expect(parsedScenario.tables[0].distributionKey).to.equal("unsigned_col"); + expect(parsedScenario.tables[0].timeout).to.equal(3.0); + expect(parsedScenario.tables[1].name).to.equal("mock_table_2"); + expect(parsedScenario.tables[1].rowCount).to.equal(5); + expect(parsedScenario.tables[1].columns.length).to.equal(1); + expect(parsedScenario.tables[1].primaryKey).to.equal("integer_col"); + expect(parsedScenario.tables[1].distributionKey).to.equal("integer_col"); + expect(parsedScenario.tables[1].timeout).to.equal(3.0); + }); + + describe("Parse columns fields", () => { + expect(parsedScenario.tables[0].columns[0].name).to.equal("unsigned_col"); + expect(parsedScenario.tables[0].columns[0].type).to.equal("Unsigned"); + expect(parsedScenario.tables[0].columns[0].distribution).to.equal("Normal"); + expect(parsedScenario.tables[0].columns[0].range).to.deep.equal([0,100]); + expect(parsedScenario.tables[0].columns[0].isNullable).to.equal(false); + expect(parsedScenario.tables[0].columns[0].nullPercentage).to.equal(0); + expect(parsedScenario.tables[0].columns[0].distributionOpts.unique).to.equal(true); + expect(parsedScenario.tables[0].columns[1].name).to.equal("string_col"); + expect(parsedScenario.tables[0].columns[1].alphabet).to.deep.equal(["en"]); + expect(parsedScenario.tables[0].columns[2].distributionOpts.parameter).to.equal(1.3); + }); + + describe("Parse queries fields", () => { + expect(parsedScenario.queries[0].sql).to.equal(`select * from "mock_table_1"`); + expect(parsedScenario.queries[0].params).to.deep.equal([]); + expect(parsedScenario.queries[0].weight).to.equal(50); + expect(parsedScenario.queries[1].sql).to.equal(`select count("unsigned_col") from "mock_table_1" where "boolean_col" = ?`); + expect(parsedScenario.queries[1].params[0]).to.deep.equal(true); + expect(parsedScenario.queries[1].weight).to.equal(100); + expect(parsedScenario.queries[2].params[0].type).to.equal("Unsigned"); + expect(parsedScenario.queries[2].params[0].distribution).to.equal("Zipf"); + expect(parsedScenario.queries[2].params[0].distributionOpts.parameter).to.equal(1.3); + }); +}); }; diff --git a/test/k6/Singleton.test.ts b/test/k6/Singleton.test.ts index 49fcd5c2f705223b027756f7bf6e68049b4423fe..fdb4e0bba1c153af7621dddfda0d1d0c344ca8a2 100644 --- a/test/k6/Singleton.test.ts +++ b/test/k6/Singleton.test.ts @@ -9,30 +9,30 @@ export default () => { const seed: number = 123456789; describe("Generate different data for 2 similar columns", () => { - const stringCol1: StringColumn = new StringColumn("", "String", "NORMAL", [1, 2], ["en"], false, 0, false); + const stringCol1: StringColumn = new StringColumn("", "String", "NORMAL", {unique: false}, [1, 2], ["en"], false, 0); const genStringCol1: string[] = stringCol1.generateData(seed, size, true); - const stringCol2: StringColumn = new StringColumn("", "String", "NORMAL", [1, 2], ["en"], false, 0, false); + const stringCol2: StringColumn = new StringColumn("", "String", "NORMAL", {unique: false}, [1, 2], ["en"], false, 0); const genStringCol2: string[] = stringCol2.generateData(seed, size, true); expect(genStringCol1).to.not.deep.equal(genStringCol2); }); describe("Generate same data for singleton (tableCol) and obeject (sql parameter)", () => { - const stringCol1: DecimalColumn = new DecimalColumn("", "Decimal", "NORMAL", [1, 15], false, 0, false); + const stringCol1: DecimalColumn = new DecimalColumn("", "Decimal", "NORMAL", {unique: false}, [1, 15], false, 0); const genStringCol1: number[] = stringCol1.generateData(seed, size, true); - const stringCol2: DecimalColumn = new DecimalColumn("", "Decimal", "NORMAL", [1, 15], false, 0, false); + const stringCol2: DecimalColumn = new DecimalColumn("", "Decimal", "NORMAL", {unique: false}, [1, 15], false, 0); const genStringCol2: number[] = stringCol2.generateData(seed, size, false); expect(genStringCol1).to.deep.equal(genStringCol2); }); describe("Generate same data for 2 sql parameters", () => { - const stringCol1: StringColumn = new StringColumn("", "", "NORMAL", [3, 7], ["en"], false, 0, false); + const stringCol1: StringColumn = new StringColumn("", "", "NORMAL", {unique: false},[3, 7], ["en"], false, 0); const genStringCol1: string[] = stringCol1.generateData(seed, size, false); - const stringCol2: StringColumn = new StringColumn("", "", "NORMAL", [3, 7], ["en"], false, 0, false); + const stringCol2: StringColumn = new StringColumn("", "", "NORMAL", {unique: false}, [3, 7], ["en"], false, 0); const genStringCol2: string[] = stringCol2.generateData(seed, size, false); expect(genStringCol1).to.deep.equal(genStringCol2); diff --git a/test/k6/TypesGeneration.test.ts b/test/k6/TypesGeneration.test.ts index 248e4c80ffc549c38448d9e951819055060bec04..8d0c8b137f4e629463e8114f5efd11e79e508278 100644 --- a/test/k6/TypesGeneration.test.ts +++ b/test/k6/TypesGeneration.test.ts @@ -14,54 +14,54 @@ export default () => { const seed: number = 123456789; describe("Generate String array", () => { - const stringType = new StringType(new NormalDistribution(seed), [3, 7], ["en", "ru"]).generateArrayWithoutNull(size, true); + const stringType = new StringType(new NormalDistribution(seed), [3, 7], ["en", "ru"]).generateArrayWithoutNull(size, {unique: true}); const expectedStringType = ["pTrй","iйuЖ","zAqОЧ","лЩРФД","Зzнд","ЛРБpdmЦ","жÐiv","iЪЩп","Ð¥SСвИ","ШБЧyЙЛ"]; expect(stringType).to.deep.equal(expectedStringType); - const stringTypeNull = new StringType(new NormalDistribution(seed), [3, 7], ["en", "ru"]).generateArrayWithNull(size, nullPerc, true); + const stringTypeNull = new StringType(new NormalDistribution(seed), [3, 7], ["en", "ru"]).generateArrayWithNull(size, nullPerc, {unique: true}); const expectedStringTypeNull = [null,"iйuЖ","zAqОЧ",null,null,"ЛРБpdmЦ",null,null,"Ð¥SСвИ","ШБЧyЙЛ"]; expect(stringTypeNull).to.deep.equal(expectedStringTypeNull); }); describe("Generate UnsignedInt array", () => { - const unsignedIntType = new UnsignedIntType(new NormalDistribution(seed), [100, 300]).generateArrayWithoutNull(size, false); + const unsignedIntType = new UnsignedIntType(new NormalDistribution(seed), [100, 300]).generateArrayWithoutNull(size, {unique: false}); const expectedUnsignedIntType = [141,172,133,175,261,142,160,262,180,200]; expect(unsignedIntType).to.deep.equal(expectedUnsignedIntType); - const unsignedIntTypeNull = new UnsignedIntType(new NormalDistribution(seed)).generateArrayWithNull(size, nullPerc, false); + const unsignedIntTypeNull = new UnsignedIntType(new NormalDistribution(seed)).generateArrayWithNull(size, nullPerc, {unique: false}); const expectedUnsignedIntTypeNull = [null,3239988727117702,1487186952157000,null,null,1869619023842354,null,null,3587111689209931,4507687566378977]; expect(unsignedIntTypeNull).to.deep.equal(expectedUnsignedIntTypeNull); }); describe("Generate SignedInt array", () => { - const signedIntType = new SignedIntType(new NormalDistribution(seed), [-100, 100]).generateArrayWithoutNull(size, false); + const signedIntType = new SignedIntType(new NormalDistribution(seed), [-100, 100]).generateArrayWithoutNull(size, {unique: false}); const expectedSignedIntType = [-59,-28,-67,-25,61,-58,-40,62,-20,0]; expect(signedIntType).to.deep.equal(expectedSignedIntType); - const signedIntTypeNull = new SignedIntType(new NormalDistribution(seed)).generateArrayWithNull(size, nullPerc, false); + const signedIntTypeNull = new SignedIntType(new NormalDistribution(seed)).generateArrayWithNull(size, nullPerc, {unique: false}); const expectedSignedIntTypeNull = [null,-2527221800505587,-6032825350426992,null,null,-5267961207056283,null,null,-1832975876321130,8175878016963]; expect(signedIntTypeNull).to.deep.equal(expectedSignedIntTypeNull); }); describe("Generate Decimal array", () =>{ - const decimalType = new DecimalType(new NormalDistribution(seed), [-10.5, 60.25]).generateArrayWithoutNull(size, false); + const decimalType = new DecimalType(new NormalDistribution(seed), [-10.5, 60.25]).generateArrayWithoutNull(size, {unique: false}); const expectedDecimalType = [3.8574782908463803,14.949553846932087,1.1815975631632014,15.947041879489474,46.45032735690995,4.185535669394966,10.626441929377277,46.79418443234036,17.676144974035047,24.907110057374144]; expect(decimalType).to.deep.equal(expectedDecimalType); - const decimalTypeNull = new DecimalType(new NormalDistribution(seed)).generateArrayWithNull(size, nullPerc, false); + const decimalTypeNull = new DecimalType(new NormalDistribution(seed)).generateArrayWithNull(size, nullPerc, {unique: false}); const expectedDecimalTypeNull = [null,-2527221800505587,-6032825350426992,null,null,-5267961207056283,null,null,-1832975876321130,8175878016963.115]; expect(decimalTypeNull).to.deep.equal(expectedDecimalTypeNull); }); describe("Generate Boolean array", () => { - const booleanType = new BooleanType(new NormalDistribution(seed)).generateArrayWithoutNull(size); + const booleanType = new BooleanType(new NormalDistribution(seed)).generateArrayWithoutNull(size, {}); const expectedBooleanType = [false,false,false,false,true,false,false,true,false,true]; expect(booleanType).to.deep.equal(expectedBooleanType); - const booleanTypeNull = new BooleanType(new NormalDistribution(seed)).generateArrayWithNull(size, nullPerc); + const booleanTypeNull = new BooleanType(new NormalDistribution(seed)).generateArrayWithNull(size, nullPerc, {}); const expectedBooleanTypeNull = [null,false,false,null,null,false,null,null,false,true]; expect(booleanTypeNull).to.deep.equal(expectedBooleanTypeNull); diff --git a/test/k6/mockScenario.yml b/test/k6/mockScenario.yml deleted file mode 100644 index a98b90997a7e6e64b11af4303759ec33700ce1d1..0000000000000000000000000000000000000000 --- a/test/k6/mockScenario.yml +++ /dev/null @@ -1,53 +0,0 @@ -scenario_name: MOCK - -# The tables under test. -tables_schema: - - name: mock_table_1 - row_count: 10 - columns: - - name: unsigned_col - type: Unsigned - distribution: Normal - range: [0, 100] - is_nullable: false - null_percentage: 0 - unique: true - - name: string_col - type: String - distribution: Uniform - range: [4, 7] - alphabet: ["en"] - is_nullable: false - null_percentage: 0 - unique: false - - name: boolean_col - type: Boolean - distribution: Normal - is_nullable: false - null_percentage: 0 - primary_key: unsigned_col - distribution_key: unsigned_col - timeout: 3.0 - - name: mock_table_2 - row_count: 5 - columns: - - name: integer_col - type: Integer - distribution: Normal - range: [0, 100] - is_nullable: false - null_percentage: 0 - unique: true - primary_key: integer_col - distribution_key: integer_col - timeout: 3.0 - -# Template SQL queries with parameters and weights -queries: - - sql: select * from "mock_table_1" - params: [] - weight: 50 - - sql: select count("unsigned_col") from "mock_table_1" where "boolean_col" = ? - params: - - value: true - weight: 100 diff --git a/test/unit/BooleanType.test.ts b/test/unit/BooleanType.test.ts index 30f80b3c2ca18bc2725e1caa45c2d1d3aec80053..6d585459fdc2098db80b771946096c8e290486b7 100644 --- a/test/unit/BooleanType.test.ts +++ b/test/unit/BooleanType.test.ts @@ -4,6 +4,7 @@ import { expect } from "chai"; import { suite, test } from "@testdeck/mocha"; +import { DistributionOpts } from "../../src/distributions/IDistribution"; import { BooleanType } from "../../src/types/Boolean"; import { MockDistribution } from "./Mocks"; @@ -14,6 +15,7 @@ class BooleanGenerationTest { private arraySize: number; private nullPercentage: number; private mockDist: MockDistribution; + private distOpts: DistributionOpts; before() { this.arraySize = 10; @@ -24,7 +26,8 @@ class BooleanGenerationTest { @test generateBooleanArrayOfCertainSizeWithoutNull() { - const booleanArray: boolean[] = this.type.generateArrayWithoutNull(this.arraySize); + this.distOpts = {}; + const booleanArray: boolean[] = this.type.generateArrayWithoutNull(this.arraySize, this.distOpts); expect(booleanArray).not.to.be.empty; expect(booleanArray.length).to.be.equal(this.arraySize); for (let i: number = 0; i < this.arraySize; i++) { @@ -34,7 +37,8 @@ class BooleanGenerationTest { @test generateBooleanArrayOfCertainSizeWithtNull() { - const booleanArray: boolean[] = this.type.generateArrayWithNull(this.arraySize, this.nullPercentage); + this.distOpts = {}; + const booleanArray: boolean[] = this.type.generateArrayWithNull(this.arraySize, this.nullPercentage, this.distOpts); expect(booleanArray).not.to.be.empty; expect(booleanArray.length).to.be.equal(this.arraySize); let nullCount: number = 0; diff --git a/test/unit/DecimalType.test.ts b/test/unit/DecimalType.test.ts index 94e38549c9deeebad28c98d527dfab080f9cb5b9..621c550ecdc9f6432f67ef2ee71917c33e511832 100644 --- a/test/unit/DecimalType.test.ts +++ b/test/unit/DecimalType.test.ts @@ -4,6 +4,7 @@ import { expect } from "chai"; import { suite, test } from "@testdeck/mocha"; +import { DistributionOpts } from "../../src/distributions/IDistribution"; import { DecimalType } from "../../src/types/Decimal"; import { MockDistribution } from "./Mocks"; @@ -17,6 +18,7 @@ class DecimalGenerationTest { private arraySize: number; private nullPercentage: number; private mockDist: MockDistribution; + private distOpts: DistributionOpts; private minBound: number; private maxBound: number; @@ -42,8 +44,8 @@ class DecimalGenerationTest { @test generateUniqueDecimalArrayOfCertainSizeWithoutNull() { - const unique: boolean = true; - const decimalArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, unique); + this.distOpts = { unique: true }; + const decimalArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, this.distOpts); expect(decimalArray).not.to.be.empty; expect(decimalArray.length).to.be.equal(this.arraySize); for (let i: number = 0; i < this.arraySize; i++) { @@ -54,11 +56,11 @@ class DecimalGenerationTest { @test generateUniqueDecimalArrayOfCertainSizeWithNull() { - const unique: boolean = true; + this.distOpts = { unique: true }; const decimalArray: (number | null)[] = this.type.generateArrayWithNull( this.arraySize, this.nullPercentage, - unique + this.distOpts ); expect(decimalArray).not.to.be.empty; expect(decimalArray.length).to.be.equal(this.arraySize); diff --git a/test/unit/NormalDistribution.test.ts b/test/unit/NormalDistribution.test.ts index 052f9dac7083c2cc2e08b3ec6f6cf04de8f032aa..be2dcb502e2e3b0d119fe8b468d88941a0db4a54 100644 --- a/test/unit/NormalDistribution.test.ts +++ b/test/unit/NormalDistribution.test.ts @@ -24,7 +24,7 @@ class NormalDistributionTest { for (const count of sampleCount) { // eslint-disable-next-line const uniqueCount = {}; - const generatedSequence: number[] = this.distribution.randomIntArray(count, customRange, false); + const generatedSequence: number[] = this.distribution.randomIntArray(count, customRange, { unique: false }); generatedSequence.forEach((value) => { expect(value).to.be.within(customRange.left, customRange.right); if (uniqueCount[value]) { @@ -42,7 +42,7 @@ class NormalDistributionTest { generateRandomIntegerInRangeOfSameValue() { const customRange: MockRange<number> = new MockRange<number>(1, 1); const sampleCount: number = 50; - const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, false); + const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, { unique: false }); generatedSequence.forEach((value) => { expect(value).to.be.equal(1); }); @@ -52,7 +52,7 @@ class NormalDistributionTest { generateArrayOfUniqueIntegersInRange() { const customRange: MockRange<number> = new MockRange<number>(0, 11); const sampleCount: number = 12; - const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, true); + const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, { unique: true }); const expectedSequence: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; expect(generatedSequence.sort((a, b) => a - b)).to.be.deep.equal(expectedSequence); } @@ -61,7 +61,7 @@ class NormalDistributionTest { generateArrayOfIntegersInRange() { const customRange: MockRange<number> = new MockRange<number>(-20, 100); const sampleCount: number = 100; - const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, false); + const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, { unique: false }); for (const value of generatedSequence) { expect(value).to.be.within(customRange.left, customRange.right); } @@ -71,7 +71,7 @@ class NormalDistributionTest { generateArrayOfFloatsInRange() { const customRange: MockRange<number> = new MockRange<number>(-20, 100); const sampleCount: number = 100; - const generatedSequence: number[] = this.distribution.randomFloatArray(sampleCount, customRange, false); + const generatedSequence: number[] = this.distribution.randomFloatArray(sampleCount, customRange, { unique: false }); for (const value of generatedSequence) { expect(value).to.be.within(customRange.left, customRange.right); } diff --git a/test/unit/SignedIntType.test.ts b/test/unit/SignedIntType.test.ts index 60f5b341ebfb10fbc6d7045a617b9f35ddd64e27..4fd46a6910d963d47fc72b111c9d4bb407bd50f6 100644 --- a/test/unit/SignedIntType.test.ts +++ b/test/unit/SignedIntType.test.ts @@ -4,6 +4,7 @@ import { expect } from "chai"; import { suite, test } from "@testdeck/mocha"; +import { DistributionOpts } from "../../src/distributions/IDistribution"; import { SignedIntType } from "../../src/types/SignedInt"; import { MockDistribution } from "./Mocks"; @@ -17,6 +18,7 @@ class SignedGenerationTest { private arraySize: number; private nullPercentage: number; private mockDist: MockDistribution; + private distOpts: DistributionOpts; private minBound: number; private maxBound: number; @@ -41,8 +43,8 @@ class SignedGenerationTest { @test generateUniqueSignedArrayOfCertainSizeWithoutNull() { - const unique: boolean = true; - const signedArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, unique); + this.distOpts = { unique: true }; + const signedArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, this.distOpts); expect(signedArray).not.to.be.empty; expect(signedArray.length).to.be.equal(this.arraySize); for (let i: number = 0; i < this.arraySize; i++) { @@ -53,8 +55,8 @@ class SignedGenerationTest { @test generateSignedArrayOfCertainSizeWithoutNull() { - const unique: boolean = false; - const signedArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, unique); + this.distOpts = { unique: false }; + const signedArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, this.distOpts); expect(signedArray).not.to.be.empty; expect(signedArray.length).to.be.equal(this.arraySize); for (let i: number = 0; i < this.arraySize; i++) { @@ -65,8 +67,12 @@ class SignedGenerationTest { @test generateSignedArrayOfCertainSizeWithNull() { - const unique: boolean = true; - const signedArray: (number | null)[] = this.type.generateArrayWithNull(this.arraySize, this.nullPercentage, unique); + this.distOpts = { unique: true }; + const signedArray: (number | null)[] = this.type.generateArrayWithNull( + this.arraySize, + this.nullPercentage, + this.distOpts + ); expect(signedArray).not.to.be.empty; expect(signedArray.length).to.be.equal(this.arraySize); let nullCount: number = 0; diff --git a/test/unit/StringType.test.ts b/test/unit/StringType.test.ts index 23909dce73128582598248cd4cfeaa00ff34d00c..30f84b06ff2f10dfa51e1b732d24d6cf1414343d 100644 --- a/test/unit/StringType.test.ts +++ b/test/unit/StringType.test.ts @@ -4,6 +4,7 @@ import { expect } from "chai"; import { suite, test } from "@testdeck/mocha"; +import { DistributionOpts } from "../../src/distributions/IDistribution"; import { StringType } from "../../src/types/String"; import { MockDistribution } from "./Mocks"; @@ -12,12 +13,12 @@ import { MockDistribution } from "./Mocks"; class StringGenerationTest { private type: StringType; private mockDist: MockDistribution; + private distOpts: DistributionOpts; private arraySize: number; private nullPercentage: number; private minLength: number; private maxLength: number; private chars: string[]; - private unique: boolean; before() { this.arraySize = 5; @@ -34,8 +35,8 @@ class StringGenerationTest { @test generateUniqueStringArrayOfCertainSizeWithoutNull() { - this.unique = true; - const stringArray: string[] = this.type.generateArrayWithoutNull(this.arraySize, this.unique); + this.distOpts = { unique: true }; + const stringArray: string[] = this.type.generateArrayWithoutNull(this.arraySize, this.distOpts); expect(stringArray).not.to.be.empty; expect(stringArray.length).to.be.equal(this.arraySize); for (let i: number = 0; i < this.arraySize; i++) { @@ -46,8 +47,8 @@ class StringGenerationTest { @test generateStringArrayOfCertainSizeWithoutNull() { - this.unique = false; - const stringArray: string[] = this.type.generateArrayWithoutNull(this.arraySize, this.unique); + this.distOpts = { unique: false }; + const stringArray: string[] = this.type.generateArrayWithoutNull(this.arraySize, this.distOpts); expect(stringArray).not.to.be.empty; expect(stringArray.length).to.be.equal(this.arraySize); for (let i: number = 0; i < this.arraySize; i++) { @@ -58,8 +59,8 @@ class StringGenerationTest { @test generateStringArrayOfCertainSizeWithNull() { - this.unique = false; - const stringArray: string[] = this.type.generateArrayWithNull(this.arraySize, this.nullPercentage, this.unique); + this.distOpts = { unique: false }; + const stringArray: string[] = this.type.generateArrayWithNull(this.arraySize, this.nullPercentage, this.distOpts); expect(stringArray).not.to.be.empty; expect(stringArray.length).to.be.equal(this.arraySize); let nullCount: number = 0; diff --git a/test/unit/UniformDistribution.test.ts b/test/unit/UniformDistribution.test.ts index b1b5d39b687074c94fea30c58cafb954fca2f30f..73b421443a6b5020602852ba259eb25041e88d21 100644 --- a/test/unit/UniformDistribution.test.ts +++ b/test/unit/UniformDistribution.test.ts @@ -24,7 +24,7 @@ class UniformDistributionTest { for (const count of sampleCount) { // eslint-disable-next-line const uniqueCount = {}; - const generatedSequence: number[] = this.distribution.randomIntArray(count, customRange, false); + const generatedSequence: number[] = this.distribution.randomIntArray(count, customRange, { unique: false }); generatedSequence.forEach((value) => { expect(value).to.be.within(customRange.left, customRange.right); if (uniqueCount[value]) { @@ -42,7 +42,7 @@ class UniformDistributionTest { generateRandomIntegerInRangeOfSameValue() { const customRange: MockRange<number> = new MockRange<number>(1, 1); const sampleCount: number = 50; - const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, false); + const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, { unique: false }); generatedSequence.forEach((value) => { expect(value).to.be.equal(1); }); @@ -52,7 +52,7 @@ class UniformDistributionTest { generateArrayOfUniqueIntegersInRange() { const customRange: MockRange<number> = new MockRange<number>(0, 11); const sampleCount: number = 12; - const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, true); + const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, { unique: true }); const expectedSequence: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; expect(generatedSequence.sort((a, b) => a - b)).to.be.deep.equal(expectedSequence); } @@ -61,7 +61,7 @@ class UniformDistributionTest { generateArrayOfIntegersInRange() { const customRange: MockRange<number> = new MockRange<number>(-20, 100); const sampleCount: number = 100; - const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, false); + const generatedSequence: number[] = this.distribution.randomIntArray(sampleCount, customRange, { unique: false }); for (const value of generatedSequence) { expect(value).to.be.within(customRange.left, customRange.right); } @@ -71,7 +71,7 @@ class UniformDistributionTest { generateArrayOfFloatsInRange() { const customRange: MockRange<number> = new MockRange<number>(-20, 100); const sampleCount: number = 100; - const generatedSequence: number[] = this.distribution.randomFloatArray(sampleCount, customRange, false); + const generatedSequence: number[] = this.distribution.randomFloatArray(sampleCount, customRange, { unique: false }); for (const value of generatedSequence) { expect(value).to.be.within(customRange.left, customRange.right); } diff --git a/test/unit/UnsignedIntType.test.ts b/test/unit/UnsignedIntType.test.ts index 398c92aa0b8163f491f91a660bb470edcfc93fa4..2c21a1b23d733d48d0f668cf0caa1a3768a0963d 100644 --- a/test/unit/UnsignedIntType.test.ts +++ b/test/unit/UnsignedIntType.test.ts @@ -4,6 +4,7 @@ import { expect } from "chai"; import { suite, test } from "@testdeck/mocha"; +import { DistributionOpts } from "../../src/distributions/IDistribution"; import { UnsignedIntType } from "../../src/types/UnsignedInt"; import { MockDistribution } from "./Mocks"; @@ -15,9 +16,9 @@ const WRONG_ARGUMENT: string = class UnsignedGenerationTest { private type: UnsignedIntType; private mockDist: MockDistribution; + private distOpts: DistributionOpts; private arraySize: number; private nullPercentage: number; - private unique: boolean; private minBound: number; private maxBound: number; @@ -50,8 +51,8 @@ class UnsignedGenerationTest { @test generateUniqueUnsignedArrayOfCertainSizeWithoutNull() { - this.unique = true; - const unsignedArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, this.unique); + this.distOpts = { unique: true }; + const unsignedArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, this.distOpts); expect(unsignedArray).not.to.be.empty; expect(unsignedArray.length).to.be.equal(this.arraySize); for (let i: number = 0; i < this.arraySize; i++) { @@ -62,8 +63,8 @@ class UnsignedGenerationTest { @test generateUnsignedArrayOfCertainSizeWithoutNull() { - this.unique = false; - const unsignedArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, this.unique); + this.distOpts = { unique: false }; + const unsignedArray: number[] = this.type.generateArrayWithoutNull(this.arraySize, this.distOpts); expect(unsignedArray).not.to.be.empty; expect(unsignedArray.length).to.be.equal(this.arraySize); for (let i: number = 0; i < this.arraySize; i++) { @@ -74,11 +75,11 @@ class UnsignedGenerationTest { @test generateUnsignedArrayOfCertainSizeWithNull() { - this.unique = false; + this.distOpts = { unique: false }; const unsignedArray: (number | null)[] = this.type.generateArrayWithNull( this.arraySize, this.nullPercentage, - this.unique + this.distOpts ); expect(unsignedArray).not.to.be.empty; expect(unsignedArray.length).to.be.equal(this.arraySize); diff --git a/test/unit/ZipfDistribution.test.ts b/test/unit/ZipfDistribution.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ad2cc91938bf968810dd78aaf283e031ce73f27 --- /dev/null +++ b/test/unit/ZipfDistribution.test.ts @@ -0,0 +1,67 @@ +import "mocha"; + +import { expect } from "chai"; + +import { suite, test } from "@testdeck/mocha"; + +import ZipfDistribution from "../../src/distributions/ZipfDistribution"; +import { MockRange } from "./Mocks"; + +@suite +// eslint-disable-next-line +class ZipfDistributionTest { + private seed: number; + private distribution: ZipfDistribution; + private min: number; + private max: number; + private zipfian: number; + + before() { + this.seed = 123456789; + this.zipfian = 1.1; + } + + @test + throwIfIncorrectZipfianProvided() { + this.distribution = new ZipfDistribution(this.seed); + const sampleCount: number = 100; + this.min = 10; + this.max = 20; + this.zipfian = 1; + expect(() => + this.distribution.randomIntArray(sampleCount, new MockRange<number>(this.min, this.max), { + parameter: this.zipfian + }) + ).to.throw(Error, "ZipfDistribution : randomFloat: Please ensure, that zipfian value is in range [1.1, 1000.0]"); + + this.zipfian = 1001; + expect(() => + this.distribution.randomIntArray(sampleCount, new MockRange<number>(this.min, this.max), { + parameter: this.zipfian + }) + ).to.throw(Error, "ZipfDistribution : randomFloat: Please ensure, that zipfian value is in range [1.1, 1000.0]"); + } + + @test + generateRandomIntArrayWithZipfianCorrectly() { + this.distribution = new ZipfDistribution(this.seed); + const sampleCount: number = 100; + const uniqueCount: object = {}; + this.min = 10; + this.max = 20; + const gen1: number[] = this.distribution.randomIntArray(sampleCount, new MockRange<number>(this.min, this.max), { + parameter: this.zipfian + }); + + gen1.forEach((value: number) => { + if (uniqueCount[value]) { + uniqueCount[value] += 1; + } else { + uniqueCount[value] = 1; + } + }); + for (let i: number = this.min; i <= this.max; i++) { + expect(uniqueCount[i]).to.be.greaterThan(0); + } + } +} diff --git a/tsconfig.json b/tsconfig.json index e48b2a1f262c6c4d73eaa7e06de82691960a837f..17fedec2e071e08493a0c0935298041d12c5b1eb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,8 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "skipLibCheck": true, - "strictNullChecks": true + "strictNullChecks": true, + "declaration": true }, - "include": ["k6_modules", "src", "test", "third_party", "example"] + "include": ["k6_modules", "src", "test", "third_party"] }