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

Commit 4384326

Browse files
feat: support date from io-ts-types. This new dependency is added as a peer -- code using it is only imported on calling loadIoTsTypesFuzzers() (#11)
BREAKING CHANGE: `core` exports re-organized; some removed. From here on, only breaking changes to exports from `index.ts` will be deemed breaking changes for the package.
1 parent 5bf9860 commit 4384326

13 files changed

+172
-75
lines changed

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
## Quick Start
88

9-
After `yarn add io-ts io-ts-fuzzer`...
9+
After `yarn add fp-ts io-ts io-ts-fuzzer`...
1010

1111
````typescript
1212
import * as t from 'io-ts';
@@ -31,7 +31,7 @@ console.log(fuzzer.encode([493, fuzz.fuzzContext()]));
3131

3232
## Types Supported
3333

34-
Currently supports (and their nested closure):
34+
Currently supports (along with their nested closure):
3535

3636
* `t.array`
3737
* `t.boolean`
@@ -55,6 +55,10 @@ Currently supports (and their nested closure):
5555
* `t.UnknownArray`
5656
* `t.void`
5757

58+
If you additionally do `yarn add monocle-ts io-ts-types` and register
59+
the `io-ts-types` extra fuzzers via `r.register(...await loadIoTsTypesFuzzers)`, the following additional types will be supported:
60+
* `date`
61+
5862
## Use Cases
5963

6064
### Generating Conforming Examples and Verifying Decoder Behavior

package.json

+6
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,15 @@
115115
"concurrently": "^4.1.1",
116116
"gts": "^1.1.0",
117117
"husky": "^3.0.2",
118+
"io-ts-types": "^0.5.0",
118119
"mocha": "^6.2.0",
120+
"monocle-ts": "^2.0.0",
119121
"nyc": "^14.1.1",
120122
"ts-mocha": "^6.0.0",
121123
"typescript": "~3.5.0"
124+
},
125+
"peerDependencies": {
126+
"io-ts-types": "^0.5.0",
127+
"monocle-ts": "^2.0.0"
122128
}
123129
}

src/core/core.ts src/core.ts

+20-45
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,12 @@ import {
55
fuzzGenerator,
66
FuzzContext,
77
FuzzerUnit,
8-
} from '../fuzzer';
8+
concreteFuzzerByName,
9+
} from './fuzzer';
910
import { isLeft, isRight } from 'fp-ts/lib/Either';
10-
import * as rnd from 'seedrandom';
11+
import { rngi, rng } from './rng';
1112

12-
function rng(seed: number) {
13-
return rnd.tychei(`${seed}`, { global: false });
14-
}
15-
16-
function rngi(seed: number) {
17-
return Math.abs(rng(seed).int32());
18-
}
19-
20-
export type BasicType =
13+
type BasicType =
2114
| t.AnyArrayType
2215
| t.ArrayType<t.Mixed>
2316
| t.BooleanType
@@ -43,22 +36,19 @@ export type BasicType =
4336
| t.DictionaryType<t.Mixed, t.Mixed>
4437
| t.RefinementType<t.Mixed>;
4538

46-
export type basicFuzzGenerator<
39+
type basicFuzzGenerator<
4740
T,
4841
C extends t.Decoder<unknown, T> & BasicType
4942
> = fuzzGenerator<T, C>;
5043

51-
export type basicLiteralConcreteFuzzer<
44+
type basicLiteralConcreteFuzzer<
5245
T,
5346
C extends t.Decoder<unknown, T> & BasicType
5447
> = ConcreteFuzzer<T>['func'];
5548

56-
export type BasicFuzzer<
57-
T,
58-
C extends t.Decoder<unknown, T> & BasicType
59-
> = Fuzzer<T, C>;
49+
type BasicFuzzer<T, C extends t.Decoder<unknown, T> & BasicType> = Fuzzer<T, C>;
6050

61-
export function concrete<T, C extends t.Decoder<unknown, T> & BasicType>(
51+
function concrete<T, C extends t.Decoder<unknown, T> & BasicType>(
6252
func: basicLiteralConcreteFuzzer<T, C>,
6353
tag: C['_tag']
6454
): BasicFuzzer<T, C> {
@@ -73,22 +63,7 @@ export function concrete<T, C extends t.Decoder<unknown, T> & BasicType>(
7363
};
7464
}
7565

76-
export function concreteNamed<T, C extends t.Decoder<unknown, T>>(
77-
func: ConcreteFuzzer<T>['func'],
78-
name: C['name']
79-
): Fuzzer<T, C> {
80-
return {
81-
impl: {
82-
type: 'fuzzer',
83-
func,
84-
mightRecurse: false,
85-
},
86-
id: name,
87-
idType: 'name',
88-
};
89-
}
90-
91-
export function gen<T, C extends t.Decoder<unknown, T> & BasicType>(
66+
function gen<T, C extends t.Decoder<unknown, T> & BasicType>(
9267
func: basicFuzzGenerator<T, C>,
9368
tag: C['_tag']
9469
): BasicFuzzer<T, C> {
@@ -138,7 +113,7 @@ const fuzzUnknownWithType = (codec: t.Decoder<unknown, unknown>) => (
138113
mightRecurse: false,
139114
children: [codec],
140115
func: (ctx, n, h0) =>
141-
ctx.shouldGoDeeper() ? h0.encode([rngi(n), ctx.deeper()]) : rngi(n),
116+
ctx.mayRecurse() ? h0.encode([rngi(n), ctx.recursed()]) : rngi(n),
142117
};
143118
};
144119

@@ -165,7 +140,7 @@ export function fuzzUnion(b: t.UnionType<t.Mixed[]>): ConcreteFuzzer<unknown> {
165140
children: b.types,
166141
func: (ctx, n, ...h0) => {
167142
const r = rng(n);
168-
const h1 = ctx.shouldGoDeeper() ? h0 : h0.filter(f => !f.mightRecurse);
143+
const h1 = ctx.mayRecurse() ? h0 : h0.filter(f => !f.mightRecurse);
169144
const h = h1.length === 0 ? h0 : h1;
170145
return h[Math.abs(r.int32()) % h.length].encode([r.int32(), ctx]);
171146
},
@@ -242,7 +217,7 @@ function arrayFuzzFunc(maxLength: number) {
242217
return (ctx: FuzzContext, n: number, h0: FuzzerUnit<unknown>) => {
243218
const ret: unknown[] = [];
244219
const r = rng(n);
245-
if (!ctx.shouldGoDeeper() && h0.mightRecurse) {
220+
if (!ctx.mayRecurse() && h0.mightRecurse) {
246221
return ret;
247222
}
248223
const ml = Math.abs(r.int32()) % maxLength;
@@ -278,7 +253,7 @@ const fuzzReadonlyArrayWithMaxLength = (maxLength: number) => (
278253
func: (ctx, n, h0) => {
279254
const ret: unknown[] = [];
280255
const r = rng(n);
281-
if (!ctx.shouldGoDeeper() && h0.mightRecurse) {
256+
if (!ctx.mayRecurse() && h0.mightRecurse) {
282257
return ret;
283258
}
284259
const ml = Math.abs(r.int32()) % maxLength;
@@ -310,7 +285,7 @@ export function anyArrayFuzzer(maxLength: number = defaultMaxArrayLength) {
310285

311286
export const defaultExtraProps = { ___0000_extra_: t.number };
312287

313-
const fuzzPartialWithExtraCodec = (extra: t.Props) => (
288+
export const fuzzPartialWithExtraCodec = (extra: t.Props) => (
314289
b: t.PartialType<t.Props>
315290
): ConcreteFuzzer<unknown> => {
316291
const kk = Object.getOwnPropertyNames(b.props);
@@ -328,7 +303,7 @@ const fuzzPartialWithExtraCodec = (extra: t.Props) => (
328303
const ret = Object.create(null);
329304
const r = rng(n0);
330305
h.forEach((v, i) => {
331-
if ((ctx.shouldGoDeeper() || !v.mightRecurse) && r.int32() % 2 === 0) {
306+
if ((ctx.mayRecurse() || !v.mightRecurse) && r.int32() % 2 === 0) {
332307
// Only allow key indices from the original type
333308
// or added keys not present in original type.
334309
if (i < kk.length || !kk.includes(keys[i])) {
@@ -345,7 +320,7 @@ export function partialFuzzer(extra: t.Props = defaultExtraProps) {
345320
return gen(fuzzPartialWithExtraCodec(extra), 'PartialType');
346321
}
347322

348-
const fuzzInterfaceWithExtraCodec = (extra: t.Props) => (
323+
export const fuzzInterfaceWithExtraCodec = (extra: t.Props) => (
349324
b: t.InterfaceType<t.Props>
350325
): ConcreteFuzzer<unknown> => {
351326
const kk = Object.getOwnPropertyNames(b.props);
@@ -366,7 +341,7 @@ const fuzzInterfaceWithExtraCodec = (extra: t.Props) => (
366341
if (i < kk.length) {
367342
ret[keys[i]] = v.encode([r.int32(), ctx]);
368343
} else if (
369-
(ctx.shouldGoDeeper() || !v.mightRecurse) &&
344+
(ctx.mayRecurse() || !v.mightRecurse) &&
370345
r.int32() % 2 === 0
371346
) {
372347
// Only allow added keys not present in original type.
@@ -417,9 +392,9 @@ export function fuzzIntersection(
417392
};
418393
}
419394

420-
export const coreFuzzers = [
395+
export const coreFuzzers: ReadonlyArray<Fuzzer> = [
421396
concrete(fuzzNumber, 'NumberType'),
422-
concreteNamed(fuzzInt, 'Int'),
397+
concreteFuzzerByName(fuzzInt, 'Int'),
423398
concrete(fuzzBoolean, 'BooleanType'),
424399
concrete(fuzzString, 'StringType'),
425400
concrete(fuzzNull, 'NullType'),
@@ -439,4 +414,4 @@ export const coreFuzzers = [
439414
gen(fuzzKeyof, 'KeyofType'),
440415
gen(fuzzTuple, 'TupleType'),
441416
gen(fuzzRecursive, 'RecursiveType'),
442-
];
417+
] as ReadonlyArray<Fuzzer>;

src/core/index.ts

-1
This file was deleted.

src/extra-fuzzers/io-ts-types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { FuzzContext, concreteFuzzerByName } from '../fuzzer';
2+
import { rngi } from '../rng';
3+
4+
export function fuzzDate(_: FuzzContext, n: number): Date {
5+
return new Date(rngi(n) / 1000.0);
6+
}
7+
8+
export const fuzzers = [concreteFuzzerByName(fuzzDate, 'Date')];

src/extras.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Fuzzer } from './fuzzer';
2+
3+
async function loadExtras(str: string): Promise<Array<Fuzzer<unknown>>> {
4+
const x = (await import(`./extra-fuzzers/${str}`)) as {
5+
fuzzers: Array<Fuzzer<unknown>>;
6+
};
7+
return x.fuzzers;
8+
}
9+
10+
export async function loadIoTsTypesFuzzers(): Promise<Array<Fuzzer<unknown>>> {
11+
return loadExtras('io-ts-types');
12+
}

src/fuzzer.ts

+25-12
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { Registry } from './registry';
33

44
export interface FuzzContext {
55
/**
6-
* context for the next .
6+
* context for the next level of recursion.
77
*/
8-
deeper(): FuzzContext;
8+
recursed(): FuzzContext;
99
/**
1010
* false iff the fuzzer should not recurse deeper.
1111
*/
12-
shouldGoDeeper(): boolean;
12+
mayRecurse(): boolean;
1313
}
1414

1515
export interface Fuzzer<
@@ -40,6 +40,21 @@ export interface ConcreteFuzzer<T> {
4040
) => T;
4141
}
4242

43+
export function concreteFuzzerByName<T, C extends t.Decoder<unknown, T>>(
44+
func: ConcreteFuzzer<T>['func'],
45+
name: C['name']
46+
): Fuzzer<T, C> {
47+
return {
48+
impl: {
49+
type: 'fuzzer',
50+
func,
51+
mightRecurse: false,
52+
},
53+
id: name,
54+
idType: 'name',
55+
};
56+
}
57+
4358
export type fuzzGenerator<T, C extends t.Decoder<unknown, T>> = (
4459
b: C
4560
) => ConcreteFuzzer<T>;
@@ -52,12 +67,12 @@ const defaultContextOpt = {
5267

5368
export function fuzzContext(opt: ContextOpts = defaultContextOpt): FuzzContext {
5469
class FC implements FuzzContext {
55-
constructor(private readonly mrh: number) {}
56-
deeper(): FuzzContext {
57-
return new FC(this.mrh - 1);
70+
constructor(private readonly maxRecursionHint: number) {}
71+
recursed(): FuzzContext {
72+
return new FC(this.maxRecursionHint - 1);
5873
}
59-
shouldGoDeeper(): boolean {
60-
return this.mrh > 0;
74+
mayRecurse(): boolean {
75+
return this.maxRecursionHint > 0;
6176
}
6277
}
6378
const ropt = { ...defaultContextOpt, ...opt };
@@ -94,7 +109,7 @@ function encoderFunction<T>(
94109
children: Array<FuzzerUnit<unknown>>
95110
): ExampleGenerator<T>['encode'] {
96111
return (a: [number, FuzzContext]) => {
97-
return k.func(k.mightRecurse ? a[1].deeper() : a[1], a[0], ...children);
112+
return k.func(k.mightRecurse ? a[1].recursed() : a[1], a[0], ...children);
98113
};
99114
}
100115

@@ -159,13 +174,11 @@ export function exampleGenerator<T>(
159174
return ret;
160175
}
161176

162-
export const defaultMaxRecursionHint = 5;
163-
164177
export function exampleOf<T>(
165178
d: t.Decoder<unknown, T>,
166179
r: Registry,
167180
a: number,
168-
maxRecursionHint: number = defaultMaxRecursionHint
181+
maxRecursionHint?: number
169182
): T {
170183
return exampleGenerator(r, d).encode([a, fuzzContext({ maxRecursionHint })]);
171184
}

src/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ Third-party dependencies may have their own licenses.
2323

2424
export * from './registry';
2525
export * from './fuzzer';
26-
export * from './core/';
26+
export * from './extras';
27+
28+
import * as core from './core';
29+
export { core };

src/registry.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { Fuzzer, ExampleGenerator, exampleGenerator } from './fuzzer';
22
import * as t from 'io-ts';
3-
import { coreFuzzers, arrayFuzzer } from './core/';
43
import {
54
partialFuzzer,
65
interfaceFuzzer,
76
readonlyArrayFuzzer,
87
anyArrayFuzzer,
98
unknownFuzzer,
10-
} from './core/core';
9+
arrayFuzzer,
10+
coreFuzzers,
11+
} from './core';
1112

1213
export interface Registry {
1314
register<T, U extends t.Decoder<unknown, T>>(v0: Fuzzer<T, U>): Registry;

src/rng.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as rnd from 'seedrandom';
2+
3+
export function rng(seed: number) {
4+
return rnd.tychei(`${seed}`, { global: false });
5+
}
6+
7+
export function rngi(seed: number) {
8+
return Math.abs(rng(seed).int32());
9+
}

0 commit comments

Comments
 (0)