Skip to content

Commit 28cf6fe

Browse files
committed
Tests
1 parent c80baef commit 28cf6fe

28 files changed

+15756
-5
lines changed

coverage.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"tests":1372,"assertions":6851,"lines":{"total":929,"covered":929,"skipped":0,"pct":100},"statements":{"total":1013,"covered":1013,"skipped":0,"pct":100},"functions":{"total":365,"covered":365,"skipped":0,"pct":100},"branches":{"total":335,"covered":334,"skipped":0,"pct":99.7},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"}}

gulpfile.mjs

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
1+
/*
2+
eslint-disable
3+
jest/no-identical-title,
4+
jest/no-disabled-tests,
5+
jest/expect-expect,
6+
jest/no-export,
7+
jest/no-jest-import,
8+
@typescript-eslint/explicit-module-boundary-types
9+
*/
10+
111
// All other imports are lazy so that single tasks start up fast.
212
import gulp from 'gulp';
313
import {promises} from 'fs';
414

5-
const ALL_MODULES = [
6-
'tinybase',
7-
'ui-react',
15+
const TEST_MODULES = ['tinybase', 'ui-react'];
16+
const ALL_MODULES = TEST_MODULES.concat([
817
'store',
918
'checkpoints',
1019
'indexes',
1120
'metrics',
1221
'relationships',
1322
'persisters',
1423
'common',
15-
];
24+
]);
1625
const LIB_DIR = 'lib';
26+
const TMP_DIR = './tmp';
1727

1828
const getPrettierConfig = async () => ({
1929
...JSON.parse(await promises.readFile('.prettierrc', 'utf-8')),
2030
parser: 'typescript',
2131
});
2232

2333
const allOf = async (array, cb) => await Promise.all(array.map(cb));
34+
const testModules = async (cb) => await allOf(TEST_MODULES, cb);
2435
const allModules = async (cb) => await allOf(ALL_MODULES, cb);
2536

2637
const makeDir = async (dir) => {
@@ -197,6 +208,61 @@ const compileModule = async (module, debug, dir = LIB_DIR, format = 'es') => {
197208
await (await rollup(inputConfig)).write(outputConfig);
198209
};
199210

211+
const test = async (dir, {coverage, countAsserts, puppeteer, serialTests}) => {
212+
const {default: jest} = await import('jest');
213+
await makeDir(TMP_DIR);
214+
const {
215+
results: {success},
216+
} = await jest.runCLI(
217+
{
218+
roots: [dir],
219+
...(puppeteer
220+
? {
221+
setupFilesAfterEnv: ['expect-puppeteer'],
222+
preset: 'jest-puppeteer',
223+
detectOpenHandles: true,
224+
}
225+
: {testEnvironment: 'jsdom'}),
226+
...(coverage
227+
? {
228+
collectCoverage: true,
229+
coverageProvider: 'babel',
230+
collectCoverageFrom: [`${LIB_DIR}/debug/tinybase.js`],
231+
coverageReporters: ['text-summary', 'json-summary'],
232+
coverageDirectory: 'tmp',
233+
}
234+
: {}),
235+
...(countAsserts
236+
? {
237+
setupFilesAfterEnv: ['./test/jest/setup'],
238+
reporters: ['default', './test/jest/reporter'],
239+
testEnvironment: './test/jest/environment',
240+
runInBand: true,
241+
}
242+
: {}),
243+
...(serialTests ? {runInBand: true} : {}),
244+
},
245+
[''],
246+
);
247+
if (!success) {
248+
throw 'Test failed';
249+
}
250+
if (coverage) {
251+
await promises.writeFile(
252+
'coverage.json',
253+
JSON.stringify({
254+
...(countAsserts
255+
? JSON.parse(await promises.readFile('./tmp/assertion-summary.json'))
256+
: {}),
257+
...JSON.parse(await promises.readFile('./tmp/coverage-summary.json'))
258+
.total,
259+
}),
260+
'utf-8',
261+
);
262+
}
263+
await removeDir(TMP_DIR);
264+
};
265+
200266
const npmInstall = async () => {
201267
const {exec} = await import('child_process');
202268
const {promisify} = await import('util');
@@ -216,14 +282,23 @@ export const lint = async () => await lintCheck('.');
216282
export const spell = async () => {
217283
await spellCheck('.');
218284
await spellCheck('src', true);
285+
await spellCheck('test', true);
219286
};
220287

221288
export const ts = async () => {
222289
await copyDefinitions();
223290
await tsCheck('src');
224291
await copyDefinitions(`${LIB_DIR}/debug`);
292+
await tsCheck('test');
225293
};
226294

295+
export const compileForTest = async () => {
296+
await clearDir(LIB_DIR);
297+
await testModules(async (module) => {
298+
await compileModule(module, true, `${LIB_DIR}/debug`);
299+
});
300+
await copyDefinitions(`${LIB_DIR}/debug`);
301+
};
227302
export const compileForProd = async () => {
228303
await clearDir(LIB_DIR);
229304
await allModules(async (module) => {
@@ -235,13 +310,36 @@ export const compileForProd = async () => {
235310
await copyDefinitions(`${LIB_DIR}/debug`);
236311
};
237312

238-
export const preCommit = gulp.series(lint, spell, ts, compileForProd);
313+
export const testUnit = async () => {
314+
await test('test/unit', {coverage: true});
315+
};
316+
export const testUnitCountAsserts = async () => {
317+
await test('test/unit', {coverage: true, countAsserts: true});
318+
};
319+
export const compileAndTestUnit = gulp.series(compileForTest, testUnit);
320+
321+
export const testPerf = async () => {
322+
await test('test/perf', {serialTests: true});
323+
};
324+
export const compileAndTestPerf = gulp.series(compileForTest, testPerf);
325+
326+
export const preCommit = gulp.series(
327+
lint,
328+
spell,
329+
ts,
330+
compileForTest,
331+
testUnit,
332+
compileForProd,
333+
);
239334

240335
export const prePublishPackage = gulp.series(
241336
npmInstall,
242337
lint,
243338
spell,
244339
ts,
340+
compileForTest,
341+
testUnitCountAsserts,
342+
testPerf,
245343
compileForProd,
246344
);
247345

jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
verbose: false,
3+
};

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
"lint": "gulp lint",
3030
"spell": "gulp spell",
3131
"ts": "gulp ts",
32+
"test": "gulp --series testUnit testPerf",
33+
"compileForTest": "gulp compileForTest",
34+
"compileForProd": "gulp compileForProd",
35+
"testUnit": "gulp testUnit",
36+
"testUnitCountAsserts": "gulp testUnitCountAsserts",
37+
"testPerf": "gulp testPerf",
3238
"compileAndTestUnit": "gulp compileAndTestUnit",
3339
"preCommit": "gulp preCommit",
3440
"prePublishPackage": "gulp prePublishPackage",

test/jest/environment.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// eslint-disable-next-line @typescript-eslint/no-var-requires
2+
import JsDomEnvironment from 'jest-environment-jsdom';
3+
4+
export default class extends JsDomEnvironment {
5+
static assertionCalls = 0;
6+
async setup() {
7+
this.global.env = this.constructor;
8+
await super.setup();
9+
}
10+
}

test/jest/reporter.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// eslint-disable-next-line @typescript-eslint/no-var-requires
2+
import env from './environment.js';
3+
import {writeFileSync} from 'fs';
4+
5+
export default class {
6+
onRunComplete(_contexts, results) {
7+
writeFileSync(
8+
'./tmp/assertion-summary.json',
9+
JSON.stringify({
10+
tests: results.numTotalTests,
11+
assertions: env.assertionCalls,
12+
}),
13+
'utf-8',
14+
);
15+
console.log('Assertions: ', env.assertionCalls, '\n');
16+
}
17+
}

test/jest/setup.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
afterEach(
2+
() => (global.env.assertionCalls += expect.getState().assertionCalls),
3+
);

test/perf/benchmark.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {Schema, createStore} from '../../lib/debug/tinybase';
2+
import {µs} from './common';
3+
4+
const SIZE = 50;
5+
6+
test(`${Math.pow(SIZE, 3)} cells benchmark`, () => {
7+
const schema: Schema = {};
8+
for (let tableId = 0; tableId <= SIZE; tableId++) {
9+
schema[tableId] = {};
10+
for (let cellId = 0; cellId <= SIZE; cellId++) {
11+
schema[tableId][cellId] = {type: 'number', default: -1};
12+
}
13+
}
14+
15+
const store2 = createStore();
16+
let cells2 = 0;
17+
store2.addCellListener(null, null, null, () => cells2++);
18+
const time2 = µs(() => {
19+
for (let tableId = 1; tableId <= SIZE; tableId++) {
20+
for (let rowId = 1; rowId <= SIZE; rowId++) {
21+
for (let cellId = 1; cellId <= SIZE; cellId++) {
22+
store2.setPartialRow(tableId + '', rowId + '', {[cellId + '']: 2});
23+
}
24+
}
25+
}
26+
});
27+
console.log(`${cells2} cells changed in ${Math.round(time2 / 10000) / 100}s`);
28+
29+
const store = createStore();
30+
let cells = 0;
31+
store.addCellListener(null, null, null, () => cells++);
32+
const time = µs(() => {
33+
for (let tableId = 1; tableId <= SIZE; tableId++) {
34+
for (let rowId = 1; rowId <= SIZE; rowId++) {
35+
for (let cellId = 1; cellId <= SIZE; cellId++) {
36+
store.setCell(tableId + '', rowId + '', cellId + '', 2);
37+
}
38+
}
39+
}
40+
});
41+
console.log(`${cells} cells changed in ${Math.round(time / 10000) / 100}s`);
42+
43+
expect(1).toEqual(1);
44+
});

test/perf/common.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/* eslint-disable @typescript-eslint/ban-ts-comment, jest/no-export */
2+
import {Row, Table, Tables} from '../../lib/debug/tinybase';
3+
import {blue, plot, red, yellow} from 'asciichart';
4+
import {performance} from 'perf_hooks';
5+
6+
export const repeat = (
7+
name: string,
8+
noun: string,
9+
unit: string,
10+
actions: (N: number, stepSize: number) => [number, number],
11+
maxResult: number,
12+
before?: () => void,
13+
maxN?: number,
14+
steps?: number,
15+
): void => {
16+
test(`${name}`, () => {
17+
if (before != null) {
18+
before();
19+
}
20+
21+
if (maxN == null) {
22+
maxN = 10000;
23+
}
24+
25+
if (steps == null) {
26+
steps = 40;
27+
}
28+
29+
const stepSize = maxN / steps;
30+
const runs = [];
31+
for (let N = stepSize; N <= maxN; N += stepSize) {
32+
runs.push(N);
33+
}
34+
35+
let totalDuration = 0;
36+
let totalCount = 0;
37+
38+
const results = runs.map((N) => {
39+
const [duration, count] = actions(N, stepSize);
40+
totalDuration += duration;
41+
totalCount += count;
42+
return parseFloat((duration / count).toFixed(2));
43+
});
44+
45+
const average = parseFloat((totalDuration / totalCount).toFixed(2));
46+
const averages = new Array(steps).fill(average);
47+
const maxes = new Array(steps).fill(maxResult);
48+
49+
console.log(
50+
`${name} with multiple ${noun}, ${unit}\n` +
51+
`First: ${results[0]} ${unit}\n` +
52+
` Last: ${results[results.length - 1]} ${unit}\n` +
53+
` Min: ${Math.min(...results)} ${unit}\n` +
54+
` Max: ${Math.max(...results)} ${unit}\n` +
55+
` Avg: ${average} ${unit}\n\n` +
56+
// @ts-ignore
57+
`${plot([maxes, averages, results], {
58+
height: 15,
59+
min: 0,
60+
max: maxResult,
61+
colors: [red, blue, yellow],
62+
})}\n`,
63+
);
64+
65+
expect(average).toBeLessThan(maxResult);
66+
});
67+
};
68+
69+
export const repeatRows = (
70+
name: string,
71+
actions: (n: number) => void,
72+
maxResult: number,
73+
before?: () => void,
74+
): void => {
75+
repeat(
76+
name,
77+
'row count',
78+
'µs per row',
79+
(N, stepSize) => [
80+
µs(() => {
81+
for (let n = N - stepSize; n <= N; n++) {
82+
actions(n);
83+
}
84+
}),
85+
stepSize,
86+
],
87+
maxResult,
88+
before,
89+
);
90+
};
91+
92+
export const µs = (actions: () => void): number => {
93+
const start = performance.now();
94+
actions();
95+
return 1000 * (performance.now() - start);
96+
};
97+
98+
export const getNTables = (N: number): Tables => {
99+
const tables: Tables = {};
100+
for (let n = 1; n <= N; n++) {
101+
tables['table' + n] = {row: {cell: n}};
102+
}
103+
return tables;
104+
};
105+
106+
export const getNRows = (N: number): Table => {
107+
const table: Table = {};
108+
for (let n = 1; n <= N; n++) {
109+
table['row' + n] = {cell: n};
110+
}
111+
return table;
112+
};
113+
114+
export const getNCells = (N: number): Row => {
115+
const row: Row = {};
116+
for (let n = 1; n <= N; n++) {
117+
row['cell' + n] = n;
118+
}
119+
return row;
120+
};

0 commit comments

Comments
 (0)