(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);