diff --git a/.gitignore b/.gitignore index ec70e51..9895e88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .DS_Store +.vscode node_modules package-lock.json +yarn.lock tests/coverage diff --git a/.travis.yml b/.travis.yml index 0433354..df0d8ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,31 +1,33 @@ language: node_js dist: trusty env: - - DISPLAY=:99.0 CHROME_BIN=chromium-browser +- DISPLAY=:99.0 CHROME_BIN=chromium-browser node_js: - - "7" +- "7" addons: - firefox: latest +- firefox: latest before_script: - - sh -e /etc/init.d/xvfb start -before_deploy: - - mkdir -p deploy - - ./build.py -l lib -d deploy +- sh -e /etc/init.d/xvfb start +script: +- yarn run build +after_success: +- yarn run docs notifications: slack: secure: Kwe1KBh4SMzgXaj6GQg3ZmopRYcDR3Vnd4C/gyiEOJsXzKRlU5dA7WwM/mbyOe9+ZvWDyp+g2CoQYYyvLR0SHEy1m0gn6M8fBzgSZQlWxOxAJxkwVsyxUNwAy2FylMpS+ugpw/fBMSbnSFqfZSa/tGB3KhBc0yA7V9wVS0hnwBfICqKKlFdLPcp+Us4zpXRUmcL1e0BVwm2klJi0CoKlOmTa4lI3tUa/YiuuMpgk89/PioMEr6/PP+5OfWV624djw2gznoualvcwdfZniC/oxkdTUbcu4nbTf+bFk5uwwW+XBpUhm9rMUoftngYCrWdtYHS6qenyzmflO5bgCDg5W8onv3qgMcDTEHDjlO8JMoEQLC2o27Hsyukrh2Iu0Hn2k4S10ZIDWLUZXf8ERKdU7v8o5xaiCrP7NO8fJlyGbWwYxwpFi9dpacb8qWuWw53k8+ld9orl2Zu5t1Y1QJCvT0DP0iGYSxbbICfex7im0fgh3A+MHTfhxxiGQtFJRUxqO5wGCIsT4dJJkYwRJ1HoCPyLT1x1WT8iy4lr8ivRhanEwQ5g2OmuRKz522yCrm2DPaRR/LDhqK9xghJqqh4RNx9Be5xNaxSxKs4Hcya4ZXrGaU9tzGTehRkNQksKUHq9PpdY85Mbk84wEq7zKLhtLPTn17DQePFm2DbdX4392o4= deploy: - - provider: releases - api_key: - secure: HszUovTqcRxuXTmH3/rStEe1ZeZbrZweQah8Bq0POs89P3/GjyEat11WbH9S7tfIT1yVcF736TmsDNRtl34Rmr71P7rczwFlX1yJOIamH4jT9vG8ocpZH4Orc6XA2s7UclD1HwJwStM1k2J4CV34eTTlX36ngjYrp09HertXAQWSK6TZk9RK6ql2HlNzchImz2EY1aQnfqkrFByOtM4o/SJJryJJBJUMD69gWrYu6cv51w/jMEoUtzvV6Nr2o9YOuo97B0scJmbxgfx67FaCQwEOxKtr8xT5nttJ2q6EeaAiErAieG8/o8yRsJRFrHyVm42v9PEfoe8O8Y4g/1VrtbjY3vH8LPpCotMxhshjUCBaLDc8TCC/osik6dxhv/fqjbdPwrGRW2qzsmXIZug7+YCEW+qUFRPwwX+Cr0ENnaPFMSDFxANeLF7KfF7oaVnr8JTlZnu9G+Uliqe6RNCoWfQtpTRM759cDLQp4b5eBQF2G61x2XOdRqcNZepRaRVnsaCo0lNPW9o5HCQnJnMbEB9Gdowt18PwgVucIdIPJeAPodMs40s7sUOP5Xik58b4YBG9lsgLBcfCgTy+n8JPS2LJsclG82pBACtSV+ucc6rVeD+5fbSUxn4ItaK4e0MonCv4Jnp7zGn6hErcpexBmXSdYQSrdsemKDTGHibOF6U= - file: deploy/octo.js - skip_cleanup: true - on: - tags: true - - provider: pages - github_token: - secure: HszUovTqcRxuXTmH3/rStEe1ZeZbrZweQah8Bq0POs89P3/GjyEat11WbH9S7tfIT1yVcF736TmsDNRtl34Rmr71P7rczwFlX1yJOIamH4jT9vG8ocpZH4Orc6XA2s7UclD1HwJwStM1k2J4CV34eTTlX36ngjYrp09HertXAQWSK6TZk9RK6ql2HlNzchImz2EY1aQnfqkrFByOtM4o/SJJryJJBJUMD69gWrYu6cv51w/jMEoUtzvV6Nr2o9YOuo97B0scJmbxgfx67FaCQwEOxKtr8xT5nttJ2q6EeaAiErAieG8/o8yRsJRFrHyVm42v9PEfoe8O8Y4g/1VrtbjY3vH8LPpCotMxhshjUCBaLDc8TCC/osik6dxhv/fqjbdPwrGRW2qzsmXIZug7+YCEW+qUFRPwwX+Cr0ENnaPFMSDFxANeLF7KfF7oaVnr8JTlZnu9G+Uliqe6RNCoWfQtpTRM759cDLQp4b5eBQF2G61x2XOdRqcNZepRaRVnsaCo0lNPW9o5HCQnJnMbEB9Gdowt18PwgVucIdIPJeAPodMs40s7sUOP5Xik58b4YBG9lsgLBcfCgTy+n8JPS2LJsclG82pBACtSV+ucc6rVeD+5fbSUxn4ItaK4e0MonCv4Jnp7zGn6hErcpexBmXSdYQSrdsemKDTGHibOF6U= - local_dir: deploy - skip_cleanup: true - on: - branch: master +- provider: releases + api_key: + secure: HszUovTqcRxuXTmH3/rStEe1ZeZbrZweQah8Bq0POs89P3/GjyEat11WbH9S7tfIT1yVcF736TmsDNRtl34Rmr71P7rczwFlX1yJOIamH4jT9vG8ocpZH4Orc6XA2s7UclD1HwJwStM1k2J4CV34eTTlX36ngjYrp09HertXAQWSK6TZk9RK6ql2HlNzchImz2EY1aQnfqkrFByOtM4o/SJJryJJBJUMD69gWrYu6cv51w/jMEoUtzvV6Nr2o9YOuo97B0scJmbxgfx67FaCQwEOxKtr8xT5nttJ2q6EeaAiErAieG8/o8yRsJRFrHyVm42v9PEfoe8O8Y4g/1VrtbjY3vH8LPpCotMxhshjUCBaLDc8TCC/osik6dxhv/fqjbdPwrGRW2qzsmXIZug7+YCEW+qUFRPwwX+Cr0ENnaPFMSDFxANeLF7KfF7oaVnr8JTlZnu9G+Uliqe6RNCoWfQtpTRM759cDLQp4b5eBQF2G61x2XOdRqcNZepRaRVnsaCo0lNPW9o5HCQnJnMbEB9Gdowt18PwgVucIdIPJeAPodMs40s7sUOP5Xik58b4YBG9lsgLBcfCgTy+n8JPS2LJsclG82pBACtSV+ucc6rVeD+5fbSUxn4ItaK4e0MonCv4Jnp7zGn6hErcpexBmXSdYQSrdsemKDTGHibOF6U= + file: deploy/octo.js + skip_cleanup: true + on: + tags: true +- provider: pages + keep-history: true + skip_cleanup: true + github_token: + secure: PvCrB9ckDfWedl1rdo/3mUvCy/Vg4ksZR2mj4KT3AdEyukbpMiVVDkocMFohuIq+mnRdeyhpvM2KluLHhQrEhWjvqVobiZ9/c0gWWxZHV8doDdqNUPm693MpGojMOxHT4qRih6x9KyjanmWAwy/Bc972dpD5vtaTx1gKOzRUjUI= + on: + branch: master + local_dir: docs/ diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 5cc3bff..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = function (grunt) { - let pkg = grunt.file.readJSON('package.json') - - grunt.initConfig({ - pkg: pkg, - - karma: { - unit: { - configFile: 'karma.conf.js' - } - }, - coveralls: { - options: { - coverageDir: 'tests/coverage/', - force: true - } - }, - standard: { - options: pkg.standard, - lib: { - src: ['lib/**/*.js'] - } - } - }) - - grunt.loadNpmTasks('grunt-karma') - grunt.loadNpmTasks('grunt-karma-coveralls') - grunt.loadNpmTasks('grunt-standard') - - grunt.registerTask('test', ['standard', 'karma', 'coveralls']) -} diff --git a/README.md b/README.md index 4aaf101..9f661d9 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,24 @@ Octo.js bundles core functions and generic boilerplate code commonly used in mos Octo's future aims to be a stable, well-tested and well-documented standard library for fuzzing in a JavaScript environment. -## Note -The ES6 branch is under active development while we are incorporating it with our existing fuzzers. +## Table of Contents +- [Table of Contents](#table-of-contents) + - [Playbook](#playbook) + - [Usage in Node](#usage-in-node) + - [Usage in Browser](#usage-in-browser) + - [Develop](#develop) + - [Testing](#testing) + - [API Documentation](#api-documentation) - -## Playbook +### Playbook https://runkit.com/posidron/octo-js-playbook -## Node +### Usage in Node ``` -npm i @mozillasecurity/octo +yarn add @mozillasecurity/octo ``` ``` @@ -38,33 +43,35 @@ const {random} = require('@mozillasecurity/octo') random.init() ``` - -## Browser - -We have not yet merged ES6 to master, hence the browser version which was released on master is not up-to-date. -Use the `dist/octo.js` version of this branch by running the following command. +### Usage in Browser ``` -npm run build +yarn build ``` - -## Development +### Develop ```bash -npm install -npm run build -npm run watch -npm run test:lint +yarn install +yarn lint +yarn test +yarn build ``` -## Testing +### Testing -Tests live in the `tests/` subdirectory, and are written for [QUnit](https://qunitjs.com/). -To run tests locally, open `tests/index.html` in a browser. -The automated tests are run in Firefox or Chrome using [Karma](https://karma-runner.github.io/). -To run the automated tests: +Octo.js uses Jest for testing. Each directory should contain a `__tests__` folder containing the tests. ```bash -npm test +yarn test +``` + +### API Documentation + +* https:// + +or + +``` +yarn docs ``` diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 324735b..0000000 --- a/karma.conf.js +++ /dev/null @@ -1,60 +0,0 @@ -module.exports = function (config) { - let configuration = { - basePath: './tests', - - frameworks: ['qunit'], - - files: [ - '../lib/utils/init.js', - '../lib/utils/*.js', - '../lib/logging/*.js', - '../lib/make/init.js', - '../lib/make/*.js', - '../lib/random/*.js', - '**/*.js' - ], - - exclude: [ - ], - - preprocessors: { - '../lib/**/*.js': ['coverage'] - }, - - reporters: ['progress', 'coverage'], - - port: 9876, - - colors: true, - - logLevel: config.LOG_INFO, - - autoWatch: true, - - browsers: ['Chrome', 'Firefox'], - - singleRun: true, - - browserNoActivityTimeout: 30000, - - customLaunchers: { - Chrome_travis_ci: { - base: 'Chrome', - flags: ['--no-sandbox'] - } - }, - - coverageReporter: { - reporters: [ - { type: 'lcov', dir: 'coverage/' }, - { type: 'text-summary' } - ] - } - } - - if (process.env.TRAVIS) { - configuration.browsers = ['Chrome_travis_ci', 'Firefox'] - } - - config.set(configuration) -} diff --git a/lib/random/__tests__/mersennetwister.test.js b/lib/random/__tests__/mersennetwister.test.js new file mode 100644 index 0000000..49f2764 --- /dev/null +++ b/lib/random/__tests__/mersennetwister.test.js @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-env jest */ +const MersenneTwister = require('../mersennetwister') + +describe('MersenneTwister', () => { + test('uniform distribution', () => { + const N = Math.pow(2, 18) + const TRIES = 10 + const XSQ = 293.25 // quantile of chi-square dist. k=255, p=.05 + + let mt = new MersenneTwister() + mt.seed(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let data = new Uint32Array(N) + let sh + for (let i = 0; i < data.length; ++i) { + data[i] = mt.int32() + } + for (sh = 0; sh <= 24; ++sh) { + let bins = new Uint32Array(256) + for (let b of data) { + ++bins[(b >>> sh) & 0xff] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 255) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + if (sh === 25) { + return + } + } + return false + } + + expect(_test()).toBe(true) + }) + + test('float distribution', () => { + const N = Math.pow(2, 18) + const TRIES = 3 + const XSQ = 564.7 // quantile of chi-square dist. k=511, p=.05 + + let mt = new MersenneTwister() + mt.seed(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(512) + for (let i = 0; i < N; ++i) { + let tmp = (mt.real2() * bins.length) >>> 0 + if (tmp >= bins.length) { + throw new Error('random.float() >= 1.0') + } + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 511) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + expect(_test()).toBe(true) + }) +}) diff --git a/lib/random/__tests__/random.test.js b/lib/random/__tests__/random.test.js new file mode 100644 index 0000000..d3434e9 --- /dev/null +++ b/lib/random/__tests__/random.test.js @@ -0,0 +1,1374 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-disable camelcase */ +/* eslint-env jest */ +const random = require('../random') + +describe('Random', () => { + test('init', () => { + expect(() => { + random.number() + }).toThrow() + random.init(1) + random.number() + }) + + test('number() corner cases', () => { + random.init(Math.random() * 0x100000000) + let sum = 0 + + for (let i = 0; i < 100; ++i) { + sum += random.number(0) + } + expect(sum).toEqual(0) + + for (let i = 0; i < 100; ++i) { + sum += random.number(1) + } + expect(sum).toEqual(0) + + let bins = new Uint32Array(2) + for (let i = 0; i < 100; ++i) { + ++bins[random.number(2)] + } + expect(bins[0] + bins[1]).toEqual(100) + expect(bins[0]).toBeGreaterThan(20) + + sum = 0 + for (let i = 0; i < 15; ++i) { + sum |= random.number() + } + expect(sum >>> 0).toEqual(0xffffffff) + }) + + test('number() uniform distriution', () => { + const N = Math.pow(2, 17) + const TRIES = 3 + const XSQ = 564.7 // quantile of chi-square dist. k=511, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(512) + for (let i = 0; i < N; ++i) { + let tmp = random.number(bins.length) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 511) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('float() uniform distribution', () => { + const N = Math.pow(2, 17) + const TRIES = 3 + const XSQ = 564.7 // quantile of chi-square dist. k=511, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(512) + for (let i = 0; i < N; ++i) { + let tmp = (random.float() * bins.length) >>> 0 + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 511) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('range() uniform distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 66.34 // quantile of chi-square dist. k=49, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(50) + for (let i = 0; i < N; ++i) { + let tmp = random.range(0, bins.length - 1) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 49) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('range() uniform distribution with offset', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 66.34 // quantile of chi-square dist. k=49, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(50) + for (let i = 0; i < N; ++i) { + let tmp = random.range(10, 10 + bins.length - 1) - 10 + expect(tmp).toBeGreaterThanOrEqual(0) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 49) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('range() PRNG reproducibility', () => { + let seed, result1, result2 + seed = Math.random() * 0x100000000 + for (let t = 0; t < 50; ++t) { + random.init(seed) + result1 = random.range(1, 20) + for (let i = 0; i < 5; ++i) { + random.init(seed) + result2 = random.range(1, 20) + expect(result1).toBe(result2) + } + seed = random.number() + } + }) + + test('item() exception cases', () => { + expect(() => { + random.item() + }).toThrowError('random.item() received invalid object: (undefined)') + expect(() => { + random.item(1) + }).toThrowError('random.item() received invalid object: (1)') + expect(() => { + random.item('1') + }).toThrowError('random.item() received invalid object: (1)') + expect(() => { + random.item({}) + }).toThrowError('random.item() received invalid object: ([object Object])') + }) + + test('item() distribution with list', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.item([99, 100, 101]) - 99 + expect(tmp).toBeGreaterThanOrEqual(0) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('key() distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.key({ 99: 0, 100: 0, 101: 0 }) - 99 + expect(tmp).toBeGreaterThanOrEqual(0) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + expect(_test()).toBe(true) + }) + + test('bool() distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 3.84 // quantile of chi-square dist. k=1, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(2) + for (let i = 0; i < N; ++i) { + let tmp = random.bool() + if (tmp === true) { + tmp = 1 + } else if (tmp === false) { + tmp = 0 + } else { + throw new Error(`Unexpected random.bool() result: ${tmp}`) + } + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 1) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + expect(_test()).toBe(true) + }) + + test('pick() cases', () => { + random.init(Math.random() * 0x100000000) + + for (let i = 0; i < 100; ++i) { + let tmp = Math.random() + expect(random.pick(tmp)).toEqual(tmp) + } + + for (let i = 0; i < 100; ++i) { + let tmp = (Math.random() * 100) >>> 0 + expect(random.pick(tmp)).toEqual(tmp) + } + + for (let i = 0; i < 100; ++i) { + let tmp = Math.random() + '' + expect(random.pick(tmp)).toEqual(tmp) + } + + for (let i = 0; i < 100; ++i) { + let tmp = Math.random() + expect(random.pick([tmp])).toEqual(tmp) + } + + for (let i = 0; i < 100; ++i) { + let tmp = Math.random() + expect(random.pick(() => tmp)).toEqual(tmp) + } + + for (let i = 0; i < 100; ++i) { + let tmp = Math.random() + expect(random.pick(() => [tmp])).toEqual([tmp]) + } + }) + + test('pick() with equal distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.pick([ + 0, + [1, 1], + () => 2 + ]) + expect(tmp).toBeGreaterThanOrEqual(0) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('pick() with unequal distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.pick([ + [0, 1], + [1], + () => 2 + ]) + expect(tmp).toBeGreaterThanOrEqual(0) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = + Math.pow(bins[0] - N / 6, 2) / (N / 6) + + Math.pow(bins[1] - N / 2, 2) / (N / 2) + + Math.pow(bins[2] - N / 3, 2) / (N / 3) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('chance(undefined) distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 3.84 // quantile of chi-square dist. k=1, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(2) + for (let i = 0; i < N; ++i) { + let tmp = random.chance() + if (tmp === true) { + tmp = 1 + } else if (tmp === false) { + tmp = 0 + } else { + throw new Error(`Unexpected random.chance() result: ${tmp}`) + } + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 1) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('chance(2) distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 3.84 // quantile of chi-square dist. k=1, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(2) + for (let i = 0; i < N; ++i) { + let tmp = random.chance(2) + if (tmp === true) { + tmp = 1 + } else if (tmp === false) { + tmp = 0 + } else { + throw new Error(`Unexpected random.chance() result: ${tmp}`) + } + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 1) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('chance(3) distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 3.84 // quantile of chi-square dist. k=1, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(2) + for (let i = 0; i < N; ++i) { + let tmp = random.chance(3) + if (tmp === true) { + tmp = 0 + } else if (tmp === false) { + tmp = 1 + } else { + throw new Error(`Unexpected random.chance() result: ${tmp}`) + } + ++bins[tmp] + } + let xsq = + Math.pow(bins[0] - N / 3, 2) / (N / 3) + + Math.pow(bins[1] - (2 * N) / 3, 2) / ((2 * N) / 3) + /* + * XSQ = scipy.stats.chi2.isf(.05, 1) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('chance(1000) distribution', () => { + const N = 1e6 + const TRIES = 3 + const XSQ = 3.84 // quantile of chi-square dist. k=1, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(2) + for (let i = 0; i < N; ++i) { + let tmp = random.chance(1000) + if (tmp === true) { + tmp = 0 + } else if (tmp === false) { + tmp = 1 + } else { + throw new Error(`Unexpected random.chance() result: ${tmp}`) + } + ++bins[tmp] + } + let xsq = + Math.pow(bins[0] - N / 1000, 2) / (N / 1000) + + Math.pow(bins[1] - (999 * N) / 1000, 2) / ((999 * N) / 1000) + /* + * XSQ = scipy.stats.chi2.isf(.05, 1) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('choose() with equal distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.choose([[1, 0], [1, 1], [1, 2]]) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('choose() with unequal distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.choose([[1, 0], [2, 1], [1, 2]]) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = + Math.pow(bins[0] - N / 4, 2) / (N / 4) + + Math.pow(bins[1] - N / 2, 2) / (N / 2) + + Math.pow(bins[2] - N / 4, 2) / (N / 4) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('choose(flat) equal distribution with types not picked', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + const v1 = 1 + const v2 = [12] + const v3 = () => {} + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.choose([[1, v1], [1, v2], [1, v3]], true) + if (tmp === v1) { + tmp = 0 + } else if (tmp === v2) { + tmp = 1 + } else if (tmp === v3) { + tmp = 2 + } else { + throw new Error(`Unexpected random.choose() result: ${tmp}`) + } + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('weighted() with equal distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.item(random.weighted([{ w: 1, v: 0 }, { w: 1, v: 1 }, { w: 1, v: 2 }])) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('choose(flat) with unequal distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.choose([[1, 0], [2, 1], [1, 2]], true) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = + Math.pow(bins[0] - N / 4, 2) / (N / 4) + + Math.pow(bins[1] - N / 2, 2) / (N / 2) + + Math.pow(bins[2] - N / 4, 2) / (N / 4) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('weighted() with unequal distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.item(random.weighted([{ w: 1, v: 0 }, { w: 2, v: 1 }, { w: 1, v: 2 }])) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = + Math.pow(bins[0] - N / 4, 2) / (N / 4) + + Math.pow(bins[1] - N / 2, 2) / (N / 2) + + Math.pow(bins[2] - N / 4, 2) / (N / 4) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": " + tries) + return false + } + + expect(_test()).toBe(true) + }) + + test('weighted() equal distribution with types not picked', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + const v1 = 1 + const v2 = [12] + const v3 = () => {} + + random.init(Math.random() * 0x100000000) + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.item( + random.weighted([{ w: 1, v: v1 }, { w: 1, v: v2 }, { w: 1, v: v3 }]) + ) + if (tmp === v1) { + tmp = 0 + } else if (tmp === v2) { + tmp = 1 + } else if (tmp === v3) { + tmp = 2 + } else { + throw new Error(`Unexpected random.weighted() result: ${tmp}`) + } + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); + return false + } + expect(_test()).toBe(true) + }) + + test('choose() with unequal distribution and pick', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.choose([ + [1, 0], + [2, [1, 2]], + [ + 1, + () => 2 + ] + ]) + expect(tmp).toBeGreaterThanOrEqual(0) + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + let xsq = + Math.pow(bins[0] - N / 4, 2) / (N / 4) + + Math.pow(bins[1] - N / 4, 2) / (N / 4) + + Math.pow(bins[2] - N / 2, 2) / (N / 2) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); + return false + } + expect(_test()).toBe(true) + }) + + test('weighted() equal distribution with types not picked', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + const v1 = 1 + const v2 = [12] + const v3 = () => {} + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + for (let i = 0; i < N; ++i) { + let tmp = random.item( + random.weighted([{ w: 1, v: v1 }, { w: 1, v: v2 }, { w: 1, v: v3 }]) + ) + if (tmp === v1) { + tmp = 0 + } else if (tmp === v2) { + tmp = 1 + } else if (tmp === v3) { + tmp = 2 + } else { + throw new Error(`Unexpected random.weighted() result: ${tmp}`) + } + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); + return false + } + expect(_test()).toBe(true) + }) + + test('use() distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 3.84 // quantile of chi-square dist. k=1, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(2) + for (let i = 0; i < N; ++i) { + let rnd = Math.random() + let use = random.use(rnd) + if (use === rnd) { + use = 1 + } else if (use === '') { + use = 0 + } else { + throw new Error(`Unexpected random.use() result: ${use}`) + } + ++bins[use] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 1) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); + return false + } + expect(_test()).toBe(true) + }) + + test('shuffle() distribution', () => { + const N = 1e4 + const M = 10 + const TRIES = 3 + const XSQ = 123.23 // quantile of chi-square dist. k=M*M-1, p=.05 + // XXX: shouldn't k be M! ? + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(M * M) + for (let i = 0; i < N; ++i) { + let array = [] + for (let j = 0; j < M; ++j) array.push(j) + random.shuffle(array) + for (let j = 0; j < M; ++j) ++bins[j * M + array[j]] + } + let xsq = bins.reduce((a, v) => { + let e = N / M + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 99) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); + return false + } + + expect(_test()).toBe(true) + }) + + test('shuffled() distribution', () => { + const N = 1e4 + const M = 10 + const TRIES = 3 + const XSQ = 123.23 // quantile of chi-square dist. k=M*M-1, p=.05 + // XXX: shouldn't k be M! ? + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(M * M) + let array_ref = [] + for (let j = 0; j < M; ++j) { + array_ref.push(j) + } + for (let i = 0; i < N; ++i) { + let array = random.shuffled(array_ref) + for (let j = 0; j < M; ++j) { + ++bins[j * M + array[j]] + expect(array_ref[j]).toEqual(j) + } + let xsq = bins.reduce((a, v) => { + let e = N / M + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 99) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); + return false + } + + expect(_test()).toBe(true) + }) + + test.skip('subset() with equal distribution', () => { + /* + * This doesn't specify limit, so length distribution should be even, + * and selections should be even within each length. + */ + const N = 1e4 + const M = 3 + const TRIES = 3 + const B0_XSQ = 5.99 + const B1_XSQ = 15.51 + const B2_XSQ = 38.89 + const LEN_XSQ = 7.81 // quantile of chi-square dist. k=[2,8,26,3], p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let bin0_xsq, bin1_xsq, bin2_xsq, length_xsq + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = [new Uint32Array(3), new Uint32Array(9), new Uint32Array(27)] + let lengths = new Uint32Array(M + 1) + for (let i = 0; i < N; ++i) { + let tmp = random.subset([ + 0, + [1, 1], + () => 2 + ]) + expect(tmp.length).toBeLessThanOrEqual(M) + ++lengths[tmp.length] + if (tmp.length) { + ++bins[tmp.length - 1][tmp.reduce((a, v) => a * 3 + v, 0)] + } + } + bin0_xsq = bins[0].reduce((a, v) => { + let e = N / (M + 1) / Math.pow(M, 1) + return a + Math.pow(v - e, 2) / e + }, 0) + bin1_xsq = bins[1].reduce((a, v) => { + let e = N / (M + 1) / Math.pow(M, 2) + return a + Math.pow(v - e, 2) / e + }, 0) + bin2_xsq = bins[2].reduce((a, v) => { + let e = N / (M + 1) / Math.pow(M, 3) + return a + Math.pow(v - e, 2) / e + }, 0) + length_xsq = lengths.reduce((a, v) => { + let e = N / (M + 1) + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (bin0_xsq < B0_XSQ && bin1_xsq < B1_XSQ && bin2_xsq < B2_XSQ && length_xsq < LEN_XSQ) { + console.log(`Expected lengths x^2 to be < ${LEN_XSQ} got ${length_xsq} on attempt #${attempt + 1}`) + console.log(`Expected length=1 x^2 to be < ${B0_XSQ} got ${bin0_xsq} on attempt # ${attempt + 1}`) + console.log(`Expected length=2 x^2 to be < ${B1_XSQ} got ${bin1_xsq} on attempt #${attempt + 1}`) + console.log(`Expected length=3 x^2 to be < ${B2_XSQ} got ${bin2_xsq} on attempt #${attempt + 1}`) + return true + } + } + console.log(`Expected lengths x^2 to be < ${LEN_XSQ} got ${length_xsq}`) + console.log(`Expected length=1 x^2 to be < ${B0_XSQ} got ${bin0_xsq}`) + console.log(`Expected length=2 x^2 to be < ${B1_XSQ} got ${bin1_xsq}`) + console.log(`Expected length=3 x^2 to be < ${B2_XSQ} got ${bin2_xsq}`) + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq low enough"); + return false + } + + expect(_test()).toBe(true) + }) + + test.skip('subset(limit) with equal distribution', () => { + /* + * limit is specified, so length should always == limit, and selections should be even + */ + const N = 1e4 + const M = 3 + const TRIES = 3 + const B0_XSQ = 5.99 + const B1_XSQ = 15.51 + const B2_XSQ = 38.89 + const B3_XSQ = 101.88 // quantile of chi-square dist. k=[2,8,26,80], p=.05 + + let bin0_xsq, bin1_xsq, bin2_xsq, bin3_xsq + + random.init(Math.random() * 0x100000000) + + const _test = () => { + for (let attempt = 0; attempt < 100; ++attempt) { + expect(random.subset([1, 2, 3], 0).length).toBe(0) + } + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = [ + new Uint32Array(3), + new Uint32Array(9), + new Uint32Array(27), + new Uint32Array(81) + ] + for (let i = 0; i < N; ++i) { + let tmp = random.subset([0, 1, 2], 1) + expect(tmp.length).toBe(1) + + ++bins[0][tmp.reduce((a, v) => a * 3 + v, 0)] + tmp = random.subset([0, 1, 2], 2) + expect(tmp.length).toBe(2) + + ++bins[1][tmp.reduce((a, v) => a * 3 + v, 0)] + tmp = random.subset([0, 1, 2], 3) + expect(tmp.length).toBe(3) + + ++bins[2][tmp.reduce((a, v) => a * 3 + v, 0)] + tmp = random.subset([0, 1, 2], 4) + expect(tmp.length).toBe(4) + + ++bins[3][tmp.reduce((a, v) => a * 3 + v, 0)] + } + bin0_xsq = bins[0].reduce((a, v) => { + let e = N / Math.pow(M, 1) + return a + Math.pow(v - e, 2) / e + }, 0) + bin1_xsq = bins[1].reduce((a, v) => { + let e = N / Math.pow(M, 2) + return a + Math.pow(v - e, 2) / e + }, 0) + bin2_xsq = bins[2].reduce((a, v) => { + let e = N / Math.pow(M, 3) + return a + Math.pow(v - e, 2) / e + }, 0) + bin3_xsq = bins[3].reduce((a, v) => { + let e = N / Math.pow(M, 4) + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (bin0_xsq < B0_XSQ && bin1_xsq < B1_XSQ && bin2_xsq < B2_XSQ && bin3_xsq < B3_XSQ) { + // assert.ok(true, "Expected length=1 x^2 to be < " + B0_XSQ + ", got " + bin0_xsq + " on attempt #" + (attempt + 1)); + // assert.ok(true, "Expected length=2 x^2 to be < " + B1_XSQ + ", got " + bin1_xsq); + // assert.ok(true, "Expected length=3 x^2 to be < " + B2_XSQ + ", got " + bin2_xsq); + // assert.ok(true, "Expected length=4 x^2 to be < " + B3_XSQ + ", got " + bin3_xsq); + return true + } + } + console.log(`Expected length=1 x^2 to be < ${B0_XSQ} got ${bin0_xsq}`) + console.log(`Expected length=2 x^2 to be < ${B1_XSQ} got ${bin1_xsq}`) + console.log(`Expected length=3 x^2 to be < ${B2_XSQ} got ${bin2_xsq}`) + console.log(`Expected length=4 x^2 to be < ${B3_XSQ} got ${bin3_xsq}`) + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq low enough"); + return false + } + expect(_test()).toBe(true) + }) + + test('pop() distribution', () => { + const N = 1e4 + const TRIES = 3 + const XSQ = 5.99 // quantile of chi-square dist. k=2, p=.05 + + random.init(Math.random() * 0x100000000) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(3) + const orig = [99, 100, 101] + for (let i = 0; i < N; ++i) { + let arr = orig.slice() + let tmp = random.pop(arr) - 99 + expect(tmp).toBeGreaterThanOrEqual(0) + expect(tmp).toBeLessThan(bins.length) + expect(arr.length).toBe(2) + expect(arr.reduce((a, v) => { return a + v }, tmp)).toBe(201) + ++bins[tmp] + } + let xsq = bins.reduce((a, v) => { + let e = N / bins.length + return a + Math.pow(v - e, 2) / e + }, 0) + /* + * XSQ = scipy.stats.chi2.isf(.05, 2) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); + return false + } + expect(_test()).toBe(true) + }) + + test('ludOneTo() distribution', () => { + const N = 1e5 + const TRIES = 3 + const XSQ = 123.22 // quantile of chi-square dist. k=99, p=.05 + let dist = new Uint32Array(100) + + random.init(Math.random() * 0x100000000) + + /* Build the ideal distribution for comparison + * I thought this would be the PDF of the log-normal distribution, but I couldn't get mu & sigma figured out? */ + for (let i = 0; i < 100 * dist.length; ++i) { + dist[Math.floor(Math.exp((i / (100 * dist.length)) * Math.log(dist.length)))] += + N / (dist.length * 100) + } + expect(dist[0]).toEqual(0) + + const _test = () => { + let tries = [] + for (let attempt = 0; attempt < TRIES; ++attempt) { + let bins = new Uint32Array(dist.length) + let xsq = 0 + for (let i = 0; i < N; ++i) { + let tmp = random.ludOneTo(bins.length) >>> 0 + expect(tmp).toBeLessThan(bins.length) + ++bins[tmp] + } + expect(bins[0]).toEqual(0) + + for (let i = 1; i < bins.length; ++i) { + xsq += Math.pow(bins[i] - dist[i], 2) / dist[i] + } + /* + * XSQ = scipy.stats.chi2.isf(.05, 99) + * if xsq > XSQ, the result is biased at 95% significance + */ + if (xsq < XSQ) { + console.log(`Expected x^2 to be < ${XSQ}, got ${xsq} on attempt #${attempt + 1}`) + return true + } + tries.push(xsq) + } + // assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); + return false + } + expect(_test()).toBe(true) + }) +}) diff --git a/lib/random/random.js b/lib/random/random.js index f75c243..2844fdf 100644 --- a/lib/random/random.js +++ b/lib/random/random.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const MersenneTwister = require('./mersennetwister') -const {logger} = require('../logging') +const logger = require('../logging') class random { /** diff --git a/lib/utils/objects.js b/lib/utils/objects.js index ee7ef0e..cbe1388 100644 --- a/lib/utils/objects.js +++ b/lib/utils/objects.js @@ -4,7 +4,7 @@ const random = require('../random') const utils = require('../utils') -const {logger} = require('../logging') +const logger = require('../logging') var o = null // eslint-disable-line no-unused-vars diff --git a/package.json b/package.json index f4f8a99..a4e3f0b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@mozillasecurity/octo", - "version": "1.0.14", - "description": "", + "version": "1.0.15", + "description": "A unified shared library which aids in building fuzzers for browsers or as complement for an existing fuzzing framework.", "keywords": [ "fuzzing", "browser", @@ -9,7 +9,7 @@ "node", "library" ], - "homepage": "https://github.com/mozillasecurity/octo/tree/es6", + "homepage": "https://github.com/mozillasecurity/octo", "repository": { "type": "git", "url": "https://github.com/mozillasecurity/octo.git" @@ -21,18 +21,20 @@ "author": "Christoph Diehl ", "license": "MPL-2.0", "scripts": { - "test": "grunt test --verbose", - "test:lint": "cross-env NODE_ENV=test standard --verbose", - "test:lint:fix": "cross-env NODE_ENV=test standard --fix --verbose", + "test": "jest --silent", + "coverage": "cross-env NODE_ENV=test jest --silent --coverage --collectCoverageFrom=lib/**/*.js", + "coveralls": "yarn coverage && cat ./coverage/lcov.info | coveralls", + "lint": "cross-env NODE_ENV=test standard --verbose", + "lint:fix": "cross-env NODE_ENV=test standard --fix --verbose", + "docs": "esdoc", "build": "webpack -p", "watch": "webpack --watch", - "precommit": "npm run test:lint", - "postinstall": "npm run build", + "precommit": "yarn lint", + "postinstall": "yarn build", "release": "np" }, "standard": { "ignore": [ - "tests/**", "dist/" ], "envs": { @@ -41,26 +43,43 @@ "es6": true } }, + "jest": { + "verbose": true + }, + "esdoc": { + "source": "./lib", + "destination": "./docs", + "plugins": [ + { + "name": "esdoc-standard-plugin", + "option": { + "lint": { + "enable": true + }, + "coverage": { + "enable": true + } + } + }, + { + "name": "esdoc-node" + } + ] + }, "devDependencies": { + "coveralls": "^3.0.2", "cross-env": "^5.1.4", - "grunt": "*", - "grunt-karma": "*", - "grunt-karma-coveralls": "*", - "grunt-standard": "*", + "esdoc": "^1.1.0", + "esdoc-node": "^1.0.3", + "esdoc-standard-plugin": "^1.0.0", "husky": "^0.14.3", - "karma": "*", - "karma-chrome-launcher": "*", - "karma-coverage": "*", - "karma-firefox-launcher": "^1.1.0", - "karma-qunit": "^2.0.1", + "jest": "^23.5.0", "np": "^3.0.4", - "qunit": "^2.5.1", - "qunitjs": "^2.4.1", - "standard": "^11.0.1" + "standard": "^11.0.1", + "webpack": "^4.1.1", + "webpack-cli": "^3.1.0" }, "dependencies": { - "jsesc": "^2.5.1", - "webpack": "^4.1.1", - "webpack-cli": "^2.0.12" + "jsesc": "^2.5.1" } } diff --git a/tests/index.html b/tests/index.html deleted file mode 100644 index 3cb6f54..0000000 --- a/tests/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - Octo Unit Tests - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/random/mersennetwister.js b/tests/random/mersennetwister.js deleted file mode 100644 index 9f55a6b..0000000 --- a/tests/random/mersennetwister.js +++ /dev/null @@ -1,58 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -QUnit.test("MersenneTwister test uniform distribution", function(assert) { - const N = Math.pow(2, 18), TRIES = 10, XSQ = 293.25; // quantile of chi-square dist. k=255, p=.05 - let mt = new MersenneTwister(); - mt.seed(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let data = new Uint32Array(N), sh; - for (let i = 0; i < data.length; ++i) { - data[i] = mt.int32(); - } - for (sh = 0; sh <= 24; ++sh) { - let bins = new Uint32Array(256); - for (let b of data) { - ++bins[(b >>> sh) & 0xFF]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 255) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq >= XSQ) - break; - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - } - if (sh == 25) { - return; - } - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ); -}); - -QUnit.test("MersenneTwister test float distribution", function(assert) { - const N = Math.pow(2, 18), TRIES = 3, XSQ = 564.7; // quantile of chi-square dist. k=511, p=.05 - let tries = [], mt = new MersenneTwister(); - mt.seed(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(512); - for (let i = 0; i < N; ++i) { - let tmp = (mt.real2() * bins.length) >>> 0; - if (tmp >= bins.length) throw "random.float() >= 1.0"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 511) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); diff --git a/tests/random/random.js b/tests/random/random.js deleted file mode 100644 index bc0a4eb..0000000 --- a/tests/random/random.js +++ /dev/null @@ -1,892 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -QUnit.test("random.init() is required", function(assert) { - assert.throws(random.number, /undefined/, "twister should be uninitialized before random.init()"); - random.init(1); - random.number(); -}); - -QUnit.test("random.number() corner cases", function(assert) { - random.init(Math.random() * 0x100000000); - let sum = 0; - for (let i = 0; i < 100; ++i) - sum += random.number(0); - assert.equal(sum, 0); - for (let i = 0; i < 100; ++i) - sum += random.number(1); - assert.equal(sum, 0); - let bins = new Uint32Array(2); - for (let i = 0; i < 100; ++i) - ++bins[random.number(2)]; - assert.equal(bins[0] + bins[1], 100); - assert.ok(bins[0] > 20); - sum = 0; - for (let i = 0; i < 15; ++i) - sum |= random.number(); - assert.equal(sum>>>0, 0xFFFFFFFF); -}); - -QUnit.test("random.number() uniform distribution", function(assert) { - const N = Math.pow(2, 17), TRIES = 3, XSQ = 564.7; // quantile of chi-square dist. k=511, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(512); - for (let i = 0; i < N; ++i) { - let tmp = random.number(bins.length); - if (tmp >= bins.length) throw "random.number() >= limit"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 511) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.float() uniform distribution", function(assert) { - const N = Math.pow(2, 17), TRIES = 3, XSQ = 564.7; // quantile of chi-square dist. k=511, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(512); - for (let i = 0; i < N; ++i) { - let tmp = (random.float() * bins.length) >>> 0; - if (tmp >= bins.length) throw "random.float() >= 1.0"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 511) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.range() uniform distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 66.34; // quantile of chi-square dist. k=49, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(50); - for (let i = 0; i < N; ++i) { - let tmp = random.range(0, bins.length - 1); - if (tmp >= bins.length) throw "random.range() > upper bound"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 49) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.range() uniform distribution with offset", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 66.34; // quantile of chi-square dist. k=49, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(50); - for (let i = 0; i < N; ++i) { - let tmp = random.range(10, 10 + bins.length - 1) - 10; - if (tmp < 0) throw "random.range() < lower bound"; - if (tmp >= bins.length) throw "random.range() > upper bound"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 49) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.range() PRNG reproducibility", function(assert) { - let seed, result1, result2; - seed = Math.random() * 0x100000000; - for (let t = 0; t < 50; ++t) { - random.init(seed); - result1 = random.range(1, 20); - for (let i = 0; i < 5; ++i) { - random.init(seed); - result2 = random.range(1, 20); - assert.equal(result1, result2, "both results are the same") - } - seed = random.number(); - } -}); - -QUnit.test("random.ludOneTo() distribution", function(assert) { - const N = 1e5, TRIES = 3, XSQ = 123.22; // quantile of chi-square dist. k=99, p=.05 - let dist = new Uint32Array(100), tries = []; - random.init(Math.random() * 0x100000000); - /* build the ideal distribution for comparison - * I thought this would be the PDF of the log-normal distribution, but I couldn't get mu & sigma figured out? */ - for (let i = 0; i < (100 * dist.length); ++i) { - dist[Math.floor(Math.exp(i / (100*dist.length) * Math.log(dist.length)))] += N / (dist.length * 100); - } - assert.equal(dist[0], 0); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(dist.length), xsq = 0; - for (let i = 0; i < N; ++i) { - let tmp = random.ludOneTo(bins.length)>>>0; - if (tmp >= bins.length) throw "random.ludOneTo() > upper bound"; // this could happen.. - ++bins[tmp]; - } - assert.equal(bins[0], 0); - for (let i = 1; i < bins.length; ++i) { - xsq += Math.pow(bins[i] - dist[i], 2) / dist[i]; - } - /* - * XSQ = scipy.stats.chi2.isf(.05, 99) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.item() exception cases", function(assert) { - assert.throws(random.item, /received an invalid object/); - assert.throws(function(){ return random.item(1); }, /received an invalid object/); - assert.throws(function(){ return random.item("1"); }, /received an invalid object/); - assert.throws(function(){ return random.item({}); }, /received an invalid object/); -}); - -QUnit.test("random.item() distribution with list", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.item([99, 100, 101]) - 99; - if (tmp < 0) throw "random.item() < lower bound"; - if (tmp >= bins.length) throw "random.item() > upper bound"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.key() distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.key({99: 0, 100: 0, 101: 0}) - 99; - if (tmp < 0) throw "random.key() < lower bound"; - if (tmp >= bins.length) throw "random.key() > upper bound"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.bool() distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 3.84; // quantile of chi-square dist. k=1, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(2); - for (let i = 0; i < N; ++i) { - let tmp = random.bool(); - if (tmp === true) - tmp = 1; - else if (tmp === false) - tmp = 0; - else - assert.ok(false, "unexpected random.bool() result: " + tmp); - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 1) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.pick() cases", function(assert) { - random.init(Math.random() * 0x100000000); - for (let i = 0; i < 100; ++i) { - let tmp = Math.random(); - assert.equal(tmp, random.pick(tmp)); - } - for (let i = 0; i < 100; ++i) { - let tmp = (Math.random() * 100) >>> 0; - assert.equal(tmp, random.pick(tmp)); - } - for (let i = 0; i < 100; ++i) { - let tmp = Math.random() + ""; - assert.equal(tmp, random.pick(tmp)); - } - for (let i = 0; i < 100; ++i) { - let tmp = Math.random(); - assert.equal(tmp, random.pick([tmp])); - } - for (let i = 0; i < 100; ++i) { - let tmp = Math.random(); - assert.equal(tmp, random.pick(function(){ return tmp; })); - } - for (let i = 0; i < 100; ++i) { - let tmp = Math.random(); - assert.equal(tmp, random.pick(function(){ return [tmp]; })); - } -}); - -QUnit.test("random.pick() with equal distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.pick([0, [1, 1], function(){ return 2; }]); - if (tmp < 0) throw "random.pick() < lower bound"; - if (tmp >= bins.length) throw "random.pick() > upper bound"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.pick() with unequal distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.pick([[0, 1], [1], function(){ return [2]; }]); - if (tmp < 0) throw "random.pick() < lower bound"; - if (tmp >= bins.length) throw "random.pick() > upper bound"; - ++bins[tmp]; - } - let xsq = Math.pow(bins[0] - N / 6, 2) / (N / 6) + Math.pow(bins[1] - N / 2, 2) / (N / 2) + Math.pow(bins[2] - N / 3, 2) / (N / 3); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.chance(2) distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 3.84; // quantile of chi-square dist. k=1, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(2); - for (let i = 0; i < N; ++i) { - let tmp = random.chance(2); - if (tmp === true) - tmp = 1; - else if (tmp === false) - tmp = 0; - else - assert.ok(false, "unexpected random.chance() result: " + tmp); - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 1) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.chance(undefined) distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 3.84; // quantile of chi-square dist. k=1, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(2); - for (let i = 0; i < N; ++i) { - let tmp = random.chance(); - if (tmp === true) - tmp = 1; - else if (tmp === false) - tmp = 0; - else - assert.ok(false, "unexpected random.chance() result: " + tmp); - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 1) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.chance(3) distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 3.84; // quantile of chi-square dist. k=1, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(2); - for (let i = 0; i < N; ++i) { - let tmp = random.chance(3); - if (tmp === true) - tmp = 0; - else if (tmp === false) - tmp = 1; - else - assert.ok(false, "unexpected random.chance() result: " + tmp); - ++bins[tmp]; - } - let xsq = Math.pow(bins[0] - (N / 3), 2) / (N / 3) + Math.pow(bins[1] - (2 * N / 3), 2) / (2 * N / 3); - /* - * XSQ = scipy.stats.chi2.isf(.05, 1) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.chance(1000) distribution", function(assert) { - const N = 1e6, TRIES = 3, XSQ = 3.84; // quantile of chi-square dist. k=1, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(2); - for (let i = 0; i < N; ++i) { - let tmp = random.chance(1000); - if (tmp === true) - tmp = 0; - else if (tmp === false) - tmp = 1; - else - assert.ok(false, "unexpected random.chance() result: " + tmp); - ++bins[tmp]; - } - let xsq = Math.pow(bins[0] - (N / 1000), 2) / (N / 1000) + Math.pow(bins[1] - (999 * N / 1000), 2) / (999 * N / 1000); - /* - * XSQ = scipy.stats.chi2.isf(.05, 1) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.choose() with equal distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.choose([[1, 0], [1, 1], [1, 2]]); - if (tmp >= bins.length) throw "random.choose() > upper bound"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.choose() with unequal distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.choose([[1, 0], [2, 1], [1, 2]]); - if (tmp >= bins.length) throw "random.choose() > upper bound"; - ++bins[tmp]; - } - let xsq = Math.pow(bins[0] - N / 4, 2) / (N / 4) + Math.pow(bins[1] - N / 2, 2) / (N / 2) + Math.pow(bins[2] - N / 4, 2) / (N / 4); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.choose() with unequal distribution and pick", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.choose([[1, 0], [2, [1, 2]], [1, function(){ return 2; }]]); - if (tmp >= bins.length) throw "random.choose() > upper bound"; - ++bins[tmp]; - } - let xsq = Math.pow(bins[0] - N / 4, 2) / (N / 4) + Math.pow(bins[1] - N / 4, 2) / (N / 4) + Math.pow(bins[2] - N / 2, 2) / (N / 2); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.choose(flat) with unequal distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.choose([[1, 0], [2, 1], [1, 2]], true); - if (tmp >= bins.length) throw "random.choose() > upper bound"; - ++bins[tmp]; - } - let xsq = Math.pow(bins[0] - N / 4, 2) / (N / 4) + Math.pow(bins[1] - N / 2, 2) / (N / 2) + Math.pow(bins[2] - N / 4, 2) / (N / 4); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.choose(flat) equal distribution with types not picked", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - const v1 = 1, v2 = [12], v3 = function(){}; - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.choose([[1, v1], [1, v2], [1, v3]], true); - if (tmp === v1) - tmp = 0; - else if (tmp === v2) - tmp = 1; - else if (tmp === v3) - tmp = 2; - else - assert.ok(false, "unexpected random.choose() result: " + tmp); - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.weighted() with equal distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.item(random.weighted([{w: 1, v: 0}, {w: 1, v: 1}, {w: 1, v: 2}])); - if (tmp >= bins.length) throw "random.weighted() > upper bound"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.weighted() with unequal distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.item(random.weighted([{w: 1, v: 0}, {w: 2, v: 1}, {w: 1, v: 2}])); - if (tmp >= bins.length) throw "random.weighted() > upper bound"; - ++bins[tmp]; - } - let xsq = Math.pow(bins[0] - N / 4, 2) / (N / 4) + Math.pow(bins[1] - N / 2, 2) / (N / 2) + Math.pow(bins[2] - N / 4, 2) / (N / 4); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.weighted() equal distribution with types not picked", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - const v1 = 1, v2 = [12], v3 = function(){}; - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - for (let i = 0; i < N; ++i) { - let tmp = random.item(random.weighted([{w: 1, v: v1}, {w: 1, v: v2}, {w: 1, v: v3}])); - if (tmp === v1) - tmp = 0; - else if (tmp === v2) - tmp = 1; - else if (tmp === v3) - tmp = 2; - else - assert.ok(false, "unexpected random.weighted() result: " + tmp); - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.use() distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 3.84; // quantile of chi-square dist. k=1, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(2); - for (let i = 0; i < N; ++i) { - let rnd = Math.random(), use = random.use(rnd); - if (use === rnd) - use = 1; - else if (use === "") - use = 0; - else - assert.ok(false, "unexpected random.use() result: " + use); - ++bins[use]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 1) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.shuffle() distribution", function(assert) { - const N = 1e4, M = 10, TRIES = 3, XSQ = 123.23; // quantile of chi-square dist. k=M*M-1, p=.05 - // XXX: shouldn't k be M! ? - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(M * M); - for (let i = 0; i < N; ++i) { - let array = []; - for (let j = 0; j < M; ++j) - array.push(j); - random.shuffle(array); - for (let j = 0; j < M; ++j) - ++bins[j * M + array[j]]; - } - let xsq = bins.reduce(function(a, v){ let e = N / M; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 99) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.shuffled() distribution", function(assert) { - const N = 1e4, M = 10, TRIES = 3, XSQ = 123.23; // quantile of chi-square dist. k=M*M-1, p=.05 - // XXX: shouldn't k be M! ? - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(M * M); - let array_ref = []; - for (let j = 0; j < M; ++j) - array_ref.push(j); - for (let i = 0; i < N; ++i) { - let array = random.shuffled(array_ref); - for (let j = 0; j < M; ++j) { - ++bins[j * M + array[j]]; - if (array_ref[j] !== j) - throw "array modified"; - } - } - let xsq = bins.reduce(function(a, v){ let e = N / M; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 99) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -}); - -QUnit.test("random.subset() with equal distribution", function(assert) { - /* - * this doesn't specify limit, so length distribution should be even, and selections should be even within each length - */ - const N = 1e4, M = 3, TRIES = 3, B0_XSQ = 5.99, B1_XSQ = 15.51, B2_XSQ = 38.89, LEN_XSQ = 7.81; // quantile of chi-square dist. k=[2,8,26,3], p=.05 - let bin0_xsq, bin1_xsq, bin2_xsq, length_xsq; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = [new Uint32Array(3), new Uint32Array(9), new Uint32Array(27)], lengths = new Uint32Array(M+1); - for (let i = 0; i < N; ++i) { - let tmp = random.subset([0, [1, 1], function(){ return 2; }]); - if (tmp.length > M) throw "random.subset() result length > input"; - ++lengths[tmp.length]; - if (tmp.length) - ++bins[tmp.length-1][tmp.reduce(function(a, v){ return a * 3 + v; }, 0)]; - } - bin0_xsq = bins[0].reduce(function(a, v){ let e = N / (M + 1) / Math.pow(M, 1); return a + Math.pow(v - e, 2) / e; }, 0); - bin1_xsq = bins[1].reduce(function(a, v){ let e = N / (M + 1) / Math.pow(M, 2); return a + Math.pow(v - e, 2) / e; }, 0); - bin2_xsq = bins[2].reduce(function(a, v){ let e = N / (M + 1) / Math.pow(M, 3); return a + Math.pow(v - e, 2) / e; }, 0); - length_xsq = lengths.reduce(function(a, v){ let e = N / (M + 1); return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (bin0_xsq < B0_XSQ && bin1_xsq < B1_XSQ && bin2_xsq < B2_XSQ && length_xsq < LEN_XSQ) { - assert.ok(true, "Expected lengths x^2 to be < " + LEN_XSQ + ", got " + length_xsq + " on attempt #" + (attempt + 1)); - assert.ok(true, "Expected length=1 x^2 to be < " + B0_XSQ + ", got " + bin0_xsq + " on attempt #" + (attempt + 1)); - assert.ok(true, "Expected length=2 x^2 to be < " + B1_XSQ + ", got " + bin1_xsq + " on attempt #" + (attempt + 1)); - assert.ok(true, "Expected length=3 x^2 to be < " + B2_XSQ + ", got " + bin2_xsq + " on attempt #" + (attempt + 1)); - return; - } - } - console.log("Expected lengths x^2 to be < " + LEN_XSQ + ", got " + length_xsq); - console.log("Expected length=1 x^2 to be < " + B0_XSQ + ", got " + bin0_xsq); - console.log("Expected length=2 x^2 to be < " + B1_XSQ + ", got " + bin1_xsq); - console.log("Expected length=3 x^2 to be < " + B2_XSQ + ", got " + bin2_xsq); - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq low enough"); -}); - -QUnit.test("random.subset(limit) with equal distribution", function(assert) { - /* - * limit is specified, so length should always == limit, and selections should be even - */ - const N = 1e4, M = 3, TRIES = 3, B0_XSQ = 5.99, B1_XSQ = 15.51, B2_XSQ = 38.89, B3_XSQ = 101.88; // quantile of chi-square dist. k=[2,8,26,80], p=.05 - let bin0_xsq, bin1_xsq, bin2_xsq, bin3_xsq; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < 100; ++attempt) { - if (random.subset([1,2,3], 0).length !== 0) throw "random.subset(..., 0) returned non-empty array"; - } - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = [new Uint32Array(3), new Uint32Array(9), new Uint32Array(27), new Uint32Array(81)]; - for (let i = 0; i < N; ++i) { - let tmp = random.subset([0, 1, 2], 1); - if (tmp.length !== 1) throw "random.subset() result length != limit"; - ++bins[0][tmp.reduce(function(a, v){ return a * 3 + v; }, 0)]; - tmp = random.subset([0, 1, 2], 2); - if (tmp.length !== 2) throw "random.subset() result length != limit"; - ++bins[1][tmp.reduce(function(a, v){ return a * 3 + v; }, 0)]; - tmp = random.subset([0, 1, 2], 3); - if (tmp.length !== 3) throw "random.subset() result length != limit"; - ++bins[2][tmp.reduce(function(a, v){ return a * 3 + v; }, 0)]; - tmp = random.subset([0, 1, 2], 4); - if (tmp.length !== 4) throw "random.subset() result length != limit"; - ++bins[3][tmp.reduce(function(a, v){ return a * 3 + v; }, 0)]; - } - bin0_xsq = bins[0].reduce(function(a, v){ let e = N / Math.pow(M, 1); return a + Math.pow(v - e, 2) / e; }, 0); - bin1_xsq = bins[1].reduce(function(a, v){ let e = N / Math.pow(M, 2); return a + Math.pow(v - e, 2) / e; }, 0); - bin2_xsq = bins[2].reduce(function(a, v){ let e = N / Math.pow(M, 3); return a + Math.pow(v - e, 2) / e; }, 0); - bin3_xsq = bins[3].reduce(function(a, v){ let e = N / Math.pow(M, 4); return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (bin0_xsq < B0_XSQ && bin1_xsq < B1_XSQ && bin2_xsq < B2_XSQ && bin3_xsq < B3_XSQ) { - assert.ok(true, "Expected length=1 x^2 to be < " + B0_XSQ + ", got " + bin0_xsq + " on attempt #" + (attempt + 1)); - assert.ok(true, "Expected length=2 x^2 to be < " + B1_XSQ + ", got " + bin1_xsq); - assert.ok(true, "Expected length=3 x^2 to be < " + B2_XSQ + ", got " + bin2_xsq); - assert.ok(true, "Expected length=4 x^2 to be < " + B3_XSQ + ", got " + bin3_xsq); - return; - } - } - console.log("Expected length=1 x^2 to be < " + B0_XSQ + ", got " + bin0_xsq); - console.log("Expected length=2 x^2 to be < " + B1_XSQ + ", got " + bin1_xsq); - console.log("Expected length=3 x^2 to be < " + B2_XSQ + ", got " + bin2_xsq); - console.log("Expected length=4 x^2 to be < " + B3_XSQ + ", got " + bin3_xsq); - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq low enough"); -}); - -QUnit.test("random.pop() distribution", function(assert) { - const N = 1e4, TRIES = 3, XSQ = 5.99; // quantile of chi-square dist. k=2, p=.05 - let tries = []; - random.init(Math.random() * 0x100000000); - for (let attempt = 0; attempt < TRIES; ++attempt) { - let bins = new Uint32Array(3); - const orig = [99, 100, 101]; - for (let i = 0; i < N; ++i) { - let arr = orig.slice(), tmp = random.pop(arr) - 99; - if (tmp < 0) throw "random.pop() < lower bound"; - if (tmp >= bins.length) throw "random.pop() > upper bound"; - if (arr.length !== 2) throw "random.pop() did not pop"; - if (arr.reduce(function(a, v){ return a + v; }, tmp) !== 201) throw "random.pop() sum error"; - ++bins[tmp]; - } - let xsq = bins.reduce(function(a, v){ let e = N / bins.length; return a + Math.pow(v - e, 2) / e; }, 0); - /* - * XSQ = scipy.stats.chi2.isf(.05, 2) - * if xsq > XSQ, the result is biased at 95% significance - */ - if (xsq < XSQ) { - assert.ok(true, "Expected x^2 to be < " + XSQ + ", got " + xsq + " on attempt #" + (attempt + 1)); - return; - } - tries.push(xsq); - } - assert.ok(false, "Failed in " + TRIES + " attempts to get xsq lower than " + XSQ + ": "+ tries); -});