From 35a3e8afd552c21182403679ef44436960322451 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sat, 2 Mar 2019 23:34:43 +0100 Subject: [PATCH] implement repetition of tests --- array.js | 1 + diff.test.js | 15 +++++++-------- encoding.test.js | 21 +++++++-------------- math.js | 1 + prng.test.js | 24 +++++++++++++----------- statistics.js | 10 ++++++++++ testing.js | 45 +++++++++++++++++++++++++++++++-------------- testing.test.js | 10 ++++++++++ 8 files changed, 80 insertions(+), 47 deletions(-) create mode 100644 array.js create mode 100644 statistics.js diff --git a/array.js b/array.js new file mode 100644 index 0000000..e402c82 --- /dev/null +++ b/array.js @@ -0,0 +1 @@ +export const last = arr => arr[arr.length - 1] diff --git a/diff.test.js b/diff.test.js index 44349c1..a834e9b 100644 --- a/diff.test.js +++ b/diff.test.js @@ -16,13 +16,12 @@ export const testDiffing = tc => { runDiffTest('abc', 'xyz', { pos: 0, remove: 3, insert: 'xyz' }) runDiffTest('axz', 'au', { pos: 1, remove: 2, insert: 'u' }) runDiffTest('ax', 'axy', { pos: 2, remove: 0, insert: 'y' }) +} - t.describe(`Running ${tc.repititions} random tests`) - for (let i = 0; i < tc.repititions; i++) { - const a = prng.word(tc.prng) - const b = prng.word(tc.prng) - const change = simpleDiff(a, b) - const recomposed = `${a.slice(0, change.pos)}${change.insert}${a.slice(change.pos + change.remove)}` - t.compareStrings(recomposed, b) - } +export const testRepeatDiffing = tc => { + const a = prng.word(tc.prng) + const b = prng.word(tc.prng) + const change = simpleDiff(a, b) + const recomposed = `${a.slice(0, change.pos)}${change.insert}${a.slice(change.pos + change.remove)}` + t.compareStrings(recomposed, b) } diff --git a/encoding.test.js b/encoding.test.js index 3cd36d7..a3a665e 100644 --- a/encoding.test.js +++ b/encoding.test.js @@ -85,17 +85,11 @@ export const testVarUintEncoding = tc => { test('varUint 3 bytes', writeVarUint, readVarUint, 1 << 17 | 1 << 9 | 3) test('varUint 4 bytes', writeVarUint, readVarUint, 1 << 25 | 1 << 17 | 1 << 9 | 3) test('varUint of 2839012934', writeVarUint, readVarUint, 2839012934) +} - t.describe(`Running ${tc.repititions} random tests on varUint`) - let allUtf8ByteLength = 0 - let allBinaryByteLength = 0 - for (let i = 0; i < tc.repititions; i++) { - const n = prng.int31(tc.prng, 0, (1 << 28) - 1) - const { utf8ByteLength, binaryByteLength } = test(`varUint of ${n}`, writeVarUint, readVarUint, n, false) - allUtf8ByteLength += utf8ByteLength - allBinaryByteLength += binaryByteLength - } - t.describe(`compression of ${math.round((allBinaryByteLength / allUtf8ByteLength) * 100)}%`) +export const testRepeatVarUintEncoding = tc => { + const n = prng.int31(tc.prng, 0, (1 << 28) - 1) + test(`varUint of ${n}`, writeVarUint, readVarUint, n, false) } export const testStringEncoding = tc => { @@ -107,8 +101,7 @@ export const testStringEncoding = tc => { testVarString('쾟') testVarString('龟') // surrogate length 3 testVarString('😝') // surrogate length 4 - t.describe(`Running ${tc.repititions} random tests on varString`) - for (let i = 0; i < tc.repititions; i++) { - testVarString(prng.utf16String(tc.prng)) - } } + +export const testRepeatStringEncoding = tc => + testVarString(prng.utf16String(tc.prng)) \ No newline at end of file diff --git a/math.js b/math.js index bb94ff4..0b9b1f0 100644 --- a/math.js +++ b/math.js @@ -2,6 +2,7 @@ * @module math */ export const floor = Math.floor +export const ceil = Math.ceil export const round = Math.round diff --git a/prng.test.js b/prng.test.js index 8353b0f..cba5be5 100644 --- a/prng.test.js +++ b/prng.test.js @@ -9,6 +9,8 @@ import { Mt19937 } from './prng/Mt19937.js' import * as dom from './dom.js' import { isBrowser } from './environment.js' +const genTestData = 5000 + /** * @param {t.TestCase} tc * @param {prng.PRNG} gen @@ -20,7 +22,7 @@ const runGenTest = (tc, gen) => { let b let i - for (i = 0; i < tc.repititions; i++) { + for (i = 0; i < genTestData; i++) { b = prng.bool(gen) if (b) { head++ @@ -29,17 +31,17 @@ const runGenTest = (tc, gen) => { } } t.info(`Generated ${head} heads and ${tail} tails.`) - t.assert(tail >= Math.floor(tc.repititions * 0.48), 'Generated enough tails.') - t.assert(head >= Math.floor(tc.repititions * 0.48), 'Generated enough heads.') + t.assert(tail >= Math.floor(genTestData * 0.48), 'Generated enough tails.') + t.assert(head >= Math.floor(genTestData * 0.48), 'Generated enough heads.') }) t.group('int31 - integers average correctly', () => { let count = 0 let i - for (i = 0; i < tc.repititions; i++) { + for (i = 0; i < genTestData; i++) { count += prng.int31(gen, 0, 100) } - const average = count / tc.repititions + const average = count / genTestData const expectedAverage = 100 / 2 t.info(`Average is: ${average}. Expected average is ${expectedAverage}.`) t.assert(Math.abs(average - expectedAverage) <= 2, 'Expected average is at most 1 off.') @@ -49,7 +51,7 @@ const runGenTest = (tc, gen) => { let num = 0 let i let newNum - for (i = 0; i < tc.repititions; i++) { + for (i = 0; i < genTestData; i++) { newNum = prng.int32(gen, 0, BITS32) if (newNum > num) { num = newNum @@ -63,7 +65,7 @@ const runGenTest = (tc, gen) => { let num = 0 let i let newNum - for (i = 0; i < tc.repititions; i++) { + for (i = 0; i < genTestData; i++) { newNum = prng.int31(gen, 0, BITS31) if (newNum > num) { num = newNum @@ -77,7 +79,7 @@ const runGenTest = (tc, gen) => { let num = 0 let i let newNum - for (i = 0; i < tc.repititions; i++) { + for (i = 0; i < genTestData; i++) { newNum = prng.real53(gen) * MAX_SAFE_INTEGER if (newNum > num) { num = newNum @@ -93,7 +95,7 @@ const runGenTest = (tc, gen) => { for (let i = chars.length - 1; i >= 0; i--) { charSet.add(chars[i]) } - for (let i = 0; i < tc.repititions; i++) { + for (let i = 0; i < genTestData; i++) { const char = prng.char(gen) charSet.delete(char) } @@ -114,14 +116,14 @@ export const testGeneratorMt19937 = tc => runGenTest(tc, new Mt19937(tc.seed)) * @param {t.TestCase} tc */ const printDistribution = (gen, tc) => { - const DIAMETER = tc.repititions / 50 + const DIAMETER = genTestData / 50 const canvas = dom.canvas(DIAMETER * 3, DIAMETER) const ctx = canvas.getContext('2d') if (ctx == null) { return t.skip() } ctx.fillStyle = 'blue' - for (let i = 0; i < tc.repititions; i++) { + for (let i = 0; i < genTestData; i++) { const x = prng.int31(gen, 0, DIAMETER * 3) const y = prng.int31(gen, 0, DIAMETER) ctx.fillRect(x, y, 1, 2) diff --git a/statistics.js b/statistics.js new file mode 100644 index 0000000..be582a0 --- /dev/null +++ b/statistics.js @@ -0,0 +1,10 @@ + +import * as math from './math.js' + +/** + * @param {Array} arr Array of values + * @return {number} + */ +export const median = arr => (arr[math.floor(arr.length / 2)] + arr[math.ceil(arr.length / 2)]) / 2 + +export const average = arr => arr.reduce(math.add, 0) / arr.length diff --git a/testing.js b/testing.js index 3cd8020..4ec0a14 100644 --- a/testing.js +++ b/testing.js @@ -9,6 +9,8 @@ import * as string from './string.js' import * as math from './math.js' import * as random from './random.js' import * as prng from './prng.js' +import * as statistics from './statistics.js' +import * as array from './array.js' const seed = random.uint32() @@ -20,7 +22,6 @@ export class TestCase { this.prng = prng.create(seed) this.moduleName = moduleName this.testName = testName - this.repititions = 5000 } get seed () { return seed @@ -32,39 +33,54 @@ const perf = typeof performance === 'undefined' ? require('perf_hooks').performance : performance // eslint-disable-line no-undef +const repititionTime = 50 + +const repeatTestRegex = /^(repeat|repeating)\s/ + export const run = async (moduleName, name, f, i, numberOfTests) => { const t = new TestCase(moduleName, name) const uncamelized = string.fromCamelCase(name.slice(4), ' ') + const repeat = repeatTestRegex.test(uncamelized) log.groupCollapsed(log.GREY, `[${i + 1}/${numberOfTests}] `, log.PURPLE, `${moduleName}: `, log.BLUE, uncamelized) perf.mark(`${name}-start`) let err = null + const times = [] const start = perf.now() - try { - const p = f(t) - if (p != null && p.constructor === Promise) { - await p + let lastTime = start + do { + try { + const p = f(t) + if (p != null && p.constructor === Promise) { + await p + } + } catch (_err) { + err = _err } - } catch (_err) { - err = _err - } - const end = perf.now() + const currTime = perf.now() + times.push(currTime - lastTime) + lastTime = currTime + } while (repeat && err === null && (lastTime - start) < repititionTime) perf.mark(`${name}-end`) if (err !== null && err.constructor !== SkipError) { log.printError(err) } log.groupEnd() perf.measure(name, `${name}-start`, `${name}-end`) - const duration = end - start + const duration = lastTime - start let success = true + times.sort() + const timeInfo = repeat + ? ` - ${times.length} repititions in ${duration.toFixed(2)}ms (best: ${times[0].toFixed(2)}ms, worst: ${array.last(times).toFixed(2)}ms, median: ${statistics.median(times).toFixed(2)}ms, average: ${statistics.average(times).toFixed(2)}ms)` + : ` in ${duration.toFixed(2)}ms` if (err !== null) { if (err.constructor === SkipError) { log.print(log.GREY, log.BOLD, 'Skipped: ', log.UNBOLD, uncamelized) } else { success = false - log.print(log.RED, log.BOLD, 'Failure: ', log.UNBOLD, log.UNCOLOR, uncamelized, log.GREY, ` in ${duration}ms`) + log.print(log.RED, log.BOLD, 'Failure: ', log.UNBOLD, log.UNCOLOR, uncamelized, log.GREY, timeInfo) } } else { - log.print(log.GREEN, log.BOLD, 'Success: ', log.UNBOLD, log.UNCOLOR, uncamelized, log.GREY, ` in ${duration}ms`) + log.print(log.GREEN, log.BOLD, 'Success: ', log.UNBOLD, log.UNCOLOR, uncamelized, log.GREY, timeInfo) } return success } @@ -188,6 +204,7 @@ export const runTests = async tests => { const numberOfTests = object.map(tests, mod => object.map(mod, f => f ? 1 : 0).reduce(math.add, 0)).reduce(math.add, 0) let successfulTests = 0 let i = 0 + const start = perf.now() for (const modName in tests) { const mod = tests[modName] for (const fname in mod) { @@ -200,10 +217,10 @@ export const runTests = async tests => { } } } - + const end = perf.now() log.print('') if (successfulTests === numberOfTests) { - log.print(log.GREEN, log.BOLD, 'All tests successful!') + log.print(log.GREEN, log.BOLD, 'All tests successful!', log.GREY, log.UNBOLD, ` in ${(end - start).toFixed(2)}ms`) log.printImgBase64(nyanCatImage, 50) } else { const failedTests = numberOfTests - successfulTests diff --git a/testing.test.js b/testing.test.js index 11c5824..565e8fc 100644 --- a/testing.test.js +++ b/testing.test.js @@ -1,4 +1,5 @@ import * as t from './testing.js' +import * as math from './math.js' export const testComparing = () => { t.compare({}, {}) @@ -18,3 +19,12 @@ export const testSkipping = () => { t.skip() t.fail('should have skipped') } + +export const testRepeatRepitition = () => { + const arr = [] + const n = 100 + for (let i = 1; i <= n; i++) { + arr.push(i) + } + t.assert(arr.reduce(math.add, 0) === (n+1)*n/2, 'We can count the smart way') +}