149 lines
4.8 KiB
JavaScript
149 lines
4.8 KiB
JavaScript
(function(global) {
|
|
const TEST_SAMPLE_INTERVAL = 10;
|
|
const ENSURE_SAMPLE_SPIN_WAIT_MS = 500;
|
|
|
|
function forceSample() {
|
|
// Spin for |TEST_SAMPLE_INTERVAL + 500|ms to ensure that a sample occurs
|
|
// before this function returns. As periodic sampling is enforced by a
|
|
// SHOULD clause, it is indeed testable.
|
|
//
|
|
// More reliable sampling will be handled in a future testdriver RFC
|
|
// (https://github.com/web-platform-tests/rfcs/pull/81).
|
|
for (const deadline = performance.now() + TEST_SAMPLE_INTERVAL +
|
|
ENSURE_SAMPLE_SPIN_WAIT_MS;
|
|
performance.now() < deadline;)
|
|
;
|
|
}
|
|
|
|
// Creates a new profile that captures the execution of when the given
|
|
// function calls the `sample` function passed to it.
|
|
async function profileFunction(func) {
|
|
const profiler = new Profiler({
|
|
sampleInterval: TEST_SAMPLE_INTERVAL,
|
|
maxBufferSize: Number.MAX_SAFE_INTEGER,
|
|
});
|
|
|
|
func(() => forceSample());
|
|
|
|
const trace = await profiler.stop();
|
|
|
|
// Sanity check ensuring that we captured a sample.
|
|
assert_greater_than(trace.resources.length, 0);
|
|
assert_greater_than(trace.frames.length, 0);
|
|
assert_greater_than(trace.stacks.length, 0);
|
|
assert_greater_than(trace.samples.length, 0);
|
|
|
|
return trace;
|
|
}
|
|
|
|
async function testFunction(func, frame) {
|
|
const trace = await profileFunction(func);
|
|
assert_true(containsFrame(trace, frame), 'trace contains frame');
|
|
}
|
|
|
|
function substackMatches(trace, stackId, expectedStack) {
|
|
if (expectedStack.length === 0) {
|
|
return true;
|
|
}
|
|
if (stackId === undefined) {
|
|
return false;
|
|
}
|
|
|
|
const stackElem = trace.stacks[stackId];
|
|
const expectedFrame = expectedStack[0];
|
|
|
|
if (!frameMatches(trace.frames[stackElem.frameId], expectedFrame)) {
|
|
return false;
|
|
}
|
|
return substackMatches(trace, stackElem.parentId, expectedStack.slice(1));
|
|
}
|
|
|
|
// Returns true if the trace contains a frame matching the given specification.
|
|
// We define a "match" as follows: a frame A matches an expectation E if (and
|
|
// only if) for each field of E, A has the same value.
|
|
function containsFrame(trace, expectedFrame) {
|
|
return trace.frames.find(frame => {
|
|
return frameMatches(frame, expectedFrame);
|
|
}) !== undefined;
|
|
}
|
|
|
|
// Returns true if a trace contains a substack in one of its samples, ordered
|
|
// leaf to root.
|
|
function containsSubstack(trace, expectedStack) {
|
|
return trace.samples.find(sample => {
|
|
let stackId = sample.stackId;
|
|
while (stackId !== undefined) {
|
|
if (substackMatches(trace, stackId, expectedStack)) {
|
|
return true;
|
|
}
|
|
stackId = trace.stacks[stackId].parentId;
|
|
}
|
|
return false;
|
|
}) !== undefined;
|
|
}
|
|
|
|
function containsResource(trace, expectedResource) {
|
|
return trace.resources.includes(expectedResource);
|
|
}
|
|
|
|
// Returns true if a trace contains a sample matching the given specification.
|
|
// We define a "match" as follows: a sample A matches an expectation E if (and
|
|
// only if) for each field of E, A has the same value.
|
|
function containsSample(trace, expectedSample) {
|
|
return trace.samples.find(sample => {
|
|
return sampleMatches(sample, expectedSample);
|
|
}) !== undefined;
|
|
}
|
|
|
|
// Compares each set field of `expected` against the given frame `actual`.
|
|
function sampleMatches(actual, expected) {
|
|
return (expected.timestamp === undefined ||
|
|
expected.timestamp === actual.timestamp) &&
|
|
(expected.stackId === undefined ||
|
|
expected.stackId === actual.stackId) &&
|
|
(expected.marker === undefined || expected.marker === actual.marker);
|
|
}
|
|
|
|
// Compares each set field of `expected` against the given frame `actual`.
|
|
function frameMatches(actual, expected) {
|
|
return (expected.name === undefined || expected.name === actual.name) &&
|
|
(expected.resourceId === undefined || expected.resourceId === actual.resourceId) &&
|
|
(expected.line === undefined || expected.line === actual.line) &&
|
|
(expected.column === undefined || expected.column === actual.column);
|
|
}
|
|
|
|
function forceSampleFrame(frame) {
|
|
const channel = new MessageChannel();
|
|
const replyPromise = new Promise(res => {
|
|
channel.port1.onmessage = res;
|
|
});
|
|
frame.postMessage('', '*', [channel.port2]);
|
|
return replyPromise;
|
|
}
|
|
|
|
window.addEventListener('message', message => {
|
|
// Force sample in response to messages received.
|
|
(function sampleFromMessage() {
|
|
ProfileUtils.forceSample();
|
|
message.ports[0].postMessage('');
|
|
})();
|
|
});
|
|
|
|
global.ProfileUtils = {
|
|
// Capturing
|
|
profileFunction,
|
|
forceSample,
|
|
|
|
// Containment checks
|
|
containsFrame,
|
|
containsSubstack,
|
|
containsResource,
|
|
containsSample,
|
|
|
|
// Cross-frame sampling
|
|
forceSampleFrame,
|
|
|
|
// Assertions
|
|
testFunction,
|
|
};
|
|
})(this);
|