Skip to content

Commit

Permalink
Add with_deleted_keys option for record
Browse files Browse the repository at this point in the history
Fixes #46
  • Loading branch information
dubzzz committed Feb 19, 2018
1 parent cc2276b commit 8984e78
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 4 deletions.
26 changes: 25 additions & 1 deletion src/check/arbitrary/RecordArbitrary.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import Arbitrary from './definition/Arbitrary'
import MutableRandomGenerator from '../../random/generator/MutableRandomGenerator'

import { option } from './OptionArbitrary'
import { generic_tuple } from './TupleArbitrary'

function record<T>(recordModel: {[Key:string]: Arbitrary<T>}): Arbitrary<{[Key:string]: T}> {
export interface RecordConstraints {
with_deleted_keys?: boolean;
}

function rawRecord<T>(recordModel: {[Key:string]: Arbitrary<T>}): Arbitrary<{[Key:string]: T}> {
const keys = Object.keys(recordModel);
const arbs: Arbitrary<T>[] = keys.map(v => recordModel[v]);
return generic_tuple(arbs).map((gs: T[]) => {
Expand All @@ -14,4 +19,23 @@ function record<T>(recordModel: {[Key:string]: Arbitrary<T>}): Arbitrary<{[Key:s
});
}

function record<T>(recordModel: {[Key:string]: Arbitrary<T>}): Arbitrary<{[Key:string]: T}>;
function record<T>(recordModel: {[Key:string]: Arbitrary<T>}, constraints: RecordConstraints): Arbitrary<{[Key:string]: T}>;
function record<T>(recordModel: {[Key:string]: Arbitrary<T>}, constraints?: RecordConstraints): Arbitrary<{[Key:string]: T}> {
if (constraints == null || constraints.with_deleted_keys !== true)
return rawRecord(recordModel);

const updatedRecordModel: {[Key:string]: Arbitrary<{value:T} | null>} = {};
for (const k of Object.keys(recordModel))
updatedRecordModel[k] = option(recordModel[k].map(v => {return {value: v}}));
return rawRecord(updatedRecordModel).map(obj => {
const nobj: {[Key:string]: T} = {};
for (const k of Object.keys(obj)) {
if (obj[k] != null)
nobj[k] = obj[k]!.value;
}
return nobj;
});
}

export { record };
4 changes: 2 additions & 2 deletions src/fast-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { lorem } from './check/arbitrary/LoremArbitrary'
import { anything, object, jsonObject, unicodeJsonObject, json, unicodeJson, ObjectConstraints } from './check/arbitrary/ObjectArbitrary'
import { oneof } from './check/arbitrary/OneOfArbitrary'
import { option } from './check/arbitrary/OptionArbitrary'
import { record } from './check/arbitrary/RecordArbitrary'
import { record, RecordConstraints } from './check/arbitrary/RecordArbitrary'
import { string, asciiString, string16bits, unicodeString, fullUnicodeString, hexaString, base64String } from './check/arbitrary/StringArbitrary'
import { set } from './check/arbitrary/SetArbitrary'
import { tuple, generic_tuple } from './check/arbitrary/TupleArbitrary'
Expand Down Expand Up @@ -49,7 +49,7 @@ export {
// extend the framework
Arbitrary, Shrinkable,
// interfaces
ObjectConstraints, Parameters, RunDetails,
ObjectConstraints, Parameters, RecordConstraints, RunDetails,
UniformDistribution, LinearCongruential, MersenneTwister, MutableRandomGenerator, RandomGenerator,
Stream, stream,
};
10 changes: 10 additions & 0 deletions test/e2e/arbitraries/RecordArbitrary.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,15 @@ describe(`RecordArbitrary (seed: ${seed})`, () => {
assert.ok(out.failed, 'Should have failed');
assert.deepEqual(out.counterexample, [{aa:0,bb:{},cc:" "}], 'Should shrink to counterexample {aa: 0, bb: {}, cc: " "}');
});
it('Should shrink on a record with bb as single key', () => {
const recordModel = {
aa: fc.integer(),
bb: fc.object(),
cc: fc.string()
};
const out = fc.check(fc.property(fc.record(recordModel, {with_deleted_keys: true}), (obj: any) => obj['bb'] == null), {seed: seed});
assert.ok(out.failed, 'Should have failed');
assert.deepEqual(out.counterexample, [{bb:{}}], 'Should shrink to counterexample {bb: {}}');
});
});
});
35 changes: 34 additions & 1 deletion test/unit/check/arbitrary/RecordArbitrary.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as assert from 'power-assert';
import * as fc from '../../../../lib/fast-check';

import { constant } from '../../../../src/check/arbitrary/ConstantArbitrary';
import { record } from '../../../../src/check/arbitrary/RecordArbitrary';
import MersenneTwister from '../../../../src/random/generator/MersenneTwister';
import MutableRandomGenerator from '../../../../src/random/generator/MutableRandomGenerator';
Expand All @@ -11,7 +12,7 @@ import * as stubRng from '../../stubs/generators';
describe("RecordArbitrary", () => {
describe('record', () => {
it('Should produce a record having the right keys', () => fc.assert(
fc.property(fc.array(fc.string()), fc.integer(), (keys, seed) => {
fc.property(fc.set(fc.string()), fc.integer(), (keys, seed) => {
const mrng = stubRng.mutable.fastincrease(seed);
const expectedRecord = {};
const recordModel = {};
Expand All @@ -23,5 +24,37 @@ describe("RecordArbitrary", () => {
assert.deepStrictEqual(g, expectedRecord);
})
));
it('Should produce a record with missing keys', () => fc.assert(
fc.property(fc.set(fc.string(), 1, 10), fc.nat(), fc.integer(), (keys, missingIdx, seed) => {
const mrng = new MutableRandomGenerator(MersenneTwister.from(seed));
const recordModel = {};
for (const k of keys)
recordModel[k] = constant(`_${k}_`);

const arb = record(recordModel, {with_deleted_keys: true});
for (let idx = 0 ; idx != 1000 ; ++idx) {
const g = arb.generate(mrng).value;
if (! g.hasOwnProperty(keys[missingIdx % keys.length]))
return true;
}
return false;
})
));
it('Should produce a record with present keys', () => fc.assert(
fc.property(fc.set(fc.string(), 1, 10), fc.nat(), fc.integer(), (keys, missingIdx, seed) => {
const mrng = new MutableRandomGenerator(MersenneTwister.from(seed));
const recordModel = {};
for (const k of keys)
recordModel[k] = constant(`_${k}_`);

const arb = record(recordModel, {with_deleted_keys: true});
for (let idx = 0 ; idx != 1000 ; ++idx) {
const g = arb.generate(mrng).value;
if (g[keys[missingIdx % keys.length]] === `_${keys[missingIdx % keys.length]}_`)
return true;
}
return false;
})
));
});
});

0 comments on commit 8984e78

Please sign in to comment.