Experimenting with profiler tests
Brian Vaughn committed
May 3, 2019 at 15:01 UTC
f7c8e3a05c9dd4f32891f563efa4307dc1333f5a
6 files changed
+217
-24
package.json
+2
@@ -23,6 +23,7 @@
23
"<rootDir>/src/__tests__/setupTests"
24
],
25
"snapshotSerializers": [
26
+ "<rootDir>/src/__tests__/profilingSummarySerializer",
27
"<rootDir>/src/__tests__/storeSerializer"
28
],
29
"testMatch": [
@@ -134,6 +135,7 @@
135
"react-color": "^2.11.7",
136
"react-dom": "0.0.0-fb28e9048",
137
"react-is": "0.0.0-fb28e9048",
138
+ "react-test-renderer": "0.0.0-fb28e9048",
139
"react-virtualized-auto-sizer": "^1.0.2",
140
"react-window": "^1.8.0",
141
"request-promise": "^4.2.4",
src/__tests__/__snapshots__/profiler-test.js.snap
+92
-13
@@ -1,31 +1,110 @@
1
// Jest Snapshot v1, https://goo.gl/fbAQLP
2
3
-exports[`Profiler should start and stop profiling, handle root unmounting: 1: mount 1`] = `
3
+exports[`Profiler should clean up after a root has been unmounted: 1: mount 1`] = `
4
[root]
5
- ▸ <Parent key="A">
5
+ ▾ <Parent key="A">
6
+ <Child key="0">
7
+ <Child key="1">
8
+ <Child key="2">
9
[root]
7
- ▸ <Parent key="B">
10
+ ▾ <Parent key="B">
11
+ <Child key="0">
12
+ <Child key="1">
13
`;
14
10
-exports[`Profiler should start and stop profiling, handle root unmounting: 2: profiling started 1`] = `
15
+exports[`Profiler should clean up after a root has been unmounted: 2: profiling started 1`] = `
16
[root]
12
- ▸ <Parent key="A">
17
+ ▾ <Parent key="A">
18
+ <Child key="0">
19
+ <Child key="1">
20
+ <Child key="2">
21
[root]
14
- ▸ <Parent key="B">
22
+ ▾ <Parent key="B">
23
+ <Child key="0">
24
+ <Child key="1">
25
`;
26
17
-exports[`Profiler should start and stop profiling, handle root unmounting: 3: update 1`] = `
27
+exports[`Profiler should clean up after a root has been unmounted: 3: update 1`] = `
28
[root]
19
- ▸ <Parent key="A">
29
+ ▾ <Parent key="A">
30
+ <Child key="0">
31
+ <Child key="1">
32
+ <Child key="2">
33
+ <Child key="3">
34
[root]
21
- ▸ <Parent key="B">
35
+ ▾ <Parent key="B">
36
+ <Child key="0">
37
`;
38
24
-exports[`Profiler should start and stop profiling, handle root unmounting: 4: unmount B 1`] = `
39
+exports[`Profiler should clean up after a root has been unmounted: 4: unmount B 1`] = `
40
[root]
26
- ▸ <Parent key="A">
41
+ ▾ <Parent key="A">
42
+ <Child key="0">
43
+ <Child key="1">
44
+ <Child key="2">
45
+ <Child key="3">
46
`;
47
29
-exports[`Profiler should start and stop profiling, handle root unmounting: 5: unmount A 1`] = ``;
48
+exports[`Profiler should clean up after a root has been unmounted: 5: unmount A 1`] = ``;
49
31
-exports[`Profiler should start and stop profiling, handle root unmounting: 6: profiling stopped 1`] = ``;
50
+exports[`Profiler should clean up after a root has been unmounted: 6: profiling stopped 1`] = ``;
51
+
52
+exports[`Profiler should collect basic profiling metrics: 1: mount 1`] = `
53
+[root]
54
+ ▾ <Parent key="A">
55
+ <Child key="0">
56
+ <Child key="1">
57
+`;
58
+
59
+exports[`Profiler should collect basic profiling metrics: 2: add child 1`] = `
60
+[root]
61
+ ▾ <Parent key="A">
62
+ <Child key="0">
63
+ <Child key="1">
64
+ <Child key="2">
65
+`;
66
+
67
+exports[`Profiler should collect basic profiling metrics: 3: remove children 1`] = `
68
+[root]
69
+ ▾ <Parent key="A">
70
+ <Child key="0">
71
+`;
72
+
73
+exports[`Profiler should collect basic profiling metrics: 4: profiling stopped 1`] = `
74
+[root]
75
+ ▾ <Parent key="A">
76
+ <Child key="0">
77
+`;
78
+
79
+exports[`Profiler should collect basic profiling metrics: ProfilingSummary 1`] = `
80
+{
81
+ "rootID": 1,
82
+ "commitDurations": [
83
+ 0,
84
+ 1
85
+ ],
86
+ "commitTimes": [
87
+ 0,
88
+ 1
89
+ ],
90
+ "initialTreeBaseDurations": [
91
+ [
92
+ 1,
93
+ 0
94
+ ],
95
+ [
96
+ 2,
97
+ 1
98
+ ],
99
+ [
100
+ 3,
101
+ 2
102
+ ],
103
+ [
104
+ 4,
105
+ 3
106
+ ]
107
+ ],
108
+ "interactionCount": 0
109
+}
110
+`;
src/__tests__/profiler-test.js
+78
-7
@@ -3,7 +3,9 @@
3
describe('Profiler', () => {
4
let React;
5
let ReactDOM;
6
+ let TestRenderer;
7
let TestUtils;
8
+ let agent;
9
let store;
10
11
const act = (callback: Function) => {
@@ -13,15 +15,88 @@ describe('Profiler', () => {
15
jest.runAllTimers(); // Flush Bridge operations
16
};
17
18
+ const renderAndResolve = async (root, element) => {
19
+ // $FlowFixMe Flow doens't know about "await act()" yet
20
+ await TestUtils.act(async () => {
21
+ root.update(element);
22
+
23
+ // Resolve pending suspense promises
24
+ jest.runAllTimers();
25
+ });
26
+
27
+ // Re-render after resolved promises
28
+ jest.runAllTimers();
29
+ };
30
+
31
beforeEach(() => {
32
+ agent = global.agent;
33
store = global.store;
34
+ store.collapseNodesByDefault = false;
35
36
React = require('react');
37
ReactDOM = require('react-dom');
38
TestUtils = require('react-dom/test-utils');
39
+
40
+ // Hide the hook before requiring TestRenderer, so we don't end up with a loop.
41
+ const hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
42
+ delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
43
+ TestRenderer = require('react-test-renderer');
44
+ global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;
45
+ });
46
+
47
+ it('should collect basic profiling metrics', async done => {
48
+ const Parent = ({ count }) =>
49
+ new Array(count).fill(true).map((_, index) => <Child key={index} />);
50
+ const Child = () => {
51
+ jest.advanceTimersByTime(1);
52
+ return null;
53
+ };
54
+
55
+ const container = document.createElement('div');
56
+
57
+ act(() => ReactDOM.render(<Parent key="A" count={2} />, container));
58
+ expect(store).toMatchSnapshot('1: mount');
59
+
60
+ act(() => store.startProfiling());
61
+
62
+ act(() => ReactDOM.render(<Parent key="A" count={3} />, container));
63
+ expect(store).toMatchSnapshot('2: add child');
64
+
65
+ act(() => ReactDOM.render(<Parent key="A" count={1} />, container));
66
+ expect(store).toMatchSnapshot('3: remove children');
67
+
68
+ act(() => store.stopProfiling());
69
+ expect(store).toMatchSnapshot('4: profiling stopped');
70
+
71
+ let profilingSummary;
72
+ function Suspender({ rendererID, rootID }) {
73
+ profilingSummary = store.profilingCache.ProfilingSummary.read({
74
+ rendererID,
75
+ rootID,
76
+ });
77
+ return null;
78
+ }
79
+
80
+ // HACK There's only one renderer for this test
81
+ const rendererID = Object.keys(agent._rendererInterfaces)[0];
82
+ const rootID = store.roots[0];
83
+
84
+ let root = TestRenderer.create();
85
+ await renderAndResolve(
86
+ root,
87
+ <React.Suspense fallback={null}>
88
+ <Suspender rendererID={rendererID} rootID={rootID} />
89
+ </React.Suspense>
90
+ );
91
+
92
+ // HACK root.toTree() doesn't handle Suspense yet
93
+ // but Jest serializer wouldn't work with a JSON string
94
+ expect(profilingSummary).toMatchSnapshot('ProfilingSummary');
95
+
96
+ done();
97
});
98
24
- it('should start and stop profiling, handle root unmounting', async () => {
99
+ it('should clean up after a root has been unmounted', async () => {
100
const Parent = ({ count }) =>
101
new Array(count).fill(true).map((_, index) => <Child key={index} />);
102
const Child = () => <div>Hi!</div>;
@@ -35,9 +110,7 @@ describe('Profiler', () => {
110
});
111
expect(store).toMatchSnapshot('1: mount');
112
38
- act(() => {
39
- store.startProfiling();
40
- });
113
+ act(() => store.startProfiling());
114
expect(store).toMatchSnapshot('2: profiling started');
115
116
act(() => {
@@ -52,9 +125,7 @@ describe('Profiler', () => {
125
act(() => ReactDOM.unmountComponentAtNode(containerA));
126
expect(store).toMatchSnapshot('5: unmount A');
127
55
- act(() => {
56
- store.stopProfiling();
57
- });
128
+ act(() => store.stopProfiling());
129
expect(store).toMatchSnapshot('6: profiling stopped');
130
});
131
});
src/__tests__/profilingSummarySerializer.js
new
+29
@@ -0,0 +1,29 @@
1
+// test() is part of Jest's serializer API
2
+export function test(maybeProfilingSummary) {
3
+ return (
4
+ typeof maybeProfilingSummary === 'object' &&
5
+ maybeProfilingSummary !== null &&
6
+ typeof maybeProfilingSummary.rootID === 'number' &&
7
+ Array.isArray(maybeProfilingSummary.commitDurations) &&
8
+ Array.isArray(maybeProfilingSummary.commitTimes) &&
9
+ typeof maybeProfilingSummary.initialTreeBaseDurations === 'object' &&
10
+ maybeProfilingSummary.initialTreeBaseDurations !== null &&
11
+ typeof maybeProfilingSummary.interactionCount === 'number'
12
+ );
13
+}
14
+
15
+// print() is part of Jest's serializer API
16
+export function print(profilingSummary, serialize, indent) {
17
+ return JSON.stringify(
18
+ {
19
+ ...profilingSummary,
20
+ commitDurations: profilingSummary.commitDurations.map((_, i) => i),
21
+ commitTimes: profilingSummary.commitTimes.map((_, i) => i),
22
+ initialTreeBaseDurations: [
23
+ ...profilingSummary.initialTreeBaseDurations,
24
+ ].map(([id, _], index) => [id, index]),
25
+ },
26
+ null,
27
+ 2
28
+ );
29
+}
src/__tests__/setupTests.js
+6
-4
@@ -8,10 +8,6 @@ import { installHook } from 'src/hook';
8
9
const env = jasmine.getEnv();
10
env.beforeEach(() => {
11
- // It's important to reset modules between test runs;
12
- // Without this, ReactDOM won't re-inject itself into the new hook.
13
- jest.resetModules();
14
-
11
// Fake timers let us flush Bridge operations between setup and assertions.
12
jest.useFakeTimers();
13
@@ -55,4 +51,10 @@ env.beforeEach(() => {
51
});
52
env.afterEach(() => {
53
delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
54
+
55
+ // It's important to reset modules between test runs;
56
+ // Without this, ReactDOM won't re-inject itself into the new hook.
57
+ // It's also important to reset after tests, rather than before,
58
+ // so that we don't disconnect the ReactCurrentDispatcher ref.
59
+ jest.resetModules();
60
});
yarn.lock
+10
@@ -9837,6 +9837,16 @@ react-lifecycles-compat@^3.0.4:
9837
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
9838
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
9839
9840
+react-test-renderer@0.0.0-fb28e9048:
9841
+ version "0.0.0-fb28e9048"
9842
+ resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-0.0.0-fb28e9048.tgz#1a94c8d19cbb1ac98ab37c66c0b294e5be280c52"
9843
+ integrity sha512-WK/wQOh0v6+8Gbkurgb3he9hKoOKWueqQY+RFs2vM3u3vn7PMyYhzm/KkU75VvTG/GVciojNQBHBpekuvU5dYw==
9844
+ dependencies:
9845
+ object-assign "^4.1.1"
9846
+ prop-types "^15.6.2"
9847
+ react-is "0.0.0-fb28e9048"
9848
+ scheduler "0.0.0-fb28e9048"
9849
+
9850
react-virtualized-auto-sizer@^1.0.2:
9851
version "1.0.2"
9852
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"