Bug 1456972 - Add recording settings tests for perf recording panel; r=nchevobbe

MozReview-Commit-ID: CwSTtMpdyel

--HG--
extra : rebase_source : 1c6c1670f6a07e1f21be3e5bd484ae8e98405b99
This commit is contained in:
Greg Tatum 2018-04-25 17:13:35 -05:00
parent 624847ad88
commit 325287ece2
11 changed files with 396 additions and 37 deletions

View File

@ -21,51 +21,62 @@ const threadColumns = [
[
{
name: "GeckoMain",
id: "gecko-main",
title: "The main processes for both the parent process, and content processes"
},
{
name: "Compositor",
id: "compositor",
title: "Composites together different painted elements on the page."
},
{
name: "DOM Worker",
id: "dom-worker",
title: "This handle both web workers and service workers"
},
{
name: "Renderer",
id: "renderer",
title: "When WebRender is enabled, the thread that executes OpenGL calls"
},
],
[
{
name: "RenderBackend",
id: "render-backend",
title: "The WebRender RenderBackend thread"
},
{
name: "PaintWorker",
id: "paint-worker",
title: "When off-main-thread painting is enabled, the thread on which " +
"painting happens"
},
{
name: "StyleThread",
id: "style-thread",
title: "Style computation is split into multiple threads"
},
{
name: "Socket Thread",
id: "socket-thread",
title: "The thread where networking code runs any blocking socket calls"
},
],
[
{
name: "StreamTrans",
id: "stream-trans",
title: "TODO"
},
{
name: "ImgDecoder",
id: "img-decoder",
title: "Image decoding threads"
},
{
name: "DNS Resolver",
id: "dns-resolver",
title: "DNS resolution happens on this thread"
},
]
@ -227,7 +238,7 @@ class Settings extends PureComponent {
const { threads } = this.props;
return div(
{ className: "perf-settings-thread-column", key: index },
threadDisplay.map(({name, title}) => label(
threadDisplay.map(({name, title, id}) => label(
{
className: "perf-settings-checkbox-label",
key: name,
@ -235,6 +246,7 @@ class Settings extends PureComponent {
},
input({
className: "perf-settings-checkbox",
id: `perf-settings-thread-checkbox-${id}`,
type: "checkbox",
value: name,
checked: threads.includes(name),
@ -248,7 +260,13 @@ class Settings extends PureComponent {
_renderThreads() {
return details(
{ className: "perf-settings-details" },
summary({ className: "perf-settings-summary" }, "Threads:"),
summary(
{
className: "perf-settings-summary",
id: "perf-settings-threads-summary"
},
"Threads:"
),
// Contain the overflow of the slide down animation with the first div.
div(
{ className: "perf-settings-details-contents" },
@ -272,6 +290,7 @@ class Settings extends PureComponent {
div({}, "Add custom threads by name:"),
input({
className: "perf-settings-text-input",
id: "perf-settings-thread-text",
type: "text",
value: this.state.temporaryThreadText === null
? this.props.threads
@ -290,7 +309,13 @@ class Settings extends PureComponent {
_renderFeatures() {
return details(
{ className: "perf-settings-details" },
summary({ className: "perf-settings-summary" }, "Features:"),
summary(
{
className: "perf-settings-summary",
id: "perf-settings-features-summary"
},
"Features:"
),
div(
{ className: "perf-settings-details-contents" },
div(
@ -302,6 +327,7 @@ class Settings extends PureComponent {
},
input({
className: "perf-settings-checkbox",
id: `perf-settings-feature-checkbox-${value}`,
type: "checkbox",
value,
checked: this.props.features.includes(value),

View File

@ -2,6 +2,10 @@
support-files =
head.js
[test_perf-settings-entries.html]
[test_perf-settings-features.html]
[test_perf-settings-interval.html]
[test_perf-settings-threads.html]
[test_perf-state-01.html]
[test_perf-state-02.html]
[test_perf-state-03.html]

View File

@ -43,7 +43,7 @@ function addPerfTest(asyncTest) {
/**
* The Gecko Profiler is a rather heavy-handed component that uses a lot of resources.
* In order to get around that, and have quick component tests we provide a mock of
* the performance front. It also has a method called flushAsyncQueue() that will
* the performance front. It also has a method called _flushAsyncQueue() that will
* flush any queued async calls to deterministically run our tests.
*/
class MockPerfFront extends EventEmitter {
@ -51,6 +51,7 @@ class MockPerfFront extends EventEmitter {
super();
this._isActive = false;
this._asyncQueue = [];
this._startProfilerCalls = [];
// Tests can update these two values directly as needed.
this.mockIsSupported = true;
@ -83,7 +84,11 @@ class MockPerfFront extends EventEmitter {
};
}
flushAsyncQueue() {
/**
* This method is in the mock only, and it serves to flush out any pending async calls
* so that tests can properly wait on asynchronous behavior.
*/
_flushAsyncQueue() {
const pending = this._asyncQueue;
this._asyncQueue = [];
pending.forEach(fn => fn());
@ -91,7 +96,8 @@ class MockPerfFront extends EventEmitter {
return new Promise(resolve => setTimeout(resolve, 0));
}
startProfiler() {
startProfiler(settings) {
this._startProfilerCalls.push(settings);
this._isActive = true;
this.emit("profiler-started");
}
@ -130,6 +136,24 @@ Object.getOwnPropertyNames(perfDescription.methods).forEach(methodName => {
}
});
/**
* Set a React-friendly input value. Doing this the normal way doesn't work.
*
* See: https://github.com/facebook/react/issues/10135#issuecomment-314441175
*/
function setReactFriendlyInputValue(element, value) {
const valueSetter = Object.getOwnPropertyDescriptor(element, "value").set;
const prototype = Object.getPrototypeOf(element);
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, "value").set;
if (valueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value);
} else {
valueSetter.call(element, value);
}
element.dispatchEvent(new Event("input", { bubbles: true }));
}
/**
* This is a helper function to correctly mount the Perf component, and provide
* mocks where needed.
@ -144,23 +168,28 @@ function createPerfComponent() {
const actions = require("devtools/client/performance-new/store/actions");
const selectors = require("devtools/client/performance-new/store/selectors");
const perfFront = new MockPerfFront();
const perfFrontMock = new MockPerfFront();
const toolboxMock = {};
const store = createStore(reducers);
const container = document.querySelector("#container");
const receiveProfileCalls = [];
const recordingPreferencesCalls = [];
function receiveProfileMock(profile) {
receiveProfileCalls.push(profile);
}
const mountComponent = () => {
function recordingPreferencesMock(settings) {
recordingPreferencesCalls.push(settings);
}
function mountComponent() {
store.dispatch(actions.initializeStore({
toolbox: toolboxMock,
perfFront,
perfFront: perfFrontMock,
receiveProfile: receiveProfileMock,
recordingSettingsFromPreferences: selectors.getRecordingSettings(store.getState()),
setRecordingPreferences: () => {}
setRecordingPreferences: recordingPreferencesMock
}));
return ReactDOM.render(
@ -171,13 +200,24 @@ function createPerfComponent() {
),
container
);
};
}
/**
* The perf front is initially queried for the status of the profiler, flush
* those requests to have a fully initializated component.
*/
async function mountAndInitializeComponent() {
mountComponent();
await perfFrontMock._flushAsyncQueue();
}
// Provide a list of common values that may be needed during testing.
return {
receiveProfileCalls,
perfFront,
recordingPreferencesCalls,
perfFrontMock,
mountComponent,
mountAndInitializeComponent,
selectors,
store,
container,

View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<head>
<meta charset="utf-8">
<title>Perf component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<div id="container"></div>
<pre id="test">
<script src="head.js" type="application/javascript"></script>
<script type="application/javascript">
"use strict";
/**
* Test that the entries setting can be changed.
*/
addPerfTest(async () => {
const {
perfFrontMock,
mountAndInitializeComponent,
selectors,
getState,
recordingPreferencesCalls
} = createPerfComponent();
await mountAndInitializeComponent();
is(selectors.getEntries(getState()), 10000000,
"The entries starts out with the default");
is(recordingPreferencesCalls.length, 0,
"No calls have been made to set preferences");
const inputValue = 75;
const scaledValue = 20000000;
const input = document.querySelector("#perf-range-entries");
setReactFriendlyInputValue(input, inputValue);
is(selectors.getEntries(getState()), scaledValue,
"The entries was changed according to a logarithmic scale.");
is(recordingPreferencesCalls[0].entries, scaledValue,
"The preference was recorded.");
// Start the profiler by clicking the start button, and flushing the async
// calls out to the mock perf front.
document.querySelector("button").click();
await perfFrontMock._flushAsyncQueue();
is(perfFrontMock._startProfilerCalls.length, 1,
"Start profiler was called once");
is(perfFrontMock._startProfilerCalls[0].entries, scaledValue,
"Start profiler was called with the correct entries");
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,70 @@
<!DOCTYPE HTML>
<html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<head>
<meta charset="utf-8">
<title>Perf component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<div id="container"></div>
<pre id="test">
<script src="head.js" type="application/javascript"></script>
<script type="application/javascript">
"use strict";
/**
* Test that the features setting can be changed.
*/
addPerfTest(async () => {
const {
perfFrontMock,
mountAndInitializeComponent,
selectors,
getState,
recordingPreferencesCalls
} = createPerfComponent();
await mountAndInitializeComponent();
// Open up the features summary.
document.querySelector("#perf-settings-features-summary").click();
is(selectors.getFeatures(getState()).join(","), "js,stackwalk",
"The features starts out with the default");
is(recordingPreferencesCalls.length, 0,
"No calls have been made to set preferences");
// Click the "features checkbox.
document.querySelector("#perf-settings-feature-checkbox-js").click();
is(selectors.getFeatures(getState()).join(","), "stackwalk",
"The feature has been removed.");
is(recordingPreferencesCalls.length, 1,
"The preferences have been updated.");
is(recordingPreferencesCalls[0].features.join(","), "stackwalk",
"The preferences have been updated.");
// Enable a feature
document.querySelector("#perf-settings-feature-checkbox-leaf").click();
is(selectors.getFeatures(getState()).join(","), "leaf,stackwalk",
"Another feature was added");
// Start the profiler by clicking the start button, and flushing the async
// calls out to the mock perf front.
document.querySelector("button").click();
await perfFrontMock._flushAsyncQueue();
is(perfFrontMock._startProfilerCalls.length, 1,
"Start profiler was called once");
is(perfFrontMock._startProfilerCalls[0].features.join(","), "leaf,stackwalk",
"Start profiler was called with the correct features");
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<head>
<meta charset="utf-8">
<title>Perf component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<div id="container"></div>
<pre id="test">
<script src="head.js" type="application/javascript"></script>
<script type="application/javascript">
"use strict";
/**
* Test that the profiler can set the interval settings.
*/
addPerfTest(async () => {
const {
perfFrontMock,
mountAndInitializeComponent,
selectors,
getState,
recordingPreferencesCalls
} = createPerfComponent();
await mountAndInitializeComponent();
is(selectors.getInterval(getState()), 1,
"The interval starts out as 1");
is(recordingPreferencesCalls.length, 0,
"No calls have been made");
const inputValue = 75;
const scaledValue = 10;
const input = document.querySelector("#perf-range-interval");
setReactFriendlyInputValue(input, inputValue);
is(selectors.getInterval(getState()), scaledValue,
"The interval was changed according to a logarithmic scale.");
is(recordingPreferencesCalls[0].interval, scaledValue,
"The preference was recorded.");
// Start the profiler by clicking the start button, and flushing the async
// calls out to the mock perf front.
document.querySelector("button").click();
await perfFrontMock._flushAsyncQueue();
is(perfFrontMock._startProfilerCalls.length, 1,
"Start profiler was called once");
is(perfFrontMock._startProfilerCalls[0].interval, scaledValue,
"Start profiler was called with the correct interval");
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,95 @@
<!DOCTYPE HTML>
<html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<head>
<meta charset="utf-8">
<title>Perf component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<div id="container"></div>
<pre id="test">
<script src="head.js" type="application/javascript"></script>
<script type="application/javascript">
"use strict";
/**
* Test that the threads setting can be changed.
*/
addPerfTest(async () => {
const {
perfFrontMock,
mountAndInitializeComponent,
selectors,
getState,
recordingPreferencesCalls
} = createPerfComponent();
await mountAndInitializeComponent();
// Open up the threads summary.
document.querySelector("#perf-settings-threads-summary").click();
const threadTextEl = document.querySelector("#perf-settings-thread-text");
is(selectors.getThreads(getState()).join(","), "GeckoMain,Compositor",
"The threads starts out with the default");
is(threadTextEl.value, "GeckoMain,Compositor",
"The threads starts out with the default in the thread text input");
is(recordingPreferencesCalls.length, 0,
"No calls have been made to set preferences");
// Click the Compositor checkbox.
document.querySelector("#perf-settings-thread-checkbox-compositor").click();
is(selectors.getThreads(getState()).join(","), "GeckoMain",
"The threads have been updated");
is(threadTextEl.value, "GeckoMain",
"The threads have been updated in the thread text input");
is(recordingPreferencesCalls.length, 1,
"The preferences have been updated.");
is(recordingPreferencesCalls[0].threads.join(","), "GeckoMain",
"The preferences have been updated.");
// Enable a thread
document.querySelector("#perf-settings-thread-checkbox-dom-worker").click();
is(selectors.getThreads(getState()).join(","), "GeckoMain,DOM Worker",
"Another thread was added");
is(threadTextEl.value, "GeckoMain,DOM Worker",
"Another thread was in the thread text input");
// See the initial state of the checkbox
const styleThreadCheckbox = document.querySelector(
"#perf-settings-thread-checkbox-style-thread");
ok(!styleThreadCheckbox.checked,
"The style thread is not checked.");
// Set the input box directly
setReactFriendlyInputValue(threadTextEl, "GeckoMain,DOM Worker,StyleThread");
threadTextEl.dispatchEvent(new Event("blur", { bubbles: true }));
ok(styleThreadCheckbox.checked,
"The style thread is now checked.");
is(selectors.getThreads(getState()).join(","), "GeckoMain,DOM Worker,StyleThread",
"Another thread was added");
is(threadTextEl.value, "GeckoMain,DOM Worker,StyleThread",
"Another thread was in the thread text input");
// Start the profiler by clicking the start button, and flushing the async
// calls out to the mock perf front.
document.querySelector("button").click();
await perfFrontMock._flushAsyncQueue();
is(perfFrontMock._startProfilerCalls.length, 1,
"Start profiler was called once");
is(perfFrontMock._startProfilerCalls[0].threads.join(","),
"GeckoMain,DOM Worker,StyleThread",
"Start profiler was called with the correct threads");
});
</script>
</pre>
</body>
</html>

View File

@ -23,7 +23,7 @@
*/
addPerfTest(async () => {
const {
perfFront,
perfFrontMock,
getRecordingState,
receiveProfileCalls,
mountComponent,
@ -35,7 +35,7 @@
is(getRecordingState(), "not-yet-known",
"The component at first is in an unknown state.");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "available-to-record",
"After talking to the actor, we're ready to record.");
@ -45,7 +45,7 @@
is(getRecordingState(), "request-to-start-recording",
"Sent in a request to start recording.");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "recording",
"The actor has started its recording");
@ -54,10 +54,10 @@
"request-to-get-profile-and-stop-profiler",
"We have requested to stop the profiler.");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "available-to-record",
"The profiler is available to record again.");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(receiveProfileCalls.length, 1,
"The receiveProfile function was called once");
is(typeof receiveProfileCalls[0], "object", "Got a profile");

View File

@ -22,7 +22,7 @@
*/
addPerfTest(async () => {
const {
perfFront,
perfFrontMock,
getRecordingState,
mountComponent,
container
@ -31,15 +31,15 @@
ok(true, "Start the profiler before initiliazing the component, to simulate" +
"the profiler being controlled by another tool.");
perfFront.startProfiler();
await perfFront.flushAsyncQueue();
perfFrontMock.startProfiler();
await perfFrontMock._flushAsyncQueue();
mountComponent();
is(getRecordingState(), "not-yet-known",
"The component at first is in an unknown state.");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "other-is-recording",
"The profiler is not available to record.");
@ -49,7 +49,7 @@
is(getRecordingState(), "request-to-stop-profiler",
"We can request to stop the profiler.");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "available-to-record",
"The profiler is now available to record.");
});

View File

@ -22,7 +22,7 @@
*/
addPerfTest(async () => {
const {
perfFront,
perfFrontMock,
getRecordingState,
mountComponent,
getState,
@ -34,7 +34,7 @@
is(getRecordingState(), "not-yet-known",
"The component at first is in an unknown state.");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "available-to-record",
"After talking to the actor, we're ready to record.");
@ -42,7 +42,7 @@
is(getRecordingState(), "request-to-start-recording",
"Sent in a request to start recording.");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "recording",
"The actor has started its recording");
@ -50,8 +50,8 @@
ok(!selectors.getRecordingUnexpectedlyStopped(getState()),
"The profiler has not unexpectedly stopped.");
perfFront.stopProfilerAndDiscardProfile();
await perfFront.flushAsyncQueue();
perfFrontMock.stopProfilerAndDiscardProfile();
await perfFrontMock._flushAsyncQueue();
ok(selectors.getRecordingUnexpectedlyStopped(getState()),
"The profiler unexpectedly stopped.");

View File

@ -22,37 +22,37 @@
*/
addPerfTest(async () => {
const {
perfFront,
perfFrontMock,
getRecordingState,
mountComponent,
} = createPerfComponent();
perfFront.mockIsLocked = true;
perfFrontMock.mockIsLocked = true;
mountComponent();
is(getRecordingState(), "not-yet-known",
"The component at first is in an unknown state.");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "locked-by-private-browsing",
"After talking to the actor, it's locked for private browsing.");
perfFront.mockIsLocked = false;
perfFront.emit("profile-unlocked-from-private-browsing");
perfFrontMock.mockIsLocked = false;
perfFrontMock.emit("profile-unlocked-from-private-browsing");
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "available-to-record",
"After the profiler is unlocked, it's available to record.");
document.querySelector("button").click();
await perfFront.flushAsyncQueue();
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "recording",
"The actor has started its recording");
perfFront.mockIsLocked = true;
perfFront.emit("profile-locked-by-private-browsing");
await perfFront.flushAsyncQueue();
perfFrontMock.mockIsLocked = true;
perfFrontMock.emit("profile-locked-by-private-browsing");
await perfFrontMock._flushAsyncQueue();
is(getRecordingState(), "locked-by-private-browsing",
"The recording stops when going into private browsing mode.");
});