Skip to content

Commit 8984e78

Browse files
committed
Add with_deleted_keys option for record
Fixes #46
1 parent cc2276b commit 8984e78

File tree

4 files changed

+71
-4
lines changed

4 files changed

+71
-4
lines changed

src/check/arbitrary/RecordArbitrary.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import Arbitrary from './definition/Arbitrary'
22
import MutableRandomGenerator from '../../random/generator/MutableRandomGenerator'
33

4+
import { option } from './OptionArbitrary'
45
import { generic_tuple } from './TupleArbitrary'
56

6-
function record<T>(recordModel: {[Key:string]: Arbitrary<T>}): Arbitrary<{[Key:string]: T}> {
7+
export interface RecordConstraints {
8+
with_deleted_keys?: boolean;
9+
}
10+
11+
function rawRecord<T>(recordModel: {[Key:string]: Arbitrary<T>}): Arbitrary<{[Key:string]: T}> {
712
const keys = Object.keys(recordModel);
813
const arbs: Arbitrary<T>[] = keys.map(v => recordModel[v]);
914
return generic_tuple(arbs).map((gs: T[]) => {
@@ -14,4 +19,23 @@ function record<T>(recordModel: {[Key:string]: Arbitrary<T>}): Arbitrary<{[Key:s
1419
});
1520
}
1621

22+
function record<T>(recordModel: {[Key:string]: Arbitrary<T>}): Arbitrary<{[Key:string]: T}>;
23+
function record<T>(recordModel: {[Key:string]: Arbitrary<T>}, constraints: RecordConstraints): Arbitrary<{[Key:string]: T}>;
24+
function record<T>(recordModel: {[Key:string]: Arbitrary<T>}, constraints?: RecordConstraints): Arbitrary<{[Key:string]: T}> {
25+
if (constraints == null || constraints.with_deleted_keys !== true)
26+
return rawRecord(recordModel);
27+
28+
const updatedRecordModel: {[Key:string]: Arbitrary<{value:T} | null>} = {};
29+
for (const k of Object.keys(recordModel))
30+
updatedRecordModel[k] = option(recordModel[k].map(v => {return {value: v}}));
31+
return rawRecord(updatedRecordModel).map(obj => {
32+
const nobj: {[Key:string]: T} = {};
33+
for (const k of Object.keys(obj)) {
34+
if (obj[k] != null)
35+
nobj[k] = obj[k]!.value;
36+
}
37+
return nobj;
38+
});
39+
}
40+
1741
export { record };

src/fast-check.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { lorem } from './check/arbitrary/LoremArbitrary'
1818
import { anything, object, jsonObject, unicodeJsonObject, json, unicodeJson, ObjectConstraints } from './check/arbitrary/ObjectArbitrary'
1919
import { oneof } from './check/arbitrary/OneOfArbitrary'
2020
import { option } from './check/arbitrary/OptionArbitrary'
21-
import { record } from './check/arbitrary/RecordArbitrary'
21+
import { record, RecordConstraints } from './check/arbitrary/RecordArbitrary'
2222
import { string, asciiString, string16bits, unicodeString, fullUnicodeString, hexaString, base64String } from './check/arbitrary/StringArbitrary'
2323
import { set } from './check/arbitrary/SetArbitrary'
2424
import { tuple, generic_tuple } from './check/arbitrary/TupleArbitrary'
@@ -49,7 +49,7 @@ export {
4949
// extend the framework
5050
Arbitrary, Shrinkable,
5151
// interfaces
52-
ObjectConstraints, Parameters, RunDetails,
52+
ObjectConstraints, Parameters, RecordConstraints, RunDetails,
5353
UniformDistribution, LinearCongruential, MersenneTwister, MutableRandomGenerator, RandomGenerator,
5454
Stream, stream,
5555
};

test/e2e/arbitraries/RecordArbitrary.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,15 @@ describe(`RecordArbitrary (seed: ${seed})`, () => {
1414
assert.ok(out.failed, 'Should have failed');
1515
assert.deepEqual(out.counterexample, [{aa:0,bb:{},cc:" "}], 'Should shrink to counterexample {aa: 0, bb: {}, cc: " "}');
1616
});
17+
it('Should shrink on a record with bb as single key', () => {
18+
const recordModel = {
19+
aa: fc.integer(),
20+
bb: fc.object(),
21+
cc: fc.string()
22+
};
23+
const out = fc.check(fc.property(fc.record(recordModel, {with_deleted_keys: true}), (obj: any) => obj['bb'] == null), {seed: seed});
24+
assert.ok(out.failed, 'Should have failed');
25+
assert.deepEqual(out.counterexample, [{bb:{}}], 'Should shrink to counterexample {bb: {}}');
26+
});
1727
});
1828
});

test/unit/check/arbitrary/RecordArbitrary.spec.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as assert from 'power-assert';
22
import * as fc from '../../../../lib/fast-check';
33

4+
import { constant } from '../../../../src/check/arbitrary/ConstantArbitrary';
45
import { record } from '../../../../src/check/arbitrary/RecordArbitrary';
56
import MersenneTwister from '../../../../src/random/generator/MersenneTwister';
67
import MutableRandomGenerator from '../../../../src/random/generator/MutableRandomGenerator';
@@ -11,7 +12,7 @@ import * as stubRng from '../../stubs/generators';
1112
describe("RecordArbitrary", () => {
1213
describe('record', () => {
1314
it('Should produce a record having the right keys', () => fc.assert(
14-
fc.property(fc.array(fc.string()), fc.integer(), (keys, seed) => {
15+
fc.property(fc.set(fc.string()), fc.integer(), (keys, seed) => {
1516
const mrng = stubRng.mutable.fastincrease(seed);
1617
const expectedRecord = {};
1718
const recordModel = {};
@@ -23,5 +24,37 @@ describe("RecordArbitrary", () => {
2324
assert.deepStrictEqual(g, expectedRecord);
2425
})
2526
));
27+
it('Should produce a record with missing keys', () => fc.assert(
28+
fc.property(fc.set(fc.string(), 1, 10), fc.nat(), fc.integer(), (keys, missingIdx, seed) => {
29+
const mrng = new MutableRandomGenerator(MersenneTwister.from(seed));
30+
const recordModel = {};
31+
for (const k of keys)
32+
recordModel[k] = constant(`_${k}_`);
33+
34+
const arb = record(recordModel, {with_deleted_keys: true});
35+
for (let idx = 0 ; idx != 1000 ; ++idx) {
36+
const g = arb.generate(mrng).value;
37+
if (! g.hasOwnProperty(keys[missingIdx % keys.length]))
38+
return true;
39+
}
40+
return false;
41+
})
42+
));
43+
it('Should produce a record with present keys', () => fc.assert(
44+
fc.property(fc.set(fc.string(), 1, 10), fc.nat(), fc.integer(), (keys, missingIdx, seed) => {
45+
const mrng = new MutableRandomGenerator(MersenneTwister.from(seed));
46+
const recordModel = {};
47+
for (const k of keys)
48+
recordModel[k] = constant(`_${k}_`);
49+
50+
const arb = record(recordModel, {with_deleted_keys: true});
51+
for (let idx = 0 ; idx != 1000 ; ++idx) {
52+
const g = arb.generate(mrng).value;
53+
if (g[keys[missingIdx % keys.length]] === `_${keys[missingIdx % keys.length]}_`)
54+
return true;
55+
}
56+
return false;
57+
})
58+
));
2659
});
2760
});

0 commit comments

Comments
 (0)