Skip to content

Commit

Permalink
Merge 1fa2e92 into 032355e
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz committed Mar 29, 2019
2 parents 032355e + 1fa2e92 commit 3e0a9ab
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 0 deletions.
1 change: 1 addition & 0 deletions documentation/Arbitraries.md
Expand Up @@ -76,6 +76,7 @@ More specific strings:
- `fc.constant<T>(value: T): Arbitrary<T>` constant arbitrary only able to produce `value: T`
- `fc.constantFrom<T>(...values: T[]): Arbitrary<T>` randomly chooses among the values provided. It considers the first value as the default value so that in case of failure it will shrink to it. It expects a minimum of one value and throws whether it receives no value as parameters. It can easily be used on arrays with `fc.constantFrom(...myArray)` (or `fc.constantFrom.apply(null, myArray)` for older versions of TypeScript/JavaScript)
- `fc.clonedConstant<T>(value: T): Arbitrary<T>` constant arbitrary only able to produce `value: T`. If it exists, it called its `[fc.cloneMethod]` at each call to generate
- `fc.mapToConstant<T>(...entries: { num: number; build: (idInGroup: number) => T }[]): Arbitrary<T>` generates non-contiguous ranges of values by mapping integer values to constant
- `fc.oneof<T>(...arbs: Arbitrary<T>[]): Arbitrary<T>` randomly chooses an arbitrary at each new generation. Should be provided with at least one arbitrary. All arbitraries are equally probable and shrink is still working for the selected arbitrary. `fc.oneof` is able to shrink inside the failing arbitrary but not accross arbitraries (contrary to `fc.constantFrom` when dealing with constant arbitraries)
- `fc.option<T>(arb: Arbitrary<T>): Arbitrary<T | null>` or `fc.option<T>(arb: Arbitrary<T>, freq: number): Arbitrary<T | null>` arbitrary able to nullify its generated value. When provided a custom `freq` value it changes the frequency of `null` values so that they occur one time over `freq` tries (eg.: `freq=5` means that 20% of generated values will be `null` and 80% would be produced through `arb`). By default: `freq=5`
- `fc.subarray<T>(originalArray: T[]): Arbitrary<T[]>`, or `fc.subarray<T>(originalArray: T[], minLength: number, maxLength: number): Arbitrary<T[]>` subarray of `originalArray`. Values inside the subarray are ordered the same way they are in `originalArray`. By setting the parameters `minLength` and/or `maxLength`, the user can change the minimal (resp. maximal) size allowed for the generated subarray. By default: `minLength=0` and `maxLength=originalArray.length`
Expand Down
42 changes: 42 additions & 0 deletions src/check/arbitrary/MapToConstantArbitrary.ts
@@ -0,0 +1,42 @@
import { Arbitrary } from './definition/Arbitrary';
import { nat } from './IntegerArbitrary';

/** @hidden */
function computeNumChoices<T>(options: { num: number; build: (idInGroup: number) => T }[]): number {
if (options.length === 0) throw new Error(`fc.mapToConstant expects at least one option`);
let numChoices = 0;
for (let idx = 0; idx !== options.length; ++idx) {
if (options[idx].num < 0)
throw new Error(`fc.mapToConstant expects all options to have a number of entries greater or equal to zero`);
numChoices += options[idx].num;
}
if (numChoices === 0) throw new Error(`fc.mapToConstant expects at least one choice among options`);
return numChoices;
}

/**
* Generate non-contiguous ranges of values
* by mapping integer values to constant
*
* @param options Builders to be called to generate the values
*
* @example
* ```
* // generate alphanumeric values (a-z0-9)
* mapToConstant(
* { num: 26, build: v => String.fromCharCode(v + 0x61) },
* { num: 10, build: v => String.fromCharCode(v + 0x30) },
* )
* ```
*/
export function mapToConstant<T>(...entries: { num: number; build: (idInGroup: number) => T }[]): Arbitrary<T> {
const numChoices = computeNumChoices(entries);
return nat(numChoices - 1).map(choice => {
let idx = -1;
let numSkips = 0;
while (choice >= numSkips) {
numSkips += entries[++idx].num;
}
return entries[idx].build(choice - numSkips + entries[idx].num);
});
}
2 changes: 2 additions & 0 deletions src/fast-check-default.ts
Expand Up @@ -22,6 +22,7 @@ import { compareBooleanFunc, compareFunc, func } from './check/arbitrary/Functio
import { integer, maxSafeInteger, maxSafeNat, nat } from './check/arbitrary/IntegerArbitrary';
import { ipV4, ipV6 } from './check/arbitrary/IpArbitrary';
import { lorem } from './check/arbitrary/LoremArbitrary';
import { mapToConstant } from './check/arbitrary/MapToConstantArbitrary';
import {
anything,
json,
Expand Down Expand Up @@ -115,6 +116,7 @@ export {
constant,
constantFrom,
clonedConstant,
mapToConstant,
option,
oneof,
frequency,
Expand Down
16 changes: 16 additions & 0 deletions test/legacy/main.js
Expand Up @@ -70,3 +70,19 @@ testArbitrary(fc.string());
testArbitrary(fc.fullUnicodeString());
testArbitrary(fc.lorem());
testArbitrary(fc.frequency({ weight: 1, arbitrary: fc.nat() }, { weight: 2, arbitrary: fc.double() }));
testArbitrary(
fc.mapToConstant(
{
num: 26,
build: function(v) {
return String.fromCharCode(v + 0x61);
}
},
{
num: 10,
build: function(v) {
return String.fromCharCode(v + 0x30);
}
}
)
);
57 changes: 57 additions & 0 deletions test/unit/check/arbitrary/MapToConstantArbitrary.spec.ts
@@ -0,0 +1,57 @@
import * as prand from 'pure-rand';
import * as fc from '../../../../lib/fast-check';

import { mapToConstant } from '../../../../src/check/arbitrary/MapToConstantArbitrary';
import { Random } from '../../../../src/random/generator/Random';
import { minMax } from './generic/GenericArbitraryHelper';
import { nat } from '../../../../src/check/arbitrary/IntegerArbitrary';

describe('MapToConstantArbitrary', () => {
describe('mapToConstant', () => {
it('Single non-zero builder should be equivalent to nat and map', () =>
fc.assert(
fc.property(
fc.integer(),
fc.integer(1, Number.MAX_SAFE_INTEGER),
minMax(fc.nat(100)),
(seed, numValues, others) => {
const { min: pos, max: numBuilders } = others;
fc.pre(pos < numBuilders);

const entries: { num: number; build: (v: number) => string }[] = [];
for (let idx = 0; idx !== numBuilders; ++idx) {
entries.push({ num: idx === pos ? numValues : 0, build: (v: number) => `Builder #${idx}: ${v}` });
}

const refArb = nat(numValues - 1).map(v => entries[pos].build(v));
const arb = mapToConstant(...entries);

const refMrng = new Random(prand.xorshift128plus(seed));
const mrng = new Random(prand.xorshift128plus(seed));

expect(arb.generate(mrng).value).toEqual(refArb.generate(refMrng).value);
}
)
));
it('Should call the right builder', () =>
fc.assert(
fc.property(fc.integer(), fc.array(fc.nat(100)), (seed, builderSizes) => {
const totalSize = builderSizes.reduce((a, b) => a + b, 0);
fc.pre(totalSize > 0);

const entries: { num: number; build: (v: number) => any }[] = [];
for (let idx = 0, currentSize = 0; idx !== builderSizes.length; currentSize += builderSizes[idx], ++idx) {
entries.push({ num: builderSizes[idx], build: v => v + currentSize });
}

const refArb = nat(totalSize - 1);
const arb = mapToConstant(...entries);

const refMrng = new Random(prand.xorshift128plus(seed));
const mrng = new Random(prand.xorshift128plus(seed));

expect(arb.generate(mrng).value).toEqual(refArb.generate(refMrng).value);
})
));
});
});

0 comments on commit 3e0a9ab

Please sign in to comment.