Skip to content
This repository was archived by the owner on Jan 2, 2024. It is now read-only.

Commit 6a9dab1

Browse files
committed
feat: support configuring extra properties for interface fuzzers
1 parent 44df20c commit 6a9dab1

File tree

5 files changed

+91
-31
lines changed

5 files changed

+91
-31
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ The `FluentRegistry` interface lets you easily change certain core
6767
fuzzers, currently:
6868

6969
* maximum array length
70-
* extra properties inserted into `partial` objects
70+
* extra properties inserted into `partial` and `type` (interface) objects
7171

7272
### Fuzzing a Type (TODO)
7373

src/core/core.ts

+47-23
Original file line numberDiff line numberDiff line change
@@ -144,23 +144,6 @@ export function fuzzKeyof(
144144
};
145145
}
146146

147-
export function fuzzInterface(
148-
b: t.InterfaceType<t.Props>
149-
): ConcreteFuzzer<unknown> {
150-
const keys = Object.getOwnPropertyNames(b.props);
151-
const vals = keys.map(k => b.props[k]);
152-
return {
153-
children: vals,
154-
func: (n, ...h) => {
155-
const ret = Object.create(null);
156-
h.forEach((v, i) => {
157-
ret[keys[i]] = v.encode(n + i);
158-
});
159-
return ret;
160-
},
161-
};
162-
}
163-
164147
export function fuzzTuple(
165148
b: t.TupleType<t.Mixed[]>
166149
): ConcreteFuzzer<unknown[]> {
@@ -198,9 +181,11 @@ export function arrayFuzzer(maxLength: number = defaultMaxArrayLength) {
198181
*/
199182
export const fuzzArray = fuzzArrayWithMaxLength();
200183

201-
const fuzzPartialWithExtraCodec = (
202-
extra: t.Props = { ___0000_extra_: t.number }
203-
) => (b: t.PartialType<t.Props>): ConcreteFuzzer<unknown> => {
184+
export const defaultExtraProps = { ___0000_extra_: t.number };
185+
186+
const fuzzPartialWithExtraCodec = (extra: t.Props = defaultExtraProps) => (
187+
b: t.PartialType<t.Props>
188+
): ConcreteFuzzer<unknown> => {
204189
const kk = Object.getOwnPropertyNames(b.props);
205190
const xx = Object.getOwnPropertyNames(extra);
206191
const keys = Object.getOwnPropertyNames(b.props).concat(
@@ -216,7 +201,7 @@ const fuzzPartialWithExtraCodec = (
216201
h.forEach((v, i) => {
217202
if (n & (2 ** i)) {
218203
// Only allow key indices from the original type
219-
// or added keys without indices.
204+
// or added keys not present in original type.
220205
if (i < kk.length || !kk.includes(keys[i])) {
221206
ret[keys[i]] = v.encode(n + i);
222207
}
@@ -227,7 +212,7 @@ const fuzzPartialWithExtraCodec = (
227212
};
228213
};
229214

230-
export function partialFuzzer(extra: t.Props = { ___0000_extra_: t.number }) {
215+
export function partialFuzzer(extra: t.Props = defaultExtraProps) {
231216
return gen(fuzzPartialWithExtraCodec(extra), 'PartialType');
232217
}
233218

@@ -236,6 +221,45 @@ export function partialFuzzer(extra: t.Props = { ___0000_extra_: t.number }) {
236221
*/
237222
export const fuzzPartial = fuzzPartialWithExtraCodec();
238223

224+
const fuzzInterfaceWithExtraCodec = (extra: t.Props = defaultExtraProps) => (
225+
b: t.InterfaceType<t.Props>
226+
): ConcreteFuzzer<unknown> => {
227+
const kk = Object.getOwnPropertyNames(b.props);
228+
const xx = Object.getOwnPropertyNames(extra);
229+
const keys = Object.getOwnPropertyNames(b.props).concat(
230+
Object.getOwnPropertyNames(extra)
231+
);
232+
const vals = keys.map((k, i) =>
233+
i < kk.length ? b.props[k] : extra[xx[i - kk.length]]
234+
);
235+
return {
236+
children: vals,
237+
func: (n, ...h) => {
238+
const ret = Object.create(null);
239+
h.forEach((v, i) => {
240+
if (i < kk.length) {
241+
ret[keys[i]] = v.encode(n + i);
242+
} else if (n & (2 ** (i - kk.length))) {
243+
// Only allow added keys not present in original type.
244+
if (!kk.includes(keys[i])) {
245+
ret[keys[i]] = v.encode(n + i);
246+
}
247+
}
248+
});
249+
return ret;
250+
},
251+
};
252+
};
253+
254+
export function interfaceFuzzer(extra: t.Props = defaultExtraProps) {
255+
return gen(fuzzInterfaceWithExtraCodec(extra), 'InterfaceType');
256+
}
257+
258+
/**
259+
* @deprecated
260+
*/
261+
export const fuzzInterface = fuzzInterfaceWithExtraCodec();
262+
239263
export function fuzzIntersection(
240264
b: t.IntersectionType<t.Any[]>
241265
): ConcreteFuzzer<unknown> {
@@ -264,7 +288,7 @@ export const coreFuzzers = [
264288
concrete(fuzzVoid, 'VoidType'),
265289
concrete(fuzzUnknown, 'UnknownType'),
266290
gen(fuzzUnion, 'UnionType'),
267-
gen(fuzzInterface, 'InterfaceType'),
291+
interfaceFuzzer(),
268292
partialFuzzer(),
269293
arrayFuzzer(),
270294
gen(fuzzIntersection, 'IntersectionType'),

src/registry.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Fuzzer, ExampleGenerator, exampleGenerator } from './fuzzer';
22
import * as t from 'io-ts';
33
import { coreFuzzers, arrayFuzzer } from './core/';
4-
import { partialFuzzer } from './core/core';
4+
import { partialFuzzer, interfaceFuzzer } from './core/core';
55

66
export interface Registry {
77
register<T, U extends t.Decoder<unknown, T>>(v0: Fuzzer<T, U>): Registry;
@@ -15,6 +15,7 @@ export interface Registry {
1515
export interface FluentRegistry extends Registry {
1616
withArrayFuzzer(maxLength?: number): FluentRegistry;
1717
withPartialFuzzer(extra?: t.Props): FluentRegistry;
18+
withInterfaceFuzzer(extra?: t.Props): FluentRegistry;
1819
}
1920

2021
class FluentifiedRegistry implements FluentRegistry {
@@ -46,6 +47,11 @@ class FluentifiedRegistry implements FluentRegistry {
4647
this.register(partialFuzzer(extra));
4748
return this;
4849
}
50+
51+
withInterfaceFuzzer(extra?: t.Props): FluentRegistry {
52+
this.register(interfaceFuzzer(extra));
53+
return this;
54+
}
4955
}
5056

5157
export function fluent(r: Registry): FluentRegistry {

test/helpers.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export const types = [
88
t.union([t.string, t.number, t.boolean]),
99
t.intersection([t.type({ s: t.string }), t.type({ m: t.number })]),
1010
t.type({ s: t.string, m: t.number }),
11+
t.type({ s: t.string, m: t.number, ___0000_extra_: t.string }),
12+
t.partial({ s: t.string, m: t.number }),
1113
t.partial({ s: t.string, m: t.number, ___0000_extra_: t.boolean }),
1214
t.null,
1315
t.undefined,

test/test-registry.ts

+34-6
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,20 @@ describe('registry', () => {
173173
}
174174
});
175175

176+
it(`overrides apply to the underlying registry`, () => {
177+
const b = t.array(t.number);
178+
const r0 = lib.createCoreRegistry();
179+
lib.fluent(r0).withArrayFuzzer(3);
180+
const r = r0.exampleGenerator(b);
181+
for (let i = 0; i < 100; i++) {
182+
assert.ok(r.encode(i).length <= 3);
183+
}
184+
});
185+
});
186+
});
187+
188+
describe('#withPartialFuzzer', () => {
189+
describe('on the core registry', () => {
176190
it(`overrides the partial object extra properties`, () => {
177191
const b = t.partial({ a: t.number });
178192
const r0 = lib.createCoreRegistry();
@@ -188,15 +202,29 @@ describe('registry', () => {
188202
assert.ok(keys.has('a'));
189203
assert.ok(keys.has('b'));
190204
});
205+
});
206+
});
191207

192-
it(`overrides apply to the underlying registry`, () => {
193-
const b = t.array(t.number);
208+
describe('#withInterfaceFuzzer', () => {
209+
describe('on the core registry', () => {
210+
it(`overrides the partial object extra properties`, () => {
211+
const b = t.type({ a: t.number, j: t.boolean });
194212
const r0 = lib.createCoreRegistry();
195-
lib.fluent(r0).withArrayFuzzer(3);
196-
const r = r0.exampleGenerator(b);
197-
for (let i = 0; i < 100; i++) {
198-
assert.ok(r.encode(i).length <= 3);
213+
const r = lib
214+
.fluent(r0)
215+
.withInterfaceFuzzer({ b: t.string })
216+
.exampleGenerator(b);
217+
const keys = new Set<string>();
218+
for (let i = 0; i < 10; i++) {
219+
const ek = Object.keys(r.encode(i));
220+
assert.ok(ek.includes('a'));
221+
assert.ok(ek.includes('j'));
222+
ek.map(x => keys.add(x));
199223
}
224+
assert.deepStrictEqual(keys.size, 3);
225+
assert.ok(keys.has('a'));
226+
assert.ok(keys.has('j'));
227+
assert.ok(keys.has('b'));
200228
});
201229
});
202230
});

0 commit comments

Comments
 (0)