Skip to content

Commit

Permalink
Detect facebook#18657 thanks to fuzzing
Browse files Browse the repository at this point in the history
The current commit is totally work in progress but it already found back the issue by reporting the following counterexample:
```
[Scheduler`
      -> [task#2] sequence::Scheduling "8" with priority 3 resolved
      -> [task#1] promise::Request for "447b0ed" resolved with value "resolved 447b0ed!"`,"447b0ed",[{"priority":3,"text":"8"}],<function :: ["447b0ed"] => true, ["8"] => true>]
```
Reproduced by https://codesandbox.io/s/strange-frost-d4ujl?file=/src/App.js

Related to facebook#18669
  • Loading branch information
dubzzz committed Apr 23, 2020
1 parent 0b1df02 commit 2c1f191
Showing 1 changed file with 135 additions and 0 deletions.
135 changes: 135 additions & 0 deletions packages/react-reconciler/src/__tests__/ReactSuspense-test.property.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
let React;
let ReactFeatureFlags;
let ReactNoop;
let Scheduler;
let Suspense;
let fc;

const beforeEachAction = () => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
ReactFeatureFlags.enableSuspenseServerRenderer = true;
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
Suspense = React.Suspense;
fc = require('fast-check');
};

describe('ReactSuspense', () => {
beforeEach(beforeEachAction);

function Text({text}) {
return <span>{text}</span>;
}

function AsyncText({text, readOrThrow}) {
readOrThrow(text);
return <span>{text}</span>;
}

function flushAndYieldScheduler() {
Scheduler.unstable_flushAllWithoutAsserting();
Scheduler.unstable_clearYields();
}

it('should display components up-to the first unresolved one as resolved, next ones should be considered unresolved in "forward" mode', async () => {
await fc.assert(
fc
.asyncProperty(
fc.scheduler(),
fc.stringOf(fc.hexa()),
fc.array(
fc.record({
priority: fc.constantFrom(
Scheduler.unstable_ImmediatePriority,
Scheduler.unstable_UserBlockingPriority,
Scheduler.unstable_NormalPriority,
Scheduler.unstable_IdlePriority,
Scheduler.unstable_LowPriority,
),
text: fc.stringOf(fc.hexa()),
}),
),
fc.func(/*fc.boolean()*/ fc.constant(true)),
async (s, initialText, textUpdates, shouldTextResolve) => {
const cache = new Map();
const readOrThrow = text => {
if (cache.has(text)) {
const {promise, resolvedWith} = cache.get(text);
if (resolvedWith === null) throw promise;
if (resolvedWith.error) throw resolvedWith.error;
return text;
} else {
const promise = s.schedule(
shouldTextResolve(text)
? Promise.resolve(`resolved ${text}!`)
: Promise.reject(new Error(`rejected ${text}!`)),
`Request for ${JSON.stringify(text)}`,
);
const cachedValue = {promise, resolvedWith: null};
promise.then(
success => (cachedValue.resolvedWith = {success}),
error => (cachedValue.resolvedWith = {error}),
);
cache.set(text, cachedValue);
throw promise;
}
};

let setText;
function App() {
const [text, _setText] = React.useState(initialText);
setText = _setText;
return <AsyncText text={text} readOrThrow={readOrThrow} />;
}

ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<App />
</Suspense>,
);
flushAndYieldScheduler();
expect(ReactNoop).toMatchRenderedOutput(<span>Loading...</span>);

// Updates of texts will be scheduled in the order thaey appear in textUpdates
s.scheduleSequence(
textUpdates.map(update => {
return {
label: `Scheduling ${JSON.stringify(
update.text,
)} with priority ${update.priority}`,
builder: async () =>
Scheduler.unstable_runWithPriority(update.priority, () => {
setText(update.text);
}),
};
}),
);

while (s.count() !== 0) {
await ReactNoop.act(async () => {
await s.waitOne();
flushAndYieldScheduler();
});
}

const lastText =
textUpdates.length > 0
? textUpdates[textUpdates.length - 1].text
: initialText;
const success = shouldTextResolve(lastText);

if (success) {
expect(ReactNoop).toMatchRenderedOutput(<span>{lastText}</span>);
} else {
expect(ReactNoop).toMatchRenderedOutput(<span>Loading...</span>);
}
},
)
.beforeEach(beforeEachAction),
{endOnFailure: true, verbose: 2},
);
});
});

0 comments on commit 2c1f191

Please sign in to comment.