| (function(global) { |
| const TEST_SAMPLE_INTERVAL = 10; |
| |
| function forceSample() { |
| // Spin for |TEST_SAMPLE_INTERVAL * 4| 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 * 4; 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, |
| }); |
| |
| 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); |
| } |
| |
| // 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, |
| |
| // Cross-frame sampling |
| forceSampleFrame, |
| |
| // Assertions |
| testFunction, |
| }; |
| })(this); |