Experimenting with profiler tests

This commit is contained in:
Brian Vaughn
2019-05-03 15:01:26 -07:00
parent a8ed95445c
commit f7c8e3a05c
6 changed files with 217 additions and 24 deletions
+2
View File
@@ -23,6 +23,7 @@
"<rootDir>/src/__tests__/setupTests"
],
"snapshotSerializers": [
"<rootDir>/src/__tests__/profilingSummarySerializer",
"<rootDir>/src/__tests__/storeSerializer"
],
"testMatch": [
@@ -134,6 +135,7 @@
"react-color": "^2.11.7",
"react-dom": "0.0.0-fb28e9048",
"react-is": "0.0.0-fb28e9048",
"react-test-renderer": "0.0.0-fb28e9048",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.0",
"request-promise": "^4.2.4",
@@ -1,31 +1,110 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Profiler should start and stop profiling, handle root unmounting: 1: mount 1`] = `
exports[`Profiler should clean up after a root has been unmounted: 1: mount 1`] = `
[root]
<Parent key="A">
<Parent key="A">
<Child key="0">
<Child key="1">
<Child key="2">
[root]
<Parent key="B">
<Parent key="B">
<Child key="0">
<Child key="1">
`;
exports[`Profiler should start and stop profiling, handle root unmounting: 2: profiling started 1`] = `
exports[`Profiler should clean up after a root has been unmounted: 2: profiling started 1`] = `
[root]
<Parent key="A">
<Parent key="A">
<Child key="0">
<Child key="1">
<Child key="2">
[root]
<Parent key="B">
<Parent key="B">
<Child key="0">
<Child key="1">
`;
exports[`Profiler should start and stop profiling, handle root unmounting: 3: update 1`] = `
exports[`Profiler should clean up after a root has been unmounted: 3: update 1`] = `
[root]
<Parent key="A">
<Parent key="A">
<Child key="0">
<Child key="1">
<Child key="2">
<Child key="3">
[root]
<Parent key="B">
<Parent key="B">
<Child key="0">
`;
exports[`Profiler should start and stop profiling, handle root unmounting: 4: unmount B 1`] = `
exports[`Profiler should clean up after a root has been unmounted: 4: unmount B 1`] = `
[root]
<Parent key="A">
<Parent key="A">
<Child key="0">
<Child key="1">
<Child key="2">
<Child key="3">
`;
exports[`Profiler should start and stop profiling, handle root unmounting: 5: unmount A 1`] = ``;
exports[`Profiler should clean up after a root has been unmounted: 5: unmount A 1`] = ``;
exports[`Profiler should start and stop profiling, handle root unmounting: 6: profiling stopped 1`] = ``;
exports[`Profiler should clean up after a root has been unmounted: 6: profiling stopped 1`] = ``;
exports[`Profiler should collect basic profiling metrics: 1: mount 1`] = `
[root]
▾ <Parent key="A">
<Child key="0">
<Child key="1">
`;
exports[`Profiler should collect basic profiling metrics: 2: add child 1`] = `
[root]
▾ <Parent key="A">
<Child key="0">
<Child key="1">
<Child key="2">
`;
exports[`Profiler should collect basic profiling metrics: 3: remove children 1`] = `
[root]
▾ <Parent key="A">
<Child key="0">
`;
exports[`Profiler should collect basic profiling metrics: 4: profiling stopped 1`] = `
[root]
▾ <Parent key="A">
<Child key="0">
`;
exports[`Profiler should collect basic profiling metrics: ProfilingSummary 1`] = `
{
"rootID": 1,
"commitDurations": [
0,
1
],
"commitTimes": [
0,
1
],
"initialTreeBaseDurations": [
[
1,
0
],
[
2,
1
],
[
3,
2
],
[
4,
3
]
],
"interactionCount": 0
}
`;
+78 -7
View File
@@ -3,7 +3,9 @@
describe('Profiler', () => {
let React;
let ReactDOM;
let TestRenderer;
let TestUtils;
let agent;
let store;
const act = (callback: Function) => {
@@ -13,15 +15,88 @@ describe('Profiler', () => {
jest.runAllTimers(); // Flush Bridge operations
};
const renderAndResolve = async (root, element) => {
// $FlowFixMe Flow doens't know about "await act()" yet
await TestUtils.act(async () => {
root.update(element);
// Resolve pending suspense promises
jest.runAllTimers();
});
// Re-render after resolved promises
jest.runAllTimers();
};
beforeEach(() => {
agent = global.agent;
store = global.store;
store.collapseNodesByDefault = false;
React = require('react');
ReactDOM = require('react-dom');
TestUtils = require('react-dom/test-utils');
// Hide the hook before requiring TestRenderer, so we don't end up with a loop.
const hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
TestRenderer = require('react-test-renderer');
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;
});
it('should start and stop profiling, handle root unmounting', async () => {
it('should collect basic profiling metrics', async done => {
const Parent = ({ count }) =>
new Array(count).fill(true).map((_, index) => <Child key={index} />);
const Child = () => {
jest.advanceTimersByTime(1);
return null;
};
const container = document.createElement('div');
act(() => ReactDOM.render(<Parent key="A" count={2} />, container));
expect(store).toMatchSnapshot('1: mount');
act(() => store.startProfiling());
act(() => ReactDOM.render(<Parent key="A" count={3} />, container));
expect(store).toMatchSnapshot('2: add child');
act(() => ReactDOM.render(<Parent key="A" count={1} />, container));
expect(store).toMatchSnapshot('3: remove children');
act(() => store.stopProfiling());
expect(store).toMatchSnapshot('4: profiling stopped');
let profilingSummary;
function Suspender({ rendererID, rootID }) {
profilingSummary = store.profilingCache.ProfilingSummary.read({
rendererID,
rootID,
});
return null;
}
// HACK There's only one renderer for this test
const rendererID = Object.keys(agent._rendererInterfaces)[0];
const rootID = store.roots[0];
let root = TestRenderer.create();
await renderAndResolve(
root,
<React.Suspense fallback={null}>
<Suspender rendererID={rendererID} rootID={rootID} />
</React.Suspense>
);
// HACK root.toTree() doesn't handle Suspense yet
// but Jest serializer wouldn't work with a JSON string
expect(profilingSummary).toMatchSnapshot('ProfilingSummary');
done();
});
it('should clean up after a root has been unmounted', async () => {
const Parent = ({ count }) =>
new Array(count).fill(true).map((_, index) => <Child key={index} />);
const Child = () => <div>Hi!</div>;
@@ -35,9 +110,7 @@ describe('Profiler', () => {
});
expect(store).toMatchSnapshot('1: mount');
act(() => {
store.startProfiling();
});
act(() => store.startProfiling());
expect(store).toMatchSnapshot('2: profiling started');
act(() => {
@@ -52,9 +125,7 @@ describe('Profiler', () => {
act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store).toMatchSnapshot('5: unmount A');
act(() => {
store.stopProfiling();
});
act(() => store.stopProfiling());
expect(store).toMatchSnapshot('6: profiling stopped');
});
});
@@ -0,0 +1,29 @@
// test() is part of Jest's serializer API
export function test(maybeProfilingSummary) {
return (
typeof maybeProfilingSummary === 'object' &&
maybeProfilingSummary !== null &&
typeof maybeProfilingSummary.rootID === 'number' &&
Array.isArray(maybeProfilingSummary.commitDurations) &&
Array.isArray(maybeProfilingSummary.commitTimes) &&
typeof maybeProfilingSummary.initialTreeBaseDurations === 'object' &&
maybeProfilingSummary.initialTreeBaseDurations !== null &&
typeof maybeProfilingSummary.interactionCount === 'number'
);
}
// print() is part of Jest's serializer API
export function print(profilingSummary, serialize, indent) {
return JSON.stringify(
{
...profilingSummary,
commitDurations: profilingSummary.commitDurations.map((_, i) => i),
commitTimes: profilingSummary.commitTimes.map((_, i) => i),
initialTreeBaseDurations: [
...profilingSummary.initialTreeBaseDurations,
].map(([id, _], index) => [id, index]),
},
null,
2
);
}
+6 -4
View File
@@ -8,10 +8,6 @@ import { installHook } from 'src/hook';
const env = jasmine.getEnv();
env.beforeEach(() => {
// It's important to reset modules between test runs;
// Without this, ReactDOM won't re-inject itself into the new hook.
jest.resetModules();
// Fake timers let us flush Bridge operations between setup and assertions.
jest.useFakeTimers();
@@ -55,4 +51,10 @@ env.beforeEach(() => {
});
env.afterEach(() => {
delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
// It's important to reset modules between test runs;
// Without this, ReactDOM won't re-inject itself into the new hook.
// It's also important to reset after tests, rather than before,
// so that we don't disconnect the ReactCurrentDispatcher ref.
jest.resetModules();
});
+10
View File
@@ -9837,6 +9837,16 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-test-renderer@0.0.0-fb28e9048:
version "0.0.0-fb28e9048"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-0.0.0-fb28e9048.tgz#1a94c8d19cbb1ac98ab37c66c0b294e5be280c52"
integrity sha512-WK/wQOh0v6+8Gbkurgb3he9hKoOKWueqQY+RFs2vM3u3vn7PMyYhzm/KkU75VvTG/GVciojNQBHBpekuvU5dYw==
dependencies:
object-assign "^4.1.1"
prop-types "^15.6.2"
react-is "0.0.0-fb28e9048"
scheduler "0.0.0-fb28e9048"
react-virtualized-auto-sizer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"