Skip to content

Commit

Permalink
Fuzz tester for context
Browse files Browse the repository at this point in the history
  • Loading branch information
acdlite committed Dec 12, 2017
1 parent 41d822e commit e3bd9fa
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"platform": "^1.1.0",
"prettier": "1.8.1",
"prop-types": "^15.6.0",
"random-seed": "^0.3.0",
"rimraf": "^2.6.1",
"rollup": "^0.51.7",
"rollup-plugin-babel": "^2.7.1",
Expand Down
211 changes: 210 additions & 1 deletion packages/react-reconciler/src/__tests__/ReactNewContext-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@

'use strict';

let React;
let React = require('React');
let ReactNoop;
let gen;

describe('ReactNewContext', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
gen = require('random-seed');
});

// function div(...children) {
Expand Down Expand Up @@ -303,4 +305,211 @@ describe('ReactNewContext', () => {
span('Result: 4'),
]);
});

describe('fuzz test', () => {
const contextKeys = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
const contexts = new Map(
contextKeys.map(key => {
const Context = React.createContext(0);
Context.displayName = 'Context' + key;
return [key, Context];
}),
);
const Fragment = React.Fragment;

const FLUSH_ALL = 'FLUSH_ALL';
function flushAll() {
return {
type: FLUSH_ALL,
toString() {
return `flushAll()`;
},
};
}

const FLUSH = 'FLUSH';
function flush(unitsOfWork) {
return {
type: FLUSH,
unitsOfWork,
toString() {
return `flush(${unitsOfWork})`;
},
};
}

const UPDATE = 'UPDATE';
function update(key, value) {
return {
type: UPDATE,
key,
value,
toString() {
return `update('${key}', ${value})`;
},
};
}

function randomInteger(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}

function randomAction() {
switch (randomInteger(0, 3)) {
case 0:
return flushAll();
case 1:
return flush(randomInteger(0, 500));
case 2:
const key = contextKeys[randomInteger(0, contextKeys.length)];
const value = randomInteger(1, 10);
return update(key, value);
default:
throw new Error('Switch statement should be exhaustive');
}
}

function randomActions(n) {
let actions = [];
for (let i = 0; i < n; i++) {
actions.push(randomAction());
}
return actions;
}

class ConsumerTree extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
if (this.props.depth >= this.props.maxDepth) {
return null;
}
const consumers = [0, 1, 2].map(i => {
const randomKey =
contextKeys[this.props.rand.intBetween(0, contextKeys.length - 1)];
const Context = contexts.get(randomKey);
return Context.consume(
value => (
<Fragment>
<span prop={`${randomKey}:${value}`} />
<ConsumerTree
rand={this.props.rand}
depth={this.props.depth + 1}
maxDepth={this.props.maxDepth}
/>
</Fragment>
),
i,
);
});
return consumers;
}
}

function Root(props) {
return contextKeys.reduceRight((children, key) => {
const Context = contexts.get(key);
const value = props.values[key];
return Context.provide(value, children);
}, <ConsumerTree rand={props.rand} depth={0} maxDepth={props.maxDepth} />);
}

const initialValues = contextKeys.reduce(
(result, key, i) => ({...result, [key]: i + 1}),
{},
);

function assertConsistentTree(expectedValues = {}) {
const children = ReactNoop.getChildren();
children.forEach(child => {
const text = child.prop;
const key = text[0];
const value = parseInt(text[2], 10);
const expectedValue = expectedValues[key];
if (expectedValue === undefined) {
// If an expected value was not explicitly passed to this function,
// use the first occurrence.
expectedValues[key] = value;
} else if (value !== expectedValue) {
throw new Error(
`Inconsistent value! Expected: ${key}:${expectedValue}. Actual: ${
text
}`,
);
}
});
}

function ContextSimulator(maxDepth) {
function simulate(seed, actions) {
const rand = gen.create(seed);
let finalExpectedValues = initialValues;
function updateRoot() {
ReactNoop.render(
<Root
maxDepth={maxDepth}
rand={rand}
values={finalExpectedValues}
/>,
);
}
updateRoot();

actions.forEach(action => {
switch (action.type) {
case FLUSH_ALL:
ReactNoop.flush();
break;
case FLUSH:
ReactNoop.flushUnitsOfWork(action.unitsOfWork);
break;
case UPDATE:
finalExpectedValues = {
...finalExpectedValues,
[action.key]: action.value,
};
updateRoot();
break;
default:
throw new Error('Switch statement should be exhaustive');
}
assertConsistentTree();
});

ReactNoop.flush();
assertConsistentTree(finalExpectedValues);
}

return {simulate};
}

it('hard-coded tests', () => {
const {simulate} = ContextSimulator(5);
simulate('randomSeed', [flush(3), update('A', 4)]);
});

it('generated tests', () => {
const {simulate} = ContextSimulator(5);

const LIMIT = 100;
for (let i = 0; i < LIMIT; i++) {
const seed = Math.random()
.toString(36)
.substr(2, 5);
const actions = randomActions(5);
try {
simulate(seed, actions);
} catch (error) {
console.error(`
Context fuzz tester error! Copy and paste the following line into the test suite:
simulate('${seed}', ${actions.join(', ')});
`);
throw error;
}
}
});
});
});
8 changes: 7 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3045,7 +3045,7 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
dependencies:
jsonify "~0.0.0"

json-stringify-safe@~5.0.1:
json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"

Expand Down Expand Up @@ -3811,6 +3811,12 @@ qs@~6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"

random-seed@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd"
dependencies:
json-stringify-safe "^5.0.1"

randomatic@^1.1.3:
version "1.1.6"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb"
Expand Down

0 comments on commit e3bd9fa

Please sign in to comment.