Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
"use strict";
// Load the tracker in a dedicated loader using invisibleToDebugger and freshCompartment
// so that it can inspect any other module/compartment, even DevTools, chrome,
// and this script!
const { DevToolsLoader } = ChromeUtils.importESModule(
);
const loader = new DevToolsLoader({
invisibleToDebugger: true,
freshCompartment: true,
});
const { allocationTracker } = loader.require(
);
const TrackedObjects = loader.require(
);
// This test record multiple times complete heap snapshot,
// so that it can take a little bit to complete.
requestLongerTimeout(2);
add_task(async function () {
// Use a sandbox to allocate test javascript object in order to avoid any
// external noise
const global = Cu.Sandbox("http://example.com");
const tracker = allocationTracker({ watchGlobal: global });
const before = tracker.stillAllocatedObjects();
/* eslint-disable no-undef */
// This will allocation 1001 objects. The array and 1000 elements in it.
Cu.evalInSandbox(
"let list; new " +
function () {
list = [];
for (let i = 0; i < 1000; i++) {
list.push({});
}
},
global,
undefined,
"test-file.js",
1,
/* enforceFilenameRestrictions */ false
);
/* eslint-enable no-undef */
const allocations = tracker.countAllocations();
Assert.greaterOrEqual(
allocations,
1001,
`At least 1001 objects are reported as created (${allocations})`
);
// Uncomment this and comment the call to `countAllocations` to debug the allocations.
// The call to `countAllocations` will reset the allocation record.
// tracker.logAllocationSites();
const afterCreation = tracker.stillAllocatedObjects();
is(
afterCreation.objectsWithStack - before.objectsWithStack,
1001,
"We got exactly the expected number of objects recorded with an allocation site"
);
Assert.greater(
afterCreation.objectsWithStack,
before.objectsWithStack,
"We got some random number of objects without an allocation site"
);
Cu.evalInSandbox(
"list = null;",
global,
undefined,
"test-file.js",
7,
/* enforceFilenameRestrictions */ false
);
Cu.forceGC();
Cu.forceCC();
const afterGC = tracker.stillAllocatedObjects();
is(
afterCreation.objectsWithStack - afterGC.objectsWithStack,
1001,
"All the expected objects were reported freed in the count with allocation sites"
);
Assert.less(
afterGC.objectsWithoutStack,
afterCreation.objectsWithoutStack,
"And we released some random number of objects without an allocation site"
);
tracker.stop();
});
add_task(async function () {
const leaked = {};
TrackedObjects.track(leaked);
let transient = {};
TrackedObjects.track(transient);
is(TrackedObjects.getAllNodeIds().length, 2, "The two objects are reported");
info("Free the transient object");
transient = null;
Cu.forceGC();
is(
TrackedObjects.getAllNodeIds().length,
1,
"We now only have the leaked object"
);
TrackedObjects.clear();
});
add_task(async function () {
info("Test start and stop recording without any debug mode");
const tracker = allocationTracker({ watchDevToolsGlobals: true });
await tracker.startRecordingAllocations();
await tracker.stopRecordingAllocations();
tracker.stop();
});
add_task(async function () {
info("Test start and stop recording with 'allocations' debug mode");
const tracker = allocationTracker({ watchDevToolsGlobals: true });
await tracker.startRecordingAllocations("allocations");
await tracker.stopRecordingAllocations("allocations");
tracker.stop();
});
add_task(async function () {
info("Test start and stop recording with 'leaks' debug mode");
const tracker = allocationTracker({ watchDevToolsGlobals: true });
await tracker.startRecordingAllocations("leaks");
await tracker.stopRecordingAllocations("leaks");
tracker.stop();
});
add_task(async function () {
info("Test start and stop recording with tracked objects");
const leaked = {};
TrackedObjects.track(leaked);
const tracker = allocationTracker({ watchAllGlobals: true });
await tracker.startRecordingAllocations();
await tracker.stopRecordingAllocations();
tracker.stop();
TrackedObjects.clear();
});
add_task(async function () {
info("Test start and stop recording with tracked objects");
const sandbox = Cu.Sandbox(window);
const tracker = allocationTracker({ watchGlobal: sandbox });
await tracker.startRecordingAllocations("leaks");
Cu.evalInSandbox("this.foo = {};", sandbox, null, "sandbox.js", 1);
const record = await tracker.stopRecordingAllocations("leaks");
is(
record.objectsWithStack,
1,
"We get only one leaked objects, the foo object of the sandbox."
);
Assert.greater(
record.objectsWithoutStack,
10,
"We get an handful of objects without stacks. Most likely created by Memory API itself."
);
is(
record.leaks.length,
2,
"We get the one leak and the objects with missing stacks"
);
is(
record.leaks[0].src,
"UNKNOWN",
"First item is the objects with missing stacks"
);
// In theory the two following values should be equal,
// but they aren't always because of some dark matter around objects with missing stacks.
// `count` is computed out of `takeCensus`, while `objectsWithoutStack` uses `drainAllocationsLog`
// While the first go through the current GC graph, the second is a record of allocations over time,
// this probably explain why there is some subtle difference
Assert.lessOrEqual(
record.leaks[0].count,
record.objectsWithoutStack,
"For now, the leak report intermittently assume there is less leaked objects than the summary"
);
is(record.leaks[1].src, "sandbox.js", "Second item if about our 'foo' leak");
is(record.leaks[1].count, 1, "We leak one object on this file");
is(record.leaks[1].lines.length, 1, "We leak from only one line");
is(record.leaks[1].lines[0], "1: 1", "On first line, we leak one object");
tracker.stop();
TrackedObjects.clear();
});
add_task(async function () {
info("Test that transient globals are not leaked");
const tracker = allocationTracker({ watchAllGlobals: true });
let sandboxBefore = Cu.Sandbox(window);
// We need to allocate at least one object from the global to reproduce the leak
Cu.evalInSandbox(
"this.foo = {};",
sandboxBefore,
null,
"sandbox-before.js",
1
);
const weakBefore = Cu.getWeakReference(sandboxBefore);
sandboxBefore = null;
await tracker.startRecordingAllocations();
ok(
!weakBefore.get(),
"Sandbox created before the record should have been freed by GCs done by startRecordingAllocations"
);
let sandboxDuring = Cu.Sandbox(window);
// We need to allocate at least one object from the global to reproduce the leak
Cu.evalInSandbox(
"this.bar = {};",
sandboxDuring,
null,
"sandbox-during.js",
1
);
const weakDuring = Cu.getWeakReference(sandboxDuring);
sandboxDuring = null;
await tracker.stopRecordingAllocations();
ok(
!weakDuring.get(),
"Sandbox should have been freed by GCs done by stopRecordingAllocations"
);
tracker.stop();
});