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

Commit d718348

Browse files
committed
feat: basic framework + fuzz strings, numbers, bools, and unions
1 parent f149d86 commit d718348

10 files changed

+715
-189
lines changed

README.md

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
1-
# io-ts-fuzzer -
1+
# io-ts-fuzzer - Fuzzing for io-ts codecs and types
22

3-
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE) [![npm](https://img.shields.io/npm/v/io-ts-fuzzer.svg)](https://www.npmjs.com/package/io-ts-fuzzer) [![Build Status](https://travis-ci.com/holvonix-open/io-ts-fuzzer.svg?branch=master)](https://travis-ci.com/holvonix-open/io-ts-fuzzer) [![GitHub last commit](https://img.shields.io/github/last-commit/holvonix-open/io-ts-fuzzer.svg)](https://github.com/holvonix-open/io-ts-fuzzer/commits) [![codecov](https://codecov.io/gh/holvonix-open/io-ts-fuzzer/branch/master/graph/badge.svg)](https://codecov.io/gh/holvonix-open/io-ts-fuzzer) [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=holvonix-open/io-ts-fuzzer)](https://dependabot.com) [![DeepScan grade](https://deepscan.io/api/teams/XX/projects/YY/branches/ZZ/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=XX&pid=YY&bid=ZZ) [![Code Style: Google](https://img.shields.io/badge/code%20style-google-blueviolet.svg)](https://github.com/google/gts) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
3+
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE) [![npm](https://img.shields.io/npm/v/io-ts-fuzzer.svg)](https://www.npmjs.com/package/io-ts-fuzzer) [![Build Status](https://travis-ci.com/holvonix-open/io-ts-fuzzer.svg?branch=master)](https://travis-ci.com/holvonix-open/io-ts-fuzzer) [![GitHub last commit](https://img.shields.io/github/last-commit/holvonix-open/io-ts-fuzzer.svg)](https://github.com/holvonix-open/io-ts-fuzzer/commits) [![codecov](https://codecov.io/gh/holvonix-open/io-ts-fuzzer/branch/master/graph/badge.svg)](https://codecov.io/gh/holvonix-open/io-ts-fuzzer) [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=holvonix-open/io-ts-fuzzer)](https://dependabot.com) [![DeepScan grade](https://deepscan.io/api/teams/4465/projects/6653/branches/56883/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=4465&pid=6653&bid=56883) [![Code Style: Google](https://img.shields.io/badge/code%20style-google-blueviolet.svg)](https://github.com/google/gts) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
44

55

66
## Quick Start
77

8-
After `yarn add io-ts-fuzzer`:
8+
After `yarn add io-ts io-ts-fuzzer`:
99

1010
````typescript
11-
import { TODO } from 'io-ts-fuzzer';
11+
import * as t from 'io-ts';
12+
import * as fuzz from 'io-ts-fuzzer';
1213

13-
async function getIt() {
14-
// TODO
14+
function fuzz() {
15+
// Fuzzers for common types
16+
const r = fuzz.createCoreRegistry();
17+
18+
// Type to fuzz
19+
const target = t.union([t.string, t.number, t.boolean]);
20+
21+
// Builds a particular fuzzer from the registry.
22+
const fuzzer = fuzz.exampleGenerator(r, target);
23+
24+
// Make examples.
25+
console.log(fuzzer.encode(0));
26+
console.log(fuzzer.encode(1));
27+
console.log(fuzzer.encode(2));
28+
console.log(fuzzer.encode(493));
1529
}
1630
````
1731

package.json

+71-28
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
"url": "https://github.com/holvonix-open/io-ts-fuzzer.git"
88
},
99
"keywords": [
10-
"io-ts", "codecs", "fuzzer", "security"
10+
"io-ts",
11+
"codecs",
12+
"fuzzer",
13+
"fuzzing",
14+
"security"
1115
],
1216
"engines": {
1317
"node": "^10.0.0 || ^ 12.0.0",
@@ -22,43 +26,74 @@
2226
"NOTICE",
2327
"README.md"
2428
],
25-
"nyc": {
26-
"cache": false,
27-
"extension": [
28-
".ts",
29-
".tsx"
30-
],
31-
"exclude": [
32-
"prettier.config.js",
33-
"**/*.d.ts",
34-
"test/**/*.*",
35-
"build/**/**/*.js"
36-
],
37-
"all": true
38-
},
3929
"author": "Holvonix LLC",
4030
"license": "SEE LICENSE IN LICENSE",
4131
"private": false,
4232
"scripts": {
43-
"t": "yarn fix && yarn test --grep='#slow' --invert",
44-
"cleantests": "rm -rf *.lcov .nyc_output coverage",
45-
"clean": "gts clean",
46-
"start": "node index.js",
47-
"debug": "node --inspect index.js",
48-
"cleanstart": "yarn clean; yarn start",
49-
"test": "nyc ts-mocha -p ./tsconfig.json 'test/**/*.ts'",
50-
"test-coverage": "yarn clean && yarn test && yarn report-coverage",
33+
"t": "yarn test --inspect --grep='#e2e|#int' --invert",
34+
"watch:build": "yarn run --silent build:core --watch",
35+
"watch:test": "yarn run --silent test-impl --watch --grep='#e2e|#int' --invert -R min",
36+
"watch:test-cov": "chokidar --throttle 750 --debounce 500 --silent --initial \"build/**/*.js\" -c \"yarn run --silent watch:test-impl\"",
37+
"watch:test-impl": "yarn run --silent test-impl-lcov --grep='#e2e|#int' --invert -R min",
38+
"w": "yarn run clean && yarn run build && concurrently --raw -n tsc,mocha \"yarn run --silent watch:build\" \"yarn run --silent watch:test\"",
39+
"wc": "yarn run clean && yarn run build && concurrently --raw -n tsc,mocha \"yarn run --silent watch:build\" \"yarn run --silent watch:test-cov\"",
40+
"postinstall": "yarn test:postinstall",
41+
"build:core": "tsc -p .",
42+
"build": "yarn build:core",
43+
"clean.builds": "gts clean",
44+
"clean": "yarn clean.builds && yarn clean.tests",
45+
"clean.tests": "rm -rf *.lcov .nyc_output *.log coverage",
46+
"test:postinstall": "yarn clean && yarn build && yarn test-impl --grep='#e2e' --invert",
47+
"test": "yarn clean && yarn build && yarn test-impl",
48+
"test-impl": "nyc --reporter=text --reporter=lcov mocha",
49+
"test-impl-lcov": "nyc --reporter=lcov mocha",
50+
"test-coverage": "yarn test && yarn report-coverage",
5151
"report-coverage": "nyc report --reporter=json && codecov -F unit -f coverage/*.json",
5252
"lint": "yarn fix && yarn run check",
5353
"check": "gts check",
54-
"compile": "tsc -p .",
5554
"fix": "gts fix",
56-
"prepare": "yarn clean && yarn run check && yarn run compile",
57-
"pretest": "yarn run compile",
58-
"posttest": "yarn run check",
5955
"preversion": "yarn test",
56+
"docs": "yarn clean && yarn build && rm -rf docs && mkdir -p docs && ( mocha --grep='#private' --invert -R markdown >docs/index.md )",
57+
"test-unit": "yarn test --grep='#e2e|#int' --invert",
58+
"test-e2e": "yarn test --grep='#e2e'",
59+
"test-int": "yarn test --grep='#int'",
60+
"up": "yarn upgrade-interactive --latest",
61+
"yarn": "rm -rf yarn.lock node_modules/ && yarn",
6062
"semantic-release": "semantic-release"
6163
},
64+
"husky": {
65+
"hooks": {
66+
"pre-commit": "yarn clean && yarn run check && yarn test:postinstall"
67+
}
68+
},
69+
"nyc": {
70+
"skip-full": true,
71+
"cache": false,
72+
"extension": [
73+
".ts",
74+
".tsx"
75+
],
76+
"require": [
77+
"source-map-support/register",
78+
"ts-node/register"
79+
],
80+
"include": [
81+
"src/**/*.ts",
82+
"src/**/*.tsx",
83+
"build/src/**/*.js",
84+
"build/src/**/*.jsx"
85+
],
86+
"exclude": [],
87+
"all": true
88+
},
89+
"mocha": {
90+
"require": [
91+
"source-map-support/register",
92+
"ts-node/register"
93+
],
94+
"forbid-pending": true,
95+
"spec": "./build/test/**/test-*.js"
96+
},
6297
"release": {
6398
"plugins": [
6499
[
@@ -97,14 +132,22 @@
97132
"@semantic-release/github"
98133
]
99134
},
100-
"dependencies": {},
135+
"dependencies": {
136+
"fp-ts": "^2.0.3",
137+
"io-ts": "^2.0.0",
138+
"io-ts-types": "^0.5.0",
139+
"monocle-ts": "^2.0.0"
140+
},
101141
"devDependencies": {
102142
"@semantic-release/changelog": "^3.0.4",
103143
"@semantic-release/git": "^7.0.16",
104144
"@types/mocha": "^5.2.5",
105145
"@types/node": "^12.6.9",
146+
"chokidar-cli": "^2.0.0",
106147
"codecov": "^3.4.0",
148+
"concurrently": "^4.1.1",
107149
"gts": "^1.1.0",
150+
"husky": "^3.0.2",
108151
"mocha": "^6.2.0",
109152
"nyc": "^14.1.1",
110153
"semantic-release": "^15.13.19",

src/core/core.ts

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import * as t from 'io-ts';
2+
import { Fuzzer, ConcreteFuzzer, fuzzGenerator } from '../fuzzer';
3+
4+
export type BasicType =
5+
| t.NullType
6+
| t.UndefinedType
7+
| t.VoidType
8+
| t.UnknownType
9+
| t.StringType
10+
| t.NumberType
11+
| t.BooleanType
12+
| t.AnyArrayType
13+
| t.AnyDictionaryType
14+
| t.RefinementType<t.Any>
15+
| t.LiteralType<string | number | boolean>
16+
| t.KeyofType<{ [key: string]: unknown }>
17+
| t.RecursiveType<t.Any>
18+
| t.ArrayType<t.Any>
19+
// tslint:disable-next-line:no-any
20+
| t.InterfaceType<any>
21+
// tslint:disable-next-line:no-any
22+
| t.PartialType<any>
23+
| t.DictionaryType<t.Any, t.Any>
24+
| t.UnionType<t.Any[]>
25+
// tslint:disable-next-line:no-any
26+
| t.InterfaceType<any>
27+
| t.TupleType<t.Any[]>
28+
| t.ReadonlyType<t.Any>
29+
| t.ReadonlyArrayType<t.Any>
30+
| t.ExactType<t.Any>
31+
| t.UnknownType;
32+
33+
export type basicFuzzGenerator<
34+
T,
35+
C extends t.Decoder<unknown, T> & BasicType
36+
> = fuzzGenerator<T, C>;
37+
38+
export type basicLiteralConcreteFuzzer<
39+
T,
40+
C extends t.Decoder<unknown, T> & BasicType
41+
> = ConcreteFuzzer<T>['func'];
42+
43+
export type BasicFuzzer<
44+
T,
45+
C extends t.Decoder<unknown, T> & BasicType
46+
> = Fuzzer<T, C>;
47+
48+
export function concrete<T, C extends t.Decoder<unknown, T> & BasicType>(
49+
func: basicLiteralConcreteFuzzer<T, C>,
50+
tag: C['_tag']
51+
): BasicFuzzer<T, C> {
52+
return {
53+
impl: {
54+
type: 'fuzzer',
55+
func,
56+
},
57+
id: tag,
58+
idType: 'tag',
59+
};
60+
}
61+
62+
export function gen<T, C extends t.Decoder<unknown, T> & BasicType>(
63+
func: basicFuzzGenerator<T, C>,
64+
tag: C['_tag']
65+
): BasicFuzzer<T, C> {
66+
return {
67+
impl: {
68+
type: 'generator',
69+
func,
70+
},
71+
id: tag,
72+
idType: 'tag',
73+
};
74+
}
75+
76+
export function fuzzBoolean(n: number) {
77+
return n % 2 === 0;
78+
}
79+
80+
export function fuzzNumber(n: number) {
81+
return n;
82+
}
83+
84+
export function fuzzString(n: number) {
85+
return `${n}`;
86+
}
87+
88+
// tslint:disable-next-line:no-any
89+
export function fuzzUnion(b: t.UnionC<any>): ConcreteFuzzer<any> {
90+
return {
91+
children: b.types,
92+
func: (n, ...h) => {
93+
return h[n % h.length].encode(n);
94+
},
95+
};
96+
}
97+
98+
export const coreFuzzers = [
99+
concrete(fuzzNumber, 'NumberType'),
100+
concrete(fuzzBoolean, 'BooleanType'),
101+
concrete(fuzzString, 'StringType'),
102+
gen(fuzzUnion, 'UnionType'),
103+
];

src/core/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { coreFuzzers } from './core';

src/fuzzer.ts

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as t from 'io-ts';
2+
import { Registry } from './registry';
3+
4+
export interface Fuzzer<T, C extends t.Decoder<unknown, T>> {
5+
id: string;
6+
idType: 'name' | 'tag';
7+
impl: FuzzerGenerator<T, C> | ImmediateConcreteFuzzer<T>;
8+
}
9+
10+
export interface FuzzerGenerator<T, C extends t.Decoder<unknown, T>> {
11+
type: 'generator';
12+
func: fuzzGenerator<T, C>;
13+
}
14+
15+
export interface ImmediateConcreteFuzzer<T> extends ConcreteFuzzer<T> {
16+
type: 'fuzzer';
17+
}
18+
19+
export interface ConcreteFuzzer<T> {
20+
children?: Array<t.Decoder<unknown, unknown>>;
21+
22+
// tslint:disable-next-line:no-any
23+
func: (n: number, ...h: Array<ExampleGenerator<any>>) => T;
24+
}
25+
26+
export type fuzzGenerator<T, C extends t.Decoder<unknown, T>> = (
27+
b: C
28+
) => ConcreteFuzzer<T>;
29+
30+
/**
31+
* Given a number, generates a pseudorandom (but deterministic)
32+
* instance of T.
33+
*/
34+
export type ExampleGenerator<T> = t.Encoder<number, T>;
35+
36+
export function exampleOf<T>(
37+
d: t.Decoder<unknown, T>,
38+
r: Registry,
39+
a: number
40+
): T {
41+
return exampleGenerator(r, d).encode(a);
42+
}
43+
44+
export function exampleGeneratorFromConcreteFuzzer<T>(
45+
r: Registry,
46+
fi: ConcreteFuzzer<T>
47+
): ExampleGenerator<T> {
48+
const k = fi.func;
49+
const children = fi.children
50+
? fi.children.map(x => exampleGenerator(r, x))
51+
: [];
52+
return {
53+
encode: a => k(a, ...children),
54+
};
55+
}
56+
57+
export function exampleGenerator<T>(
58+
r: Registry,
59+
d: t.Decoder<unknown, T>
60+
): ExampleGenerator<T> {
61+
const f = r.getFuzzer(d);
62+
if (!f) {
63+
throw new RangeError(`no fuzzer for ${d.name}`);
64+
}
65+
const fi = f.impl;
66+
return exampleGeneratorFromConcreteFuzzer(
67+
r,
68+
fi.type === 'fuzzer' ? fi : fi.func(d)
69+
);
70+
}

src/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ limitations under the License.
2121
Third-party dependencies may have their own licenses.
2222
*/
2323

24-
export function todo() {
25-
return true;
26-
}
24+
export * from './registry';
25+
export * from './fuzzer';
26+
export * from './core/';

0 commit comments

Comments
 (0)