Skip to content

Commit

Permalink
Better balance between values produced by fc.anything()
Browse files Browse the repository at this point in the history
Fixes #324
  • Loading branch information
dubzzz committed Mar 1, 2019
1 parent 77d8fee commit 1272e5d
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 45 deletions.
90 changes: 69 additions & 21 deletions src/check/arbitrary/ObjectArbitrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { dictionary } from './DictionaryArbitrary';
import { double } from './FloatingPointArbitrary';
import { integer } from './IntegerArbitrary';
import { oneof } from './OneOfArbitrary';
import { set } from './SetArbitrary';
import { string, unicodeString } from './StringArbitrary';
import { tuple } from './TupleArbitrary';

Expand Down Expand Up @@ -135,30 +136,77 @@ export namespace ObjectConstraints {
}

/** @hidden */
const anythingInternal = (subConstraints: ObjectConstraints): Arbitrary<any> => {
const potentialArbValue = [...subConstraints.values]; // base
if (subConstraints.maxDepth > 0) {
const subAnythingArb = anythingInternal(subConstraints.next());
potentialArbValue.push(dictionary(subConstraints.key, subAnythingArb)); // sub-object
potentialArbValue.push(...subConstraints.values.map(arb => array(arb))); // arrays of base
potentialArbValue.push(array(subAnythingArb)); // mixed content arrays
if (subConstraints.withMap) {
potentialArbValue.push(array(tuple(subConstraints.key, subAnythingArb)).map(v => new Map(v))); // map string -> obj
potentialArbValue.push(array(tuple(subAnythingArb, subAnythingArb)).map(v => new Map(v))); // map obj -> obj
}
if (subConstraints.withSet) {
potentialArbValue.push(...subConstraints.values.map(arb => array(arb).map(v => new Set(v)))); // arrays of base
potentialArbValue.push(array(subAnythingArb).map(v => new Set(v))); // mixed content arrays
const arrayOfAnything = (
arbKeys: Arbitrary<string>,
arbitrariesForBase: Arbitrary<any>[],
arbAny: Arbitrary<any>
): Arbitrary<any[]> => {
return oneof(
oneof(...arbitrariesForBase.map(arb => array(arb))), // base[]
array(arbAny) // anything[]
);
};

/** @hidden */
const setOfAnything = (
arbKeys: Arbitrary<string>,
arbitrariesForBase: Arbitrary<any>[],
arbAny: Arbitrary<any>
): Arbitrary<Set<any>> => {
return oneof(
oneof(...arbitrariesForBase.map(arb => set(arb).map(v => new Set(v)))), // Set<base>
set(arbAny).map(v => new Set(v)) // Set<anything>
);
};

/** @hidden */
const objectOfAnything = (
arbKeys: Arbitrary<string>,
arbitrariesForBase: Arbitrary<any>[],
arbAny: Arbitrary<any>
): Arbitrary<{ [key: string]: any }> => {
return oneof(
oneof(...arbitrariesForBase.map(arb => dictionary(arbKeys, arb))), // {[key]: base}
dictionary(arbKeys, arbAny)
);
};

/** @hidden */
const mapOfAnything = (
arbKeys: Arbitrary<string>,
arbitrariesForBase: Arbitrary<any>[],
arbAny: Arbitrary<any>
): Arbitrary<Map<any, any>> => {
const mapOf = (keyArb: Arbitrary<any>, valueArb: Arbitrary<any>) =>
set(tuple(keyArb, valueArb), (t1, t2) => t1[0] === t2[0]).map(v => new Map(v));
return oneof(
oneof(...arbitrariesForBase.map(arb => mapOf(arbKeys, arb))), // Map<key, base>
oneof(
mapOf(arbKeys, arbAny), // Map<key, anything>
mapOf(arbAny, arbAny) // Map<anything, anything>
)
);
};

/** @hidden */
const anythingInternal = (constraints: ObjectConstraints): Arbitrary<any> => {
const arbKeys = constraints.key;
const arbitrariesForBase = constraints.values;

const eligibleArbitraries: Arbitrary<any>[] = [];
eligibleArbitraries.push(oneof(...arbitrariesForBase)); // base
if (constraints.maxDepth > 0) {
const subArbAny = anythingInternal(constraints.next());
eligibleArbitraries.push(arrayOfAnything(arbKeys, arbitrariesForBase, subArbAny));
eligibleArbitraries.push(objectOfAnything(arbKeys, arbitrariesForBase, subArbAny));
if (constraints.withMap) {
eligibleArbitraries.push(mapOfAnything(arbKeys, arbitrariesForBase, subArbAny));
}
}
if (subConstraints.maxDepth > 1) {
const subSubObjectArb = objectInternal(subConstraints.next().next());
potentialArbValue.push(array(subSubObjectArb)); // array of Object
if (subConstraints.withSet) {
potentialArbValue.push(array(subSubObjectArb).map(v => new Set(v))); // set of Object
if (constraints.withSet) {
eligibleArbitraries.push(setOfAnything(arbKeys, arbitrariesForBase, subArbAny));
}
}
return oneof(...potentialArbValue);
return oneof(...eligibleArbitraries);
};

/** @hidden */
Expand Down
49 changes: 25 additions & 24 deletions test/unit/check/arbitrary/ObjectArbitrary.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as prand from 'pure-rand';
import * as fc from '../../../../lib/fast-check';

import { constant } from '../../../../src/check/arbitrary/ConstantArbitrary';
Expand All @@ -12,7 +13,7 @@ import {
ObjectConstraints
} from '../../../../src/check/arbitrary/ObjectArbitrary';

import * as stubRng from '../../stubs/generators';
import { Random } from '../../../../src/random/generator/Random';

describe('ObjectArbitrary', () => {
const assertShrinkedValue = (original: any, shrinked: any) => {
Expand Down Expand Up @@ -68,7 +69,7 @@ describe('ObjectArbitrary', () => {
fc.array(fc.string(), 1, 10),
fc.array(fc.string(), 1, 10),
(seed, allowedKeys, allowedValues) => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));

const keyArb = oneof(...allowedKeys.map(constant));
const baseArbs = [...allowedValues.map(constant)];
Expand All @@ -86,7 +87,7 @@ describe('ObjectArbitrary', () => {
fc.array(fc.string(), 1, 10),
fc.array(fc.string(), 1, 10),
(seed, depth, keys, values) => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const keyArb = oneof(...keys.map(constant));
const baseArbs = [...values.map(constant)];
const g = anything({ key: keyArb, values: baseArbs, maxDepth: depth }).generate(mrng).value;
Expand All @@ -97,7 +98,7 @@ describe('ObjectArbitrary', () => {
it('Should shrink towards minimal value of type', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
let shrinkable = anything().generate(mrng);
const originalValue = shrinkable.value;
while (shrinkable.shrink().has(v => true)[0]) {
Expand All @@ -109,7 +110,7 @@ describe('ObjectArbitrary', () => {
const checkProduce = (settings: ObjectConstraints.Settings, f: (v: any) => boolean) => {
let numRuns = 0;
const seed = 0;
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const arb = anything(settings);
while (++numRuns <= 1000) {
if (f(arb.generate(mrng).value)) return;
Expand Down Expand Up @@ -142,23 +143,23 @@ describe('ObjectArbitrary', () => {
fc.assert(
fc.property(fc.integer(), seed => {
const settings = { maxDepth: 0 };
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
return !(anything(settings).generate(mrng).value instanceof Array);
})
));
it('Should not be able to produce Set if maxDepth is zero', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const settings = { maxDepth: 0, withSet: true };
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
return !(anything(settings).generate(mrng).value instanceof Set);
})
));
it('Should not be able to produce Map if maxDepth is zero', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const settings = { maxDepth: 0, withMap: true };
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
return !(anything(settings).generate(mrng).value instanceof Map);
})
));
Expand All @@ -178,7 +179,7 @@ describe('ObjectArbitrary', () => {
{ withDeletedKeys: true }
),
(seed, maxDepth, settings) => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const v = anything({ ...settings, maxDepth }).generate(mrng).value;
const depthEvaluator = (node: any): number => {
let subNodes: any[] = [];
Expand All @@ -200,22 +201,22 @@ describe('ObjectArbitrary', () => {
it('Should produce strings', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const g = json().generate(mrng).value;
return typeof g === 'string';
})
));
it('Should generate a parsable JSON', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
JSON.parse(json().generate(mrng).value);
})
));
it('Should shrink towards minimal value of type', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
let shrinkable = json().generate(mrng);
const originalValue = shrinkable.value;
while (shrinkable.shrink().has(v => true)[0]) {
Expand All @@ -230,22 +231,22 @@ describe('ObjectArbitrary', () => {
it('Should produce strings', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const g = unicodeJson().generate(mrng).value;
return typeof g === 'string';
})
));
it('Should generate a parsable JSON', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
JSON.parse(unicodeJson().generate(mrng).value);
})
));
it('Should shrink towards minimal value of type', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
let shrinkable = unicodeJson().generate(mrng);
const originalValue = shrinkable.value;
while (shrinkable.shrink().has(v => true)[0]) {
Expand All @@ -260,14 +261,14 @@ describe('ObjectArbitrary', () => {
it('Should generate a stringifyable object', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
return typeof JSON.stringify(jsonObject().generate(mrng).value) === 'string';
})
));
it('Should be re-created from its json representation', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const g = jsonObject().generate(mrng).value;
expect(JSON.parse(JSON.stringify(g))).toStrictEqual(g);
})
Expand All @@ -277,14 +278,14 @@ describe('ObjectArbitrary', () => {
it('Should generate a stringifyable object', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
return typeof JSON.stringify(unicodeJsonObject().generate(mrng).value) === 'string';
})
));
it('Should be re-created from its json representation', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const g = unicodeJsonObject().generate(mrng).value;
expect(JSON.parse(JSON.stringify(g))).toStrictEqual(g);
})
Expand All @@ -294,7 +295,7 @@ describe('ObjectArbitrary', () => {
it('Should generate an object', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const g = object().generate(mrng).value;
return typeof g === 'object' && !Array.isArray(g);
})
Expand All @@ -304,7 +305,7 @@ describe('ObjectArbitrary', () => {
fc.property(fc.integer(), fc.array(fc.string(), 1, 10), fc.array(fc.string(), 1, 10), (seed, keys, values) => {
const allowedKeys = [...keys];
const allowedValues = [...values];
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));

const keyArb = oneof(...keys.map(constant));
const baseArbs = [...allowedValues.map(constant)];
Expand All @@ -321,7 +322,7 @@ describe('ObjectArbitrary', () => {
fc.array(fc.string(), 1, 10),
fc.array(fc.string(), 1, 10),
(seed, depth, keys, values) => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const keyArb = oneof(...keys.map(constant));
const baseArbs = [...values.map(constant)];
const g = object({ key: keyArb, values: baseArbs, maxDepth: depth }).generate(mrng).value;
Expand All @@ -332,7 +333,7 @@ describe('ObjectArbitrary', () => {
it('Should shrink towards empty object', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
let shrinkable = object().generate(mrng);
while (shrinkable.shrink().has(v => true)[0]) {
shrinkable = shrinkable.shrink().next().value;
Expand All @@ -343,7 +344,7 @@ describe('ObjectArbitrary', () => {
it('Should not suggest input in shrinked values', () =>
fc.assert(
fc.property(fc.integer(), seed => {
const mrng = stubRng.mutable.fastincrease(seed);
const mrng = new Random(prand.xorshift128plus(seed));
const shrinkable = object().generate(mrng);
for (const s of shrinkable.shrink()) expect(s.value).not.toStrictEqual(shrinkable.value);
})
Expand Down

0 comments on commit 1272e5d

Please sign in to comment.