Added profiling chart data tests
Brian Vaughn committed
May 6, 2019 at 13:37 UTC
b25d996fc4d8d84afe00bbb852f7480ab4725fa6
4 files changed
+641
-7
src/__tests__/__snapshots__/profilingCharts-test.js.snap
new
+333
@@ -0,0 +1,333 @@
1
+// Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+exports[`profiling charts flamegraph chart should contain valid data: 0: CommitTree 1`] = `
4
+Object {
5
+ "nodes": Map {
6
+ 1 => Object {
7
+ "children": Array [
8
+ 2,
9
+ ],
10
+ "displayName": null,
11
+ "id": 1,
12
+ "key": null,
13
+ "parentID": 0,
14
+ "treeBaseDuration": 14,
15
+ },
16
+ 2 => Object {
17
+ "children": Array [
18
+ 3,
19
+ 4,
20
+ ],
21
+ "displayName": "Parent",
22
+ "id": 2,
23
+ "key": null,
24
+ "parentID": 1,
25
+ "treeBaseDuration": 14,
26
+ },
27
+ 3 => Object {
28
+ "children": Array [],
29
+ "displayName": "Memo(Child)",
30
+ "id": 3,
31
+ "key": "first",
32
+ "parentID": 2,
33
+ "treeBaseDuration": 2,
34
+ },
35
+ 4 => Object {
36
+ "children": Array [],
37
+ "displayName": "Memo(Child)",
38
+ "id": 4,
39
+ "key": "second",
40
+ "parentID": 2,
41
+ "treeBaseDuration": 2,
42
+ },
43
+ },
44
+ "rootID": 1,
45
+}
46
+`;
47
+
48
+exports[`profiling charts flamegraph chart should contain valid data: 0: FlamegraphChartData 1`] = `
49
+Object {
50
+ "baseDuration": 14,
51
+ "depth": 2,
52
+ "idToDepthMap": Map {
53
+ 2 => 1,
54
+ 4 => 2,
55
+ 3 => 2,
56
+ },
57
+ "maxSelfDuration": 10,
58
+ "rows": Array [
59
+ Array [
60
+ Object {
61
+ "actualDuration": 14,
62
+ "didRender": true,
63
+ "id": 2,
64
+ "label": "Parent (10.0ms of 14.0ms)",
65
+ "name": "Parent",
66
+ "offset": 0,
67
+ "selfDuration": 10,
68
+ "treeBaseDuration": 14,
69
+ },
70
+ ],
71
+ Array [
72
+ Object {
73
+ "actualDuration": 2,
74
+ "didRender": true,
75
+ "id": 4,
76
+ "label": "Memo(Child) key=\\"second\\" (2.0ms of 2.0ms)",
77
+ "name": "Memo(Child)",
78
+ "offset": 12,
79
+ "selfDuration": 2,
80
+ "treeBaseDuration": 2,
81
+ },
82
+ Object {
83
+ "actualDuration": 2,
84
+ "didRender": true,
85
+ "id": 3,
86
+ "label": "Memo(Child) key=\\"first\\" (2.0ms of 2.0ms)",
87
+ "name": "Memo(Child)",
88
+ "offset": 10,
89
+ "selfDuration": 2,
90
+ "treeBaseDuration": 2,
91
+ },
92
+ ],
93
+ ],
94
+}
95
+`;
96
+
97
+exports[`profiling charts flamegraph chart should contain valid data: 1: CommitTree 1`] = `
98
+Object {
99
+ "nodes": Map {
100
+ 1 => Object {
101
+ "children": Array [
102
+ 2,
103
+ ],
104
+ "displayName": null,
105
+ "id": 1,
106
+ "key": null,
107
+ "parentID": 0,
108
+ "treeBaseDuration": 14,
109
+ },
110
+ 2 => Object {
111
+ "children": Array [
112
+ 3,
113
+ 4,
114
+ ],
115
+ "displayName": "Parent",
116
+ "id": 2,
117
+ "key": null,
118
+ "parentID": 1,
119
+ "treeBaseDuration": 14,
120
+ },
121
+ 3 => Object {
122
+ "children": Array [],
123
+ "displayName": "Memo(Child)",
124
+ "id": 3,
125
+ "key": "first",
126
+ "parentID": 2,
127
+ "treeBaseDuration": 2,
128
+ },
129
+ 4 => Object {
130
+ "children": Array [],
131
+ "displayName": "Memo(Child)",
132
+ "id": 4,
133
+ "key": "second",
134
+ "parentID": 2,
135
+ "treeBaseDuration": 2,
136
+ },
137
+ },
138
+ "rootID": 1,
139
+}
140
+`;
141
+
142
+exports[`profiling charts flamegraph chart should contain valid data: 1: FlamegraphChartData 1`] = `
143
+Object {
144
+ "baseDuration": 14,
145
+ "depth": 2,
146
+ "idToDepthMap": Map {
147
+ 2 => 1,
148
+ 4 => 2,
149
+ 3 => 2,
150
+ },
151
+ "maxSelfDuration": 10,
152
+ "rows": Array [
153
+ Array [
154
+ Object {
155
+ "actualDuration": 10,
156
+ "didRender": true,
157
+ "id": 2,
158
+ "label": "Parent (10.0ms of 10.0ms)",
159
+ "name": "Parent",
160
+ "offset": 0,
161
+ "selfDuration": 10,
162
+ "treeBaseDuration": 14,
163
+ },
164
+ ],
165
+ Array [
166
+ Object {
167
+ "actualDuration": 0,
168
+ "didRender": false,
169
+ "id": 4,
170
+ "label": "Memo(Child) key=\\"second\\"",
171
+ "name": "Memo(Child)",
172
+ "offset": 12,
173
+ "selfDuration": 0,
174
+ "treeBaseDuration": 2,
175
+ },
176
+ Object {
177
+ "actualDuration": 0,
178
+ "didRender": false,
179
+ "id": 3,
180
+ "label": "Memo(Child) key=\\"first\\"",
181
+ "name": "Memo(Child)",
182
+ "offset": 10,
183
+ "selfDuration": 0,
184
+ "treeBaseDuration": 2,
185
+ },
186
+ ],
187
+ ],
188
+}
189
+`;
190
+
191
+exports[`profiling charts interactions should contain valid data: Interactions 1`] = `
192
+Object {
193
+ "lastInteractionTime": 24,
194
+ "maxCommitDuration": 14,
195
+}
196
+`;
197
+
198
+exports[`profiling charts interactions should contain valid data: Interactions 2`] = `
199
+Object {
200
+ "lastInteractionTime": 24,
201
+ "maxCommitDuration": 14,
202
+}
203
+`;
204
+
205
+exports[`profiling charts ranked chart should contain valid data: 0: CommitTree 1`] = `
206
+Object {
207
+ "nodes": Map {
208
+ 1 => Object {
209
+ "children": Array [
210
+ 2,
211
+ ],
212
+ "displayName": null,
213
+ "id": 1,
214
+ "key": null,
215
+ "parentID": 0,
216
+ "treeBaseDuration": 14,
217
+ },
218
+ 2 => Object {
219
+ "children": Array [
220
+ 3,
221
+ 4,
222
+ ],
223
+ "displayName": "Parent",
224
+ "id": 2,
225
+ "key": null,
226
+ "parentID": 1,
227
+ "treeBaseDuration": 14,
228
+ },
229
+ 3 => Object {
230
+ "children": Array [],
231
+ "displayName": "Memo(Child)",
232
+ "id": 3,
233
+ "key": "first",
234
+ "parentID": 2,
235
+ "treeBaseDuration": 2,
236
+ },
237
+ 4 => Object {
238
+ "children": Array [],
239
+ "displayName": "Memo(Child)",
240
+ "id": 4,
241
+ "key": "second",
242
+ "parentID": 2,
243
+ "treeBaseDuration": 2,
244
+ },
245
+ },
246
+ "rootID": 1,
247
+}
248
+`;
249
+
250
+exports[`profiling charts ranked chart should contain valid data: 0: RankedChartData 1`] = `
251
+Object {
252
+ "maxValue": 10,
253
+ "nodes": Array [
254
+ Object {
255
+ "id": 2,
256
+ "label": "Parent (10.0ms)",
257
+ "name": "Parent",
258
+ "value": 10,
259
+ },
260
+ Object {
261
+ "id": 3,
262
+ "label": "Memo(Child) key=\\"first\\" (2.0ms)",
263
+ "name": "Memo(Child)",
264
+ "value": 2,
265
+ },
266
+ Object {
267
+ "id": 4,
268
+ "label": "Memo(Child) key=\\"second\\" (2.0ms)",
269
+ "name": "Memo(Child)",
270
+ "value": 2,
271
+ },
272
+ ],
273
+}
274
+`;
275
+
276
+exports[`profiling charts ranked chart should contain valid data: 1: CommitTree 1`] = `
277
+Object {
278
+ "nodes": Map {
279
+ 1 => Object {
280
+ "children": Array [
281
+ 2,
282
+ ],
283
+ "displayName": null,
284
+ "id": 1,
285
+ "key": null,
286
+ "parentID": 0,
287
+ "treeBaseDuration": 14,
288
+ },
289
+ 2 => Object {
290
+ "children": Array [
291
+ 3,
292
+ 4,
293
+ ],
294
+ "displayName": "Parent",
295
+ "id": 2,
296
+ "key": null,
297
+ "parentID": 1,
298
+ "treeBaseDuration": 14,
299
+ },
300
+ 3 => Object {
301
+ "children": Array [],
302
+ "displayName": "Memo(Child)",
303
+ "id": 3,
304
+ "key": "first",
305
+ "parentID": 2,
306
+ "treeBaseDuration": 2,
307
+ },
308
+ 4 => Object {
309
+ "children": Array [],
310
+ "displayName": "Memo(Child)",
311
+ "id": 4,
312
+ "key": "second",
313
+ "parentID": 2,
314
+ "treeBaseDuration": 2,
315
+ },
316
+ },
317
+ "rootID": 1,
318
+}
319
+`;
320
+
321
+exports[`profiling charts ranked chart should contain valid data: 1: RankedChartData 1`] = `
322
+Object {
323
+ "maxValue": 10,
324
+ "nodes": Array [
325
+ Object {
326
+ "id": 2,
327
+ "label": "Parent (10.0ms)",
328
+ "name": "Parent",
329
+ "value": 10,
330
+ },
331
+ ],
332
+}
333
+`;
src/__tests__/profiling-test.js
+2
-4
@@ -1,13 +1,11 @@
1
// @flow
2
3
-import type React from 'react';
4
-import type ReactDOM from 'react-dom';
3
import typeof ReactTestRenderer from 'react-test-renderer';
4
import type Store from 'src/devtools/store';
5
6
describe('profiling', () => {
9
- let React: React;
10
- let ReactDOM: ReactDOM;
7
+ let React;
8
+ let ReactDOM;
9
let Scheduler;
10
let SchedulerTracing;
11
let TestRenderer: ReactTestRenderer;
src/__tests__/profilingCharts-test.js
new
+292
@@ -0,0 +1,292 @@
1
+// @flow
2
+
3
+import typeof TestRendererType from 'react-test-renderer';
4
+import type Store from 'src/devtools/store';
5
+
6
+describe('profiling charts', () => {
7
+ let React;
8
+ let ReactDOM;
9
+ let Scheduler;
10
+ let SchedulerTracing;
11
+ let TestRenderer: TestRendererType;
12
+ let store: Store;
13
+ let utils;
14
+
15
+ beforeEach(() => {
16
+ utils = require('./utils');
17
+ utils.beforeEachProfiling();
18
+
19
+ store = global.store;
20
+ store.collapseNodesByDefault = false;
21
+
22
+ React = require('react');
23
+ ReactDOM = require('react-dom');
24
+ Scheduler = require('scheduler');
25
+ SchedulerTracing = require('scheduler/tracing');
26
+ TestRenderer = utils.requireTestRenderer();
27
+ });
28
+
29
+ describe('flamegraph chart', () => {
30
+ it('should contain valid data', async done => {
31
+ const Parent = ({ count }) => {
32
+ Scheduler.advanceTime(10);
33
+ return (
34
+ <React.Fragment>
35
+ <Child key="first" />
36
+ <Child key="second" />
37
+ </React.Fragment>
38
+ );
39
+ };
40
+
41
+ // Memoize children to verify that chart doesn't include in the update.
42
+ const Child = React.memo(function Child() {
43
+ Scheduler.advanceTime(2);
44
+ return null;
45
+ });
46
+
47
+ const container = document.createElement('div');
48
+
49
+ utils.act(() => store.startProfiling());
50
+ utils.act(() =>
51
+ SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () =>
52
+ ReactDOM.render(<Parent />, container)
53
+ )
54
+ );
55
+ utils.act(() =>
56
+ SchedulerTracing.unstable_trace(
57
+ 'update',
58
+ Scheduler.unstable_now(),
59
+ () => ReactDOM.render(<Parent />, container)
60
+ )
61
+ );
62
+ utils.act(() => store.stopProfiling());
63
+
64
+ let suspenseResolved = false;
65
+
66
+ function Suspender({ commitIndex, rendererID, rootID }) {
67
+ const profilingSummary = store.profilingCache.ProfilingSummary.read({
68
+ rendererID,
69
+ rootID,
70
+ });
71
+ const commitDetails = store.profilingCache.CommitDetails.read({
72
+ commitIndex,
73
+ rendererID,
74
+ rootID,
75
+ });
76
+ suspenseResolved = true;
77
+ const commitTree = store.profilingCache.getCommitTree({
78
+ commitIndex,
79
+ profilingSummary,
80
+ });
81
+ const chartData = store.profilingCache.getFlamegraphChartData({
82
+ commitDetails,
83
+ commitIndex,
84
+ commitTree,
85
+ });
86
+ expect(commitTree).toMatchSnapshot(`${commitIndex}: CommitTree`);
87
+ expect(chartData).toMatchSnapshot(
88
+ `${commitIndex}: FlamegraphChartData`
89
+ );
90
+ return null;
91
+ }
92
+
93
+ const rendererID = utils.getRendererID();
94
+ const rootID = store.roots[0];
95
+
96
+ for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
97
+ suspenseResolved = false;
98
+
99
+ await utils.actSuspense(
100
+ () =>
101
+ TestRenderer.create(
102
+ <React.Suspense fallback={null}>
103
+ <Suspender
104
+ commitIndex={commitIndex}
105
+ rendererID={rendererID}
106
+ rootID={rootID}
107
+ />
108
+ </React.Suspense>
109
+ ),
110
+ 3
111
+ );
112
+
113
+ expect(suspenseResolved).toBe(true);
114
+ }
115
+
116
+ expect(suspenseResolved).toBe(true);
117
+
118
+ done();
119
+ });
120
+ });
121
+
122
+ describe('ranked chart', () => {
123
+ it('should contain valid data', async done => {
124
+ const Parent = ({ count }) => {
125
+ Scheduler.advanceTime(10);
126
+ return (
127
+ <React.Fragment>
128
+ <Child key="first" />
129
+ <Child key="second" />
130
+ </React.Fragment>
131
+ );
132
+ };
133
+
134
+ // Memoize children to verify that chart doesn't include in the update.
135
+ const Child = React.memo(function Child() {
136
+ Scheduler.advanceTime(2);
137
+ return null;
138
+ });
139
+
140
+ const container = document.createElement('div');
141
+
142
+ utils.act(() => store.startProfiling());
143
+ utils.act(() =>
144
+ SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () =>
145
+ ReactDOM.render(<Parent />, container)
146
+ )
147
+ );
148
+ utils.act(() =>
149
+ SchedulerTracing.unstable_trace(
150
+ 'update',
151
+ Scheduler.unstable_now(),
152
+ () => ReactDOM.render(<Parent />, container)
153
+ )
154
+ );
155
+ utils.act(() => store.stopProfiling());
156
+
157
+ let suspenseResolved = false;
158
+
159
+ function Suspender({ commitIndex, rendererID, rootID }) {
160
+ const profilingSummary = store.profilingCache.ProfilingSummary.read({
161
+ rendererID,
162
+ rootID,
163
+ });
164
+ const commitDetails = store.profilingCache.CommitDetails.read({
165
+ commitIndex,
166
+ rendererID,
167
+ rootID,
168
+ });
169
+ suspenseResolved = true;
170
+ const commitTree = store.profilingCache.getCommitTree({
171
+ commitIndex,
172
+ profilingSummary,
173
+ });
174
+ const chartData = store.profilingCache.getRankedChartData({
175
+ commitDetails,
176
+ commitIndex,
177
+ commitTree,
178
+ });
179
+ expect(commitTree).toMatchSnapshot(`${commitIndex}: CommitTree`);
180
+ expect(chartData).toMatchSnapshot(`${commitIndex}: RankedChartData`);
181
+ return null;
182
+ }
183
+
184
+ const rendererID = utils.getRendererID();
185
+ const rootID = store.roots[0];
186
+
187
+ for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
188
+ suspenseResolved = false;
189
+
190
+ await utils.actSuspense(
191
+ () =>
192
+ TestRenderer.create(
193
+ <React.Suspense fallback={null}>
194
+ <Suspender
195
+ commitIndex={commitIndex}
196
+ rendererID={rendererID}
197
+ rootID={rootID}
198
+ />
199
+ </React.Suspense>
200
+ ),
201
+ 3
202
+ );
203
+
204
+ expect(suspenseResolved).toBe(true);
205
+ }
206
+
207
+ done();
208
+ });
209
+ });
210
+
211
+ describe('interactions', () => {
212
+ it('should contain valid data', async done => {
213
+ const Parent = ({ count }) => {
214
+ Scheduler.advanceTime(10);
215
+ return (
216
+ <React.Fragment>
217
+ <Child key="first" />
218
+ <Child key="second" />
219
+ </React.Fragment>
220
+ );
221
+ };
222
+
223
+ // Memoize children to verify that chart doesn't include in the update.
224
+ const Child = React.memo(function Child() {
225
+ Scheduler.advanceTime(2);
226
+ return null;
227
+ });
228
+
229
+ const container = document.createElement('div');
230
+
231
+ utils.act(() => store.startProfiling());
232
+ utils.act(() =>
233
+ SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () =>
234
+ ReactDOM.render(<Parent />, container)
235
+ )
236
+ );
237
+ utils.act(() =>
238
+ SchedulerTracing.unstable_trace(
239
+ 'update',
240
+ Scheduler.unstable_now(),
241
+ () => ReactDOM.render(<Parent />, container)
242
+ )
243
+ );
244
+ utils.act(() => store.stopProfiling());
245
+
246
+ let suspenseResolved = false;
247
+
248
+ function Suspender({ commitIndex, rendererID, rootID }) {
249
+ const profilingSummary = store.profilingCache.ProfilingSummary.read({
250
+ rendererID,
251
+ rootID,
252
+ });
253
+ const interactions = store.profilingCache.Interactions.read({
254
+ rendererID,
255
+ rootID,
256
+ });
257
+ suspenseResolved = true;
258
+ const chartData = store.profilingCache.getInteractionsChartData({
259
+ interactions,
260
+ profilingSummary,
261
+ });
262
+ expect(chartData).toMatchSnapshot('Interactions');
263
+ return null;
264
+ }
265
+
266
+ const rendererID = utils.getRendererID();
267
+ const rootID = store.roots[0];
268
+
269
+ for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
270
+ suspenseResolved = false;
271
+
272
+ await utils.actSuspense(
273
+ () =>
274
+ TestRenderer.create(
275
+ <React.Suspense fallback={null}>
276
+ <Suspender
277
+ commitIndex={commitIndex}
278
+ rendererID={rendererID}
279
+ rootID={rootID}
280
+ />
281
+ </React.Suspense>
282
+ ),
283
+ 3
284
+ );
285
+
286
+ expect(suspenseResolved).toBe(true);
287
+ }
288
+
289
+ done();
290
+ });
291
+ });
292
+});
src/__tests__/utils.js
+14
-3
@@ -12,7 +12,10 @@ export function act(callback: Function): void {
12
jest.runAllTimers();
13
}
14
15
-export async function actSuspense(callback: Function): Promise<void> {
15
+export async function actSuspense(
16
+ callback: Function,
17
+ numTimesToFlush: number = 1
18
+): Promise<void> {
19
const TestUtils = require('react-dom/test-utils');
20
const Scheduler = require('scheduler');
21
@@ -24,8 +27,16 @@ export async function actSuspense(callback: Function): Promise<void> {
27
jest.runAllTimers();
28
});
29
27
- // Re-render after resolved promises
28
- Scheduler.flushAll();
30
+ // Run cascading microtasks and flush scheduled React work.
31
+ // Components that suspend multiple times will need to do this once per suspend operation.
32
+ // HACK Ideally the mock scheduler would provide an API to ask if there was outstanding work.
33
+ while (--numTimesToFlush >= 0) {
34
+ // $FlowFixMe Flow doens't know about "await act()" yet
35
+ await TestUtils.act(async () => {
36
+ jest.runAllTimers();
37
+ Scheduler.flushAll();
38
+ });
39
+ }
40
}
41
42
export function beforeEachProfiling(): void {