summaryrefslogtreecommitdiffstats
path: root/devtools/client/memory/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/memory/test/unit')
-rw-r--r--devtools/client/memory/test/unit/.eslintrc.js6
-rw-r--r--devtools/client/memory/test/unit/head.js128
-rw-r--r--devtools/client/memory/test/unit/test_action-clear-snapshots_01.js38
-rw-r--r--devtools/client/memory/test/unit/test_action-clear-snapshots_02.js47
-rw-r--r--devtools/client/memory/test/unit/test_action-clear-snapshots_03.js46
-rw-r--r--devtools/client/memory/test/unit/test_action-clear-snapshots_04.js49
-rw-r--r--devtools/client/memory/test/unit/test_action-clear-snapshots_05.js47
-rw-r--r--devtools/client/memory/test/unit/test_action-clear-snapshots_06.js65
-rw-r--r--devtools/client/memory/test/unit/test_action-export-snapshot.js39
-rw-r--r--devtools/client/memory/test/unit/test_action-filter-01.js23
-rw-r--r--devtools/client/memory/test/unit/test_action-filter-02.js74
-rw-r--r--devtools/client/memory/test/unit/test_action-filter-03.js52
-rw-r--r--devtools/client/memory/test/unit/test_action-import-snapshot-and-census.js98
-rw-r--r--devtools/client/memory/test/unit/test_action-import-snapshot-dominator-tree.js84
-rw-r--r--devtools/client/memory/test/unit/test_action-select-snapshot.js37
-rw-r--r--devtools/client/memory/test/unit/test_action-set-display-and-refresh-01.js118
-rw-r--r--devtools/client/memory/test/unit/test_action-set-display-and-refresh-02.js53
-rw-r--r--devtools/client/memory/test/unit/test_action-set-display.js55
-rw-r--r--devtools/client/memory/test/unit/test_action-take-census.js59
-rw-r--r--devtools/client/memory/test/unit/test_action-take-snapshot-and-census.js58
-rw-r--r--devtools/client/memory/test/unit/test_action-take-snapshot.js54
-rw-r--r--devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-01.js82
-rw-r--r--devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-02.js58
-rw-r--r--devtools/client/memory/test/unit/test_action-toggle-inverted.js28
-rw-r--r--devtools/client/memory/test/unit/test_action-toggle-recording-allocations.js42
-rw-r--r--devtools/client/memory/test/unit/test_action_diffing_01.js29
-rw-r--r--devtools/client/memory/test/unit/test_action_diffing_02.js46
-rw-r--r--devtools/client/memory/test/unit/test_action_diffing_03.js104
-rw-r--r--devtools/client/memory/test/unit/test_action_diffing_04.js78
-rw-r--r--devtools/client/memory/test/unit/test_action_diffing_05.js112
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_01.js61
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_02.js64
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_03.js61
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_04.js69
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_05.js59
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_06.js127
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_07.js146
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_08.js81
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_09.js78
-rw-r--r--devtools/client/memory/test/unit/test_dominator_trees_10.js74
-rw-r--r--devtools/client/memory/test/unit/test_individuals_01.js76
-rw-r--r--devtools/client/memory/test/unit/test_individuals_02.js88
-rw-r--r--devtools/client/memory/test/unit/test_individuals_03.js106
-rw-r--r--devtools/client/memory/test/unit/test_individuals_04.js89
-rw-r--r--devtools/client/memory/test/unit/test_individuals_05.js82
-rw-r--r--devtools/client/memory/test/unit/test_individuals_06.js84
-rw-r--r--devtools/client/memory/test/unit/test_pop_view_01.js81
-rw-r--r--devtools/client/memory/test/unit/test_tree-map-01.js57
-rw-r--r--devtools/client/memory/test/unit/test_tree-map-02.js81
-rw-r--r--devtools/client/memory/test/unit/test_utils-get-snapshot-totals.js72
-rw-r--r--devtools/client/memory/test/unit/test_utils.js70
-rw-r--r--devtools/client/memory/test/unit/xpcshell.ini56
52 files changed, 3571 insertions, 0 deletions
diff --git a/devtools/client/memory/test/unit/.eslintrc.js b/devtools/client/memory/test/unit/.eslintrc.js
new file mode 100644
index 000000000..aec096a0f
--- /dev/null
+++ b/devtools/client/memory/test/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/client/memory/test/unit/head.js b/devtools/client/memory/test/unit/head.js
new file mode 100644
index 000000000..f1335dc3d
--- /dev/null
+++ b/devtools/client/memory/test/unit/head.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+var { console } = Cu.import("resource://gre/modules/Console.jsm", {});
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+var Services = require("Services");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
+flags.testing = true;
+flags.wantLogging = true;
+flags.wantVerbose = false;
+
+var { OS } = require("resource://gre/modules/osfile.jsm");
+var { FileUtils } = require("resource://gre/modules/FileUtils.jsm");
+var { TargetFactory } = require("devtools/client/framework/target");
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+var { Task } = require("devtools/shared/task");
+var { expectState } = require("devtools/server/actors/common");
+var HeapSnapshotFileUtils = require("devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
+var HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
+var { addDebuggerToGlobal } = require("resource://gre/modules/jsdebugger.jsm");
+var Store = require("devtools/client/memory/store");
+var { L10N } = require("devtools/client/memory/utils");
+var SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
+
+var EXPECTED_DTU_ASSERT_FAILURE_COUNT = 0;
+
+do_register_cleanup(function () {
+ equal(DevToolsUtils.assertionFailureCount, EXPECTED_DTU_ASSERT_FAILURE_COUNT,
+ "Should have had the expected number of DevToolsUtils.assert() failures.");
+});
+
+function dumpn(msg) {
+ dump(`MEMORY-TEST: ${msg}\n`);
+}
+
+function initDebugger() {
+ let global = new Cu.Sandbox(SYSTEM_PRINCIPAL, { freshZone: true });
+ addDebuggerToGlobal(global);
+ return new global.Debugger();
+}
+
+function StubbedMemoryFront() {
+ this.state = "detached";
+ this.recordingAllocations = false;
+ this.dbg = initDebugger();
+}
+
+StubbedMemoryFront.prototype.attach = Task.async(function* () {
+ this.state = "attached";
+});
+
+StubbedMemoryFront.prototype.detach = Task.async(function* () {
+ this.state = "detached";
+});
+
+StubbedMemoryFront.prototype.saveHeapSnapshot = expectState("attached", Task.async(function* () {
+ return ThreadSafeChromeUtils.saveHeapSnapshot({ runtime: true });
+}), "saveHeapSnapshot");
+
+StubbedMemoryFront.prototype.startRecordingAllocations = expectState("attached", Task.async(function* () {
+ this.recordingAllocations = true;
+}));
+
+StubbedMemoryFront.prototype.stopRecordingAllocations = expectState("attached", Task.async(function* () {
+ this.recordingAllocations = false;
+}));
+
+function waitUntilSnapshotState(store, expected) {
+ let predicate = () => {
+ let snapshots = store.getState().snapshots;
+ do_print(snapshots.map(x => x.state));
+ return snapshots.length === expected.length &&
+ expected.every((state, i) => state === "*" || snapshots[i].state === state);
+ };
+ do_print(`Waiting for snapshots to be of state: ${expected}`);
+ return waitUntilState(store, predicate);
+}
+
+function findReportLeafIndex(node, name = null) {
+ if (node.reportLeafIndex && (!name || node.name === name)) {
+ return node.reportLeafIndex;
+ }
+
+ if (node.children) {
+ for (let child of node.children) {
+ const found = findReportLeafIndex(child);
+ if (found) {
+ return found;
+ }
+ }
+ }
+
+ return null;
+}
+
+function waitUntilCensusState(store, getCensus, expected) {
+ let predicate = () => {
+ let snapshots = store.getState().snapshots;
+
+ do_print("Current census state:" +
+ snapshots.map(x => getCensus(x) ? getCensus(x).state : null));
+
+ return snapshots.length === expected.length &&
+ expected.every((state, i) => {
+ let census = getCensus(snapshots[i]);
+ return (state === "*") ||
+ (!census && !state) ||
+ (census && census.state === state);
+ });
+ };
+ do_print(`Waiting for snapshots' censuses to be of state: ${expected}`);
+ return waitUntilState(store, predicate);
+}
+
+function* createTempFile() {
+ let file = FileUtils.getFile("TmpD", ["tmp.fxsnapshot"]);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ let destPath = file.path;
+ let stat = yield OS.File.stat(destPath);
+ ok(stat.size === 0, "new file is 0 bytes at start");
+ return destPath;
+}
diff --git a/devtools/client/memory/test/unit/test_action-clear-snapshots_01.js b/devtools/client/memory/test/unit/test_action-clear-snapshots_01.js
new file mode 100644
index 000000000..a4e611e84
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_01.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots deletes snapshots with READ censuses
+
+let { takeSnapshotAndCensus, clearSnapshots } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, actions } = require("devtools/client/memory/constants");
+const { treeMapState } = require("devtools/client/memory/constants");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.treeMap, [treeMapState.SAVED]);
+ ok(true, "snapshot created");
+
+ ok(true, "dispatch clearSnapshots action");
+ let deleteEvents = Promise.all([
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_END)
+ ]);
+ dispatch(clearSnapshots(heapWorker));
+ yield deleteEvents;
+ ok(true, "received delete snapshots events");
+
+ equal(getState().snapshots.length, 0, "no snapshot remaining");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-clear-snapshots_02.js b/devtools/client/memory/test/unit/test_action-clear-snapshots_02.js
new file mode 100644
index 000000000..bb8c118cd
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_02.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots preserves snapshots with state != READ or ERROR
+
+let { takeSnapshotAndCensus, clearSnapshots, takeSnapshot } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, treeMapState, actions } = require("devtools/client/memory/constants");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ ok(true, "create a snapshot with a census in SAVED state");
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ ok(true, "create a snapshot in SAVED state");
+ dispatch(takeSnapshot(front));
+ yield waitUntilSnapshotState(store, [states.SAVED, states.SAVED]);
+ yield waitUntilCensusState(store, snapshot => snapshot.treeMap,
+ [treeMapState.SAVED, null]);
+ ok(true, "snapshots created with expected states");
+
+ ok(true, "dispatch clearSnapshots action");
+ let deleteEvents = Promise.all([
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_END)
+ ]);
+ dispatch(clearSnapshots(heapWorker));
+ yield deleteEvents;
+ ok(true, "received delete snapshots events");
+
+ equal(getState().snapshots.length, 1, "one snapshot remaining");
+ let remainingSnapshot = getState().snapshots[0];
+ equal(remainingSnapshot.treeMap, undefined,
+ "remaining snapshot doesn't have a treeMap property");
+ equal(remainingSnapshot.census, undefined,
+ "remaining snapshot doesn't have a census property");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-clear-snapshots_03.js b/devtools/client/memory/test/unit/test_action-clear-snapshots_03.js
new file mode 100644
index 000000000..ae888f858
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_03.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots deletes snapshots with state ERROR
+
+let { takeSnapshotAndCensus, clearSnapshots } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, treeMapState, actions } = require("devtools/client/memory/constants");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ ok(true, "create a snapshot with a treeMap");
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilSnapshotState(store, [states.SAVED]);
+ ok(true, "snapshot created with a SAVED state");
+ yield waitUntilCensusState(store, snapshot => snapshot.treeMap,
+ [treeMapState.SAVED]);
+ ok(true, "treeMap created with a SAVED state");
+
+ ok(true, "set snapshot state to error");
+ let id = getState().snapshots[0].id;
+ dispatch({ type: actions.SNAPSHOT_ERROR, id, error: new Error("_") });
+ yield waitUntilSnapshotState(store, [states.ERROR]);
+ ok(true, "snapshot set to error state");
+
+ ok(true, "dispatch clearSnapshots action");
+ let deleteEvents = Promise.all([
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_END)
+ ]);
+ dispatch(clearSnapshots(heapWorker));
+ yield deleteEvents;
+ ok(true, "received delete snapshots events");
+ equal(getState().snapshots.length, 0, "error snapshot deleted");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-clear-snapshots_04.js b/devtools/client/memory/test/unit/test_action-clear-snapshots_04.js
new file mode 100644
index 000000000..f5b316b1a
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_04.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots deletes several snapshots
+
+let { takeSnapshotAndCensus, clearSnapshots } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, actions, treeMapState } = require("devtools/client/memory/constants");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ ok(true, "create 3 snapshots with a saved census");
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, snapshot => snapshot.treeMap,
+ [treeMapState.SAVED, treeMapState.SAVED,
+ treeMapState.SAVED]);
+ ok(true, "snapshots created with a saved census");
+
+ ok(true, "set first snapshot state to error");
+ let id = getState().snapshots[0].id;
+ dispatch({ type: actions.SNAPSHOT_ERROR, id, error: new Error("_") });
+ yield waitUntilSnapshotState(store,
+ [states.ERROR, states.READ, states.READ]);
+ ok(true, "first snapshot set to error state");
+
+ ok(true, "dispatch clearSnapshots action");
+ let deleteEvents = Promise.all([
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_END)
+ ]);
+ dispatch(clearSnapshots(heapWorker));
+ yield deleteEvents;
+ ok(true, "received delete snapshots events");
+
+ equal(getState().snapshots.length, 0, "no snapshot remaining");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-clear-snapshots_05.js b/devtools/client/memory/test/unit/test_action-clear-snapshots_05.js
new file mode 100644
index 000000000..9b889b3a4
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_05.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots deletes several snapshots
+
+let { takeSnapshotAndCensus, clearSnapshots } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, actions, treeMapState } = require("devtools/client/memory/constants");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ ok(true, "create 2 snapshots with a saved census");
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ ok(true, "snapshots created with a saved census");
+ yield waitUntilCensusState(store, snapshot => snapshot.treeMap,
+ [treeMapState.SAVED, treeMapState.SAVED]);
+
+ let errorHeapWorker = {
+ deleteHeapSnapshot: function () {
+ return Promise.reject("_");
+ }
+ };
+
+ ok(true, "dispatch clearSnapshots action");
+ let deleteEvents = Promise.all([
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_END),
+ waitUntilAction(store, actions.SNAPSHOT_ERROR),
+ waitUntilAction(store, actions.SNAPSHOT_ERROR),
+ ]);
+ dispatch(clearSnapshots(errorHeapWorker));
+ yield deleteEvents;
+ ok(true, "received delete snapshots and snapshot error events");
+ equal(getState().snapshots.length, 0, "no snapshot remaining");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-clear-snapshots_06.js b/devtools/client/memory/test/unit/test_action-clear-snapshots_06.js
new file mode 100644
index 000000000..1ee32bb12
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_06.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that clearSnapshots disables diffing when deleting snapshots
+
+const {
+ takeSnapshotAndCensus,
+ clearSnapshots
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ snapshotState: states,
+ actions,
+ treeMapState
+} = require("devtools/client/memory/constants");
+const {
+ toggleDiffing,
+ selectSnapshotForDiffingAndRefresh
+} = require("devtools/client/memory/actions/diffing");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ ok(true, "create 2 snapshots with a saved census");
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, snapshot => snapshot.treeMap,
+ [treeMapState.SAVED, treeMapState.SAVED]);
+ ok(true, "snapshots created with a saved census");
+
+ dispatch(toggleDiffing());
+ dispatch(selectSnapshotForDiffingAndRefresh(heapWorker,
+ getState().snapshots[0]));
+ dispatch(selectSnapshotForDiffingAndRefresh(heapWorker,
+ getState().snapshots[1]));
+
+ ok(getState().diffing, "We should be in diffing view");
+
+ yield waitUntilAction(store, actions.TAKE_CENSUS_DIFF_END);
+ ok(true, "Received TAKE_CENSUS_DIFF_END action");
+
+ ok(true, "Dispatch clearSnapshots action");
+ let deleteEvents = Promise.all([
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+ waitUntilAction(store, actions.DELETE_SNAPSHOTS_END)
+ ]);
+ dispatch(clearSnapshots(heapWorker));
+ yield deleteEvents;
+ ok(true, "received delete snapshots events");
+
+ ok(getState().snapshots.length === 0, "Snapshots array should be empty");
+ ok(!getState().diffing, "We should no longer be diffing");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-export-snapshot.js b/devtools/client/memory/test/unit/test_action-export-snapshot.js
new file mode 100644
index 000000000..0582abbf0
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-export-snapshot.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test exporting a snapshot to a user specified location on disk.
+
+let { exportSnapshot } = require("devtools/client/memory/actions/io");
+let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, actions, treeMapState } = require("devtools/client/memory/constants");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ let destPath = yield createTempFile();
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, snapshot => snapshot.treeMap,
+ [treeMapState.SAVED]);
+
+ let exportEvents = Promise.all([
+ waitUntilAction(store, actions.EXPORT_SNAPSHOT_START),
+ waitUntilAction(store, actions.EXPORT_SNAPSHOT_END)
+ ]);
+ dispatch(exportSnapshot(getState().snapshots[0], destPath));
+ yield exportEvents;
+
+ stat = yield OS.File.stat(destPath);
+ do_print(stat.size);
+ ok(stat.size > 0, "destination file is more than 0 bytes");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-filter-01.js b/devtools/client/memory/test/unit/test_action-filter-01.js
new file mode 100644
index 000000000..e0894606d
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-filter-01.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test setting the filter string.
+
+let { setFilterString } = require("devtools/client/memory/actions/filter");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ equal(getState().filter, null, "no filter by default");
+
+ dispatch(setFilterString("my filter"));
+ equal(getState().filter, "my filter", "now we have the expected filter");
+
+ dispatch(setFilterString(""));
+ equal(getState().filter, null, "no filter again");
+});
diff --git a/devtools/client/memory/test/unit/test_action-filter-02.js b/devtools/client/memory/test/unit/test_action-filter-02.js
new file mode 100644
index 000000000..31d777704
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-filter-02.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that changing filter state properly refreshes the selected census.
+
+let { snapshotState: states, viewState, censusState } = require("devtools/client/memory/constants");
+let { setFilterStringAndRefresh } = require("devtools/client/memory/actions/filter");
+let { takeSnapshotAndCensus, selectSnapshotAndRefresh } = require("devtools/client/memory/actions/snapshot");
+let { setCensusDisplay } = require("devtools/client/memory/actions/census-display");
+let { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ equal(getState().filter, null, "no filter by default");
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVED]);
+ ok(true, "saved 3 snapshots and took a census of each of them");
+
+ dispatch(setFilterStringAndRefresh("str", heapWorker));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVING]);
+ ok(true, "setting filter string should recompute the selected snapshot's census");
+
+ equal(getState().filter, "str", "now inverted");
+
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVED]);
+
+ equal(getState().snapshots[0].census.filter, null);
+ equal(getState().snapshots[1].census.filter, null);
+ equal(getState().snapshots[2].census.filter, "str");
+
+ dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[1].id));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED,
+ censusState.SAVING,
+ censusState.SAVED]);
+ ok(true, "selecting non-inverted census should trigger a recompute");
+
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVED]);
+
+ equal(getState().snapshots[0].census.filter, null);
+ equal(getState().snapshots[1].census.filter, "str");
+ equal(getState().snapshots[2].census.filter, "str");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-filter-03.js b/devtools/client/memory/test/unit/test_action-filter-03.js
new file mode 100644
index 000000000..4d46d5361
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-filter-03.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that changing filter state in the middle of taking a snapshot results in
+// the properly fitered census.
+
+let { snapshotState: states, censusState, viewState } = require("devtools/client/memory/constants");
+let { setFilterString, setFilterStringAndRefresh } = require("devtools/client/memory/actions/filter");
+let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
+let { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilSnapshotState(store, [states.SAVING]);
+
+ dispatch(setFilterString("str"));
+
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED]);
+ equal(getState().filter, "str",
+ "should want filtered trees");
+ equal(getState().snapshots[0].census.filter, "str",
+ "snapshot-we-were-in-the-middle-of-saving's census should be filtered");
+
+ dispatch(setFilterStringAndRefresh("", heapWorker));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVING]);
+ ok(true, "changing filter string retriggers census");
+ ok(!getState().filter, "no longer filtering");
+
+ dispatch(setFilterString("obj"));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED]);
+ equal(getState().filter, "obj", "filtering for obj now");
+ equal(getState().snapshots[0].census.filter, "obj",
+ "census-we-were-in-the-middle-of-recomputing should be filtered again");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-import-snapshot-and-census.js b/devtools/client/memory/test/unit/test_action-import-snapshot-and-census.js
new file mode 100644
index 000000000..a7e1d366a
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-import-snapshot-and-census.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests the task creator `importSnapshotAndCensus()` for the whole flow of
+ * importing a snapshot, and its sub-actions.
+ */
+
+let { actions, snapshotState: states, treeMapState } = require("devtools/client/memory/constants");
+let { exportSnapshot, importSnapshotAndCensus } = require("devtools/client/memory/actions/io");
+let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { subscribe, dispatch, getState } = store;
+
+ let destPath = yield createTempFile();
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.treeMap, [treeMapState.SAVED]);
+
+ let exportEvents = Promise.all([
+ waitUntilAction(store, actions.EXPORT_SNAPSHOT_START),
+ waitUntilAction(store, actions.EXPORT_SNAPSHOT_END)
+ ]);
+ dispatch(exportSnapshot(getState().snapshots[0], destPath));
+ yield exportEvents;
+
+ // Now import our freshly exported snapshot
+ let snapshotI = 0;
+ let censusI = 0;
+ let snapshotStates = ["IMPORTING", "READING", "READ"];
+ let censusStates = ["SAVING", "SAVED"];
+ let expectStates = () => {
+ let snapshot = getState().snapshots[1];
+ if (!snapshot) {
+ return;
+ }
+ if (snapshotI < snapshotStates.length) {
+ let isCorrectState = snapshot.state === states[snapshotStates[snapshotI]];
+ if (isCorrectState) {
+ ok(true, `Found expected snapshot state ${snapshotStates[snapshotI]}`);
+ snapshotI++;
+ }
+ }
+ if (snapshot.treeMap && censusI < censusStates.length) {
+ if (snapshot.treeMap.state === treeMapState[censusStates[censusI]]) {
+ ok(true, `Found expected census state ${censusStates[censusI]}`);
+ censusI++;
+ }
+ }
+ };
+
+ let unsubscribe = subscribe(expectStates);
+ dispatch(importSnapshotAndCensus(heapWorker, destPath));
+
+ yield waitUntilState(store, () => { return snapshotI === snapshotStates.length &&
+ censusI === censusStates.length; });
+ unsubscribe();
+ equal(snapshotI, snapshotStates.length, "importSnapshotAndCensus() produces the correct sequence of states in a snapshot");
+ equal(getState().snapshots[1].state, states.READ, "imported snapshot is in READ state");
+ equal(censusI, censusStates.length, "importSnapshotAndCensus() produces the correct sequence of states in a census");
+ equal(getState().snapshots[1].treeMap.state, treeMapState.SAVED, "imported snapshot is in READ state");
+ ok(getState().snapshots[1].selected, "imported snapshot is selected");
+
+ // Check snapshot data
+ let snapshot1 = getState().snapshots[0];
+ let snapshot2 = getState().snapshots[1];
+
+ equal(snapshot1.treeMap.display, snapshot2.treeMap.display,
+ "imported snapshot has correct display");
+
+ // Clone the census data so we can destructively remove the ID/parents to compare
+ // equal census data
+ let census1 = stripUnique(JSON.parse(JSON.stringify(snapshot1.treeMap.report)));
+ let census2 = stripUnique(JSON.parse(JSON.stringify(snapshot2.treeMap.report)));
+
+ equal(JSON.stringify(census1), JSON.stringify(census2), "Imported snapshot has correct census");
+
+ function stripUnique(obj) {
+ let children = obj.children || [];
+ for (let child of children) {
+ delete child.id;
+ delete child.parent;
+ stripUnique(child);
+ }
+ delete obj.id;
+ delete obj.parent;
+ return obj;
+ }
+});
diff --git a/devtools/client/memory/test/unit/test_action-import-snapshot-dominator-tree.js b/devtools/client/memory/test/unit/test_action-import-snapshot-dominator-tree.js
new file mode 100644
index 000000000..868f22ccf
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-import-snapshot-dominator-tree.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests `importSnapshotAndCensus()` when importing snapshots from the dominator
+ * tree view. The snapshot is expected to be loaded and its dominator tree
+ * should be computed.
+ */
+
+let { snapshotState, dominatorTreeState, viewState, treeMapState } =
+ require("devtools/client/memory/constants");
+let { importSnapshotAndCensus } = require("devtools/client/memory/actions/io");
+let { changeViewAndRefresh } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { subscribe, dispatch, getState } = store;
+
+ dispatch(changeViewAndRefresh(viewState.DOMINATOR_TREE, heapWorker));
+ equal(getState().view.state, viewState.DOMINATOR_TREE,
+ "We should now be in the DOMINATOR_TREE view");
+
+ let i = 0;
+ let expected = [
+ "IMPORTING",
+ "READING",
+ "READ",
+ "treeMap:SAVING",
+ "treeMap:SAVED",
+ "dominatorTree:COMPUTING",
+ "dominatorTree:FETCHING",
+ "dominatorTree:LOADED",
+ ];
+ let expectStates = () => {
+ let snapshot = getState().snapshots[0];
+ if (snapshot && hasExpectedState(snapshot, expected[i])) {
+ ok(true, `Found expected state ${expected[i]}`);
+ i++;
+ }
+ };
+
+ let unsubscribe = subscribe(expectStates);
+ const snapshotPath = yield front.saveHeapSnapshot();
+ dispatch(importSnapshotAndCensus(heapWorker, snapshotPath));
+
+ yield waitUntilState(store, () => i === expected.length);
+ unsubscribe();
+ equal(i, expected.length, "importSnapshotAndCensus() produces the correct " +
+ "sequence of states in a snapshot");
+ equal(getState().snapshots[0].dominatorTree.state, dominatorTreeState.LOADED,
+ "imported snapshot's dominator tree is in LOADED state");
+ ok(getState().snapshots[0].selected, "imported snapshot is selected");
+});
+
+/**
+ * Check that the provided snapshot is in the expected state. The expected state
+ * is a snapshotState by default. If the expected state is prefixed by
+ * dominatorTree, a dominatorTree is expected on the provided snapshot, in the
+ * corresponding state from dominatorTreeState.
+ */
+function hasExpectedState(snapshot, expectedState) {
+ let isDominatorState = expectedState.indexOf("dominatorTree:") === 0;
+ if (isDominatorState) {
+ let state = dominatorTreeState[expectedState.replace("dominatorTree:", "")];
+ return snapshot.dominatorTree && snapshot.dominatorTree.state === state;
+ }
+
+ let isTreeMapState = expectedState.indexOf("treeMap:") === 0;
+ if (isTreeMapState) {
+ let state = treeMapState[expectedState.replace("treeMap:", "")];
+ return snapshot.treeMap && snapshot.treeMap.state === state;
+ }
+
+ let state = snapshotState[expectedState];
+ return snapshot.state === state;
+}
diff --git a/devtools/client/memory/test/unit/test_action-select-snapshot.js b/devtools/client/memory/test/unit/test_action-select-snapshot.js
new file mode 100644
index 000000000..2329ab2fa
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-select-snapshot.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the reducer responding to the action `selectSnapshot(snapshot)`
+ */
+
+let actions = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states } = require("devtools/client/memory/constants");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ yield front.attach();
+ let store = Store();
+
+ for (let i = 0; i < 5; i++) {
+ store.dispatch(actions.takeSnapshot(front));
+ }
+
+ yield waitUntilState(store, ({ snapshots }) => snapshots.length === 5 && snapshots.every(isDone));
+
+ for (let i = 0; i < 5; i++) {
+ do_print(`Selecting snapshot[${i}]`);
+ store.dispatch(actions.selectSnapshot(store.getState().snapshots[i].id));
+ yield waitUntilState(store, ({ snapshots }) => snapshots[i].selected);
+
+ let { snapshots } = store.getState();
+ ok(snapshots[i].selected, `snapshot[${i}] selected`);
+ equal(snapshots.filter(s => !s.selected).length, 4, "All other snapshots are unselected");
+ }
+});
+
+function isDone(s) { return s.state === states.SAVED; }
diff --git a/devtools/client/memory/test/unit/test_action-set-display-and-refresh-01.js b/devtools/client/memory/test/unit/test_action-set-display-and-refresh-01.js
new file mode 100644
index 000000000..570ffdf05
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-set-display-and-refresh-01.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests the task creator `setCensusDisplayAndRefreshAndRefresh()` for display
+ * changing. We test this rather than `setCensusDisplayAndRefresh` directly, as
+ * we use the refresh action in the app itself composed from
+ * `setCensusDisplayAndRefresh`.
+ */
+
+let { censusDisplays, snapshotState: states, censusState, viewState } = require("devtools/client/memory/constants");
+let { setCensusDisplayAndRefresh } = require("devtools/client/memory/actions/census-display");
+let { takeSnapshotAndCensus, selectSnapshotAndRefresh } = require("devtools/client/memory/actions/snapshot");
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+// We test setting an invalid display, which triggers an assertion failure.
+EXPECTED_DTU_ASSERT_FAILURE_COUNT = 1;
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ // Test default display with no snapshots
+ equal(getState().censusDisplay.breakdown.by, "coarseType",
+ "default coarseType display selected at start.");
+ dispatch(setCensusDisplayAndRefresh(heapWorker,
+ censusDisplays.allocationStack));
+ equal(getState().censusDisplay.breakdown.by, "allocationStack",
+ "display changed with no snapshots");
+
+ // Test invalid displays
+ ok(getState().errors.length === 0, "No error actions in the queue.");
+ dispatch(setCensusDisplayAndRefresh(heapWorker, {}));
+ yield waitUntilState(store, () => getState().errors.length === 1);
+ ok(true, "Emits an error action when passing in an invalid display object");
+
+ equal(getState().censusDisplay.breakdown.by, "allocationStack",
+ "current display unchanged when passing invalid display");
+
+ // Test new snapshots
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED]);
+
+ equal(getState().snapshots[0].census.display, censusDisplays.allocationStack,
+ "New snapshot's census uses correct display");
+
+ // Updates when changing display during `SAVING`
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED, censusState.SAVING]);
+ dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.coarseType));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED, censusState.SAVED]);
+ equal(getState().snapshots[1].census.display, censusDisplays.coarseType,
+ "Changing display while saving a snapshot results in a census using the new display");
+
+
+ // Updates when changing display during `SAVING_CENSUS`
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVING]);
+ dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.allocationStack));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVED]);
+ equal(getState().snapshots[2].census.display, censusDisplays.allocationStack,
+ "Display can be changed while saving census, stores updated display in snapshot");
+
+ // Updates census on currently selected snapshot when changing display
+ ok(getState().snapshots[2].selected, "Third snapshot currently selected");
+ dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.coarseType));
+ yield waitUntilState(store, state => state.snapshots[2].census.state === censusState.SAVING);
+ yield waitUntilState(store, state => state.snapshots[2].census.state === censusState.SAVED);
+ equal(getState().snapshots[2].census.display, censusDisplays.coarseType,
+ "Snapshot census updated when changing displays after already generating one census");
+
+ dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.allocationStack));
+ yield waitUntilState(store, state => state.snapshots[2].census.state === censusState.SAVED);
+ equal(getState().snapshots[2].census.display, censusDisplays.allocationStack,
+ "Snapshot census updated when changing displays after already generating one census");
+
+ // Does not update unselected censuses.
+ ok(!getState().snapshots[1].selected, "Second snapshot selected currently");
+ equal(getState().snapshots[1].census.display, censusDisplays.coarseType,
+ "Second snapshot using `coarseType` display still and not yet updated to correct display");
+
+ // Updates to current display when switching to stale snapshot.
+ dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[1].id));
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED,
+ censusState.SAVING,
+ censusState.SAVED]);
+ yield waitUntilCensusState(store, snapshot => snapshot.census,
+ [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVED]);
+
+ ok(getState().snapshots[1].selected, "Second snapshot selected currently");
+ equal(getState().snapshots[1].census.display, censusDisplays.allocationStack,
+ "Second snapshot using `allocationStack` display and updated to correct display");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-set-display-and-refresh-02.js b/devtools/client/memory/test/unit/test_action-set-display-and-refresh-02.js
new file mode 100644
index 000000000..5be5444d4
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-set-display-and-refresh-02.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests the task creator `setCensusDisplayAndRefreshAndRefresh()` for custom
+ * displays.
+ */
+
+let { snapshotState: states, censusState, viewState } = require("devtools/client/memory/constants");
+let { setCensusDisplayAndRefresh } = require("devtools/client/memory/actions/census-display");
+let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
+let { changeView } = require("devtools/client/memory/actions/view");
+
+let CUSTOM = {
+ displayName: "Custom",
+ tooltip: "Custom tooltip",
+ inverted: false,
+ breakdown: {
+ by: "internalType",
+ then: { by: "count", bytes: true, count: false }
+ }
+};
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+ dispatch(setCensusDisplayAndRefresh(heapWorker, CUSTOM));
+ equal(getState().censusDisplay, CUSTOM,
+ "CUSTOM display stored in display state.");
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ equal(getState().snapshots[0].census.display, CUSTOM,
+ "New snapshot stored CUSTOM display when done taking census");
+ ok(getState().snapshots[0].census.report.children.length, "Census has some children");
+ // Ensure we don't have `count` in any results
+ ok(getState().snapshots[0].census.report.children.every(c => !c.count),
+ "Census used CUSTOM display without counts");
+ // Ensure we do have `bytes` in the results
+ ok(getState().snapshots[0].census.report.children.every(c => typeof c.bytes === "number"),
+ "Census used CUSTOM display with bytes");
+});
diff --git a/devtools/client/memory/test/unit/test_action-set-display.js b/devtools/client/memory/test/unit/test_action-set-display.js
new file mode 100644
index 000000000..43ea975da
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-set-display.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests the action creator `setCensusDisplay()` for display changing. Does not
+ * test refreshing the census information, check `setCensusDisplayAndRefresh`
+ * action for that.
+ */
+
+let { censusDisplays, snapshotState: states, censusState, viewState } = require("devtools/client/memory/constants");
+let { setCensusDisplay } = require("devtools/client/memory/actions/census-display");
+let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+// We test setting an invalid display, which triggers an assertion failure.
+EXPECTED_DTU_ASSERT_FAILURE_COUNT = 1;
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ // Test default display with no snapshots
+ equal(getState().censusDisplay.breakdown.by, "coarseType",
+ "default coarseType display selected at start.");
+
+ dispatch(setCensusDisplay(censusDisplays.allocationStack));
+ equal(getState().censusDisplay.breakdown.by, "allocationStack",
+ "display changed with no snapshots");
+
+ // Test invalid displays
+ try {
+ dispatch(setCensusDisplay({}));
+ ok(false, "Throws when passing in an invalid display object");
+ } catch (e) {
+ ok(true, "Throws when passing in an invalid display object");
+ }
+ equal(getState().censusDisplay.breakdown.by, "allocationStack",
+ "current display unchanged when passing invalid display");
+
+ // Test new snapshots
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+ equal(getState().snapshots[0].census.display, censusDisplays.allocationStack,
+ "New snapshots use the current, non-default display");
+});
diff --git a/devtools/client/memory/test/unit/test_action-take-census.js b/devtools/client/memory/test/unit/test_action-take-census.js
new file mode 100644
index 000000000..3e3d30e8e
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-take-census.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the async reducer responding to the action `takeCensus(heapWorker, snapshot)`
+ */
+
+var { snapshotState: states, censusDisplays, censusState, censusState, viewState } = require("devtools/client/memory/constants");
+var actions = require("devtools/client/memory/actions/snapshot");
+var { changeView } = require("devtools/client/memory/actions/view");
+
+
+function run_test() {
+ run_next_test();
+}
+
+// This tests taking a census on a snapshot that is still being read, which
+// triggers an assertion failure.
+EXPECTED_DTU_ASSERT_FAILURE_COUNT = 1;
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+
+ store.dispatch(changeView(viewState.CENSUS));
+
+ store.dispatch(actions.takeSnapshot(front));
+ yield waitUntilState(store, () => {
+ let snapshots = store.getState().snapshots;
+ return snapshots.length === 1 && snapshots[0].state === states.SAVED;
+ });
+
+ let snapshot = store.getState().snapshots[0];
+ equal(snapshot.census, null, "No census data exists yet on the snapshot.");
+
+ // Test error case of wrong state.
+ store.dispatch(actions.takeCensus(heapWorker, snapshot.id));
+ yield waitUntilState(store, () => store.getState().errors.length === 1);
+
+ dumpn("Found error: " + store.getState().errors[0]);
+ ok(/Assertion failure/.test(store.getState().errors[0]),
+ "Error thrown when taking a census of a snapshot that has not been read.");
+
+ store.dispatch(actions.readSnapshot(heapWorker, snapshot.id));
+ yield waitUntilState(store, () => store.getState().snapshots[0].state === states.READ);
+
+ store.dispatch(actions.takeCensus(heapWorker, snapshot.id));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVING]);
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ snapshot = store.getState().snapshots[0];
+ ok(snapshot.census, "Snapshot has census after saved census");
+ ok(snapshot.census.report.children.length, "Census is in tree node form");
+ equal(snapshot.census.display, censusDisplays.coarseType,
+ "Snapshot stored correct display used for the census");
+
+});
diff --git a/devtools/client/memory/test/unit/test_action-take-snapshot-and-census.js b/devtools/client/memory/test/unit/test_action-take-snapshot-and-census.js
new file mode 100644
index 000000000..77c3b8e38
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-take-snapshot-and-census.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the task creator `takeSnapshotAndCensus()` for the whole flow of
+ * taking a snapshot, and its sub-actions.
+ */
+
+let { snapshotState: states, treeMapState } = require("devtools/client/memory/constants");
+let actions = require("devtools/client/memory/actions/snapshot");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+
+ let snapshotI = 0;
+ let censusI = 0;
+ let snapshotStates = ["SAVING", "SAVED", "READING", "READ"];
+ let censusStates = ["SAVING", "SAVED"];
+ let expectStates = () => {
+ let snapshot = store.getState().snapshots[0];
+ if (!snapshot) {
+ return;
+ }
+ if (snapshotI < snapshotStates.length) {
+ let isCorrectState = snapshot.state === states[snapshotStates[snapshotI]];
+ if (isCorrectState) {
+ ok(true, `Found expected snapshot state ${snapshotStates[snapshotI]}`);
+ snapshotI++;
+ }
+ }
+ if (snapshot.treeMap && censusI < censusStates.length) {
+ if (snapshot.treeMap.state === treeMapState[censusStates[censusI]]) {
+ ok(true, `Found expected census state ${censusStates[censusI]}`);
+ censusI++;
+ }
+ }
+ };
+
+
+ let unsubscribe = store.subscribe(expectStates);
+ store.dispatch(actions.takeSnapshotAndCensus(front, heapWorker));
+
+ yield waitUntilState(store, () => { return snapshotI === snapshotStates.length &&
+ censusI === censusStates.length; });
+ unsubscribe();
+
+ ok(true, "takeSnapshotAndCensus() produces the correct sequence of states in a snapshot");
+ let snapshot = store.getState().snapshots[0];
+ ok(snapshot.treeMap, "snapshot has tree map census data");
+ ok(snapshot.selected, "snapshot is selected");
+});
diff --git a/devtools/client/memory/test/unit/test_action-take-snapshot.js b/devtools/client/memory/test/unit/test_action-take-snapshot.js
new file mode 100644
index 000000000..c05583b22
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-take-snapshot.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the async reducer responding to the action `takeSnapshot(front)`
+ */
+
+let actions = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states } = require("devtools/client/memory/constants");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ yield front.attach();
+ let store = Store();
+
+ let unsubscribe = store.subscribe(checkState);
+
+ let foundPendingState = false;
+ let foundDoneState = false;
+
+ function checkState() {
+ let { snapshots } = store.getState();
+ let lastSnapshot = snapshots[snapshots.length - 1];
+
+ if (lastSnapshot.state === states.SAVING) {
+ foundPendingState = true;
+ ok(foundPendingState, "Got state change for pending heap snapshot request");
+ ok(!lastSnapshot.path, "Snapshot does not yet have a path");
+ ok(!lastSnapshot.census, "Has no census data when loading");
+ }
+ else if (lastSnapshot.state === states.SAVED) {
+ foundDoneState = true;
+ ok(foundDoneState, "Got state change for completed heap snapshot request");
+ ok(foundPendingState, "SAVED state occurs after SAVING state");
+ ok(lastSnapshot.path, "Snapshot fetched with a path");
+ ok(snapshots.every(s => s.selected === (s.id === lastSnapshot.id)),
+ "Only recent snapshot is selected");
+ }
+ }
+
+ for (let i = 0; i < 4; i++) {
+ store.dispatch(actions.takeSnapshot(front));
+ yield waitUntilState(store, () => foundPendingState && foundDoneState);
+
+ // reset state trackers
+ foundDoneState = foundPendingState = false;
+ }
+
+ unsubscribe();
+});
diff --git a/devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-01.js b/devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-01.js
new file mode 100644
index 000000000..cd015557d
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-01.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that changing displays with different inverted state properly
+// refreshes the selected census.
+
+const {
+ censusDisplays,
+ snapshotState: states,
+ censusState,
+ viewState
+} = require("devtools/client/memory/constants");
+const {
+ setCensusDisplayAndRefresh
+} = require("devtools/client/memory/actions/census-display");
+const {
+ takeSnapshotAndCensus,
+ selectSnapshotAndRefresh,
+} = require("devtools/client/memory/actions/snapshot");
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ // Select a non-inverted display.
+ dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.allocationStack));
+ equal(getState().censusDisplay.inverted, false, "not inverted by default");
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVED]);
+ ok(true, "saved 3 snapshots and took a census of each of them");
+
+ // Select an inverted display.
+ dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.invertedAllocationStack));
+
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVING]);
+ ok(true, "toggling inverted should recompute the selected snapshot's census");
+
+ equal(getState().censusDisplay.inverted, true, "now inverted");
+
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVED]);
+
+ equal(getState().snapshots[0].census.display.inverted, false);
+ equal(getState().snapshots[1].census.display.inverted, false);
+ equal(getState().snapshots[2].census.display.inverted, true);
+
+ dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[1].id));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED,
+ censusState.SAVING,
+ censusState.SAVED]);
+ ok(true, "selecting non-inverted census should trigger a recompute");
+
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVED]);
+
+ equal(getState().snapshots[0].census.display.inverted, false);
+ equal(getState().snapshots[1].census.display.inverted, true);
+ equal(getState().snapshots[2].census.display.inverted, true);
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-02.js b/devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-02.js
new file mode 100644
index 000000000..f0cdba264
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-02.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that changing inverted state in the middle of taking a snapshot results
+// in an inverted census.
+
+const { censusDisplays, snapshotState: states, censusState, viewState } = require("devtools/client/memory/constants");
+const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
+const {
+ setCensusDisplay,
+ setCensusDisplayAndRefresh,
+} = require("devtools/client/memory/actions/census-display");
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ dispatch(setCensusDisplay(censusDisplays.allocationStack));
+ equal(getState().censusDisplay.inverted, false,
+ "Should not have an inverted census display");
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilSnapshotState(store, [states.SAVING]);
+
+ dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.invertedAllocationStack));
+
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ ok(getState().censusDisplay.inverted,
+ "should want inverted trees");
+ ok(getState().snapshots[0].census.display.inverted,
+ "snapshot-we-were-in-the-middle-of-saving's census should be inverted");
+
+ dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.allocationStack));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVING]);
+ ok(true, "toggling inverted retriggers census");
+ ok(!getState().censusDisplay.inverted, "no longer inverted");
+
+ dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.invertedAllocationStack));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+ ok(getState().censusDisplay.inverted, "inverted again");
+ ok(getState().snapshots[0].census.display.inverted,
+ "census-we-were-in-the-middle-of-recomputing should be inverted again");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action-toggle-inverted.js b/devtools/client/memory/test/unit/test_action-toggle-inverted.js
new file mode 100644
index 000000000..665e5d822
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-toggle-inverted.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test toggling the top level inversion state of the tree.
+
+const { censusDisplays } = require("devtools/client/memory/constants");
+const { setCensusDisplay } = require("devtools/client/memory/actions/census-display");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ dispatch(setCensusDisplay(censusDisplays.allocationStack));
+ equal(getState().censusDisplay.inverted, false,
+ "not inverted initially");
+
+ dispatch(setCensusDisplay(censusDisplays.invertedAllocationStack));
+ equal(getState().censusDisplay.inverted, true, "now inverted after toggling");
+
+ dispatch(setCensusDisplay(censusDisplays.allocationStack));
+ equal(getState().censusDisplay.inverted, false,
+ "not inverted again after toggling again");
+});
diff --git a/devtools/client/memory/test/unit/test_action-toggle-recording-allocations.js b/devtools/client/memory/test/unit/test_action-toggle-recording-allocations.js
new file mode 100644
index 000000000..ebbc17173
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-toggle-recording-allocations.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test toggling the recording of allocation stacks.
+ */
+
+let { toggleRecordingAllocationStacks } = require("devtools/client/memory/actions/allocations");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ equal(getState().allocations.recording, false, "not recording by default");
+ equal(getState().allocations.togglingInProgress, false,
+ "not in the process of toggling by default");
+
+ dispatch(toggleRecordingAllocationStacks(front));
+ yield waitUntilState(store, () => getState().allocations.togglingInProgress);
+ ok(true, "`togglingInProgress` set to true when toggling on");
+ yield waitUntilState(store, () => !getState().allocations.togglingInProgress);
+
+ equal(getState().allocations.recording, true, "now we are recording");
+ ok(front.recordingAllocations, "front is recording too");
+
+ dispatch(toggleRecordingAllocationStacks(front));
+ yield waitUntilState(store, () => getState().allocations.togglingInProgress);
+ ok(true, "`togglingInProgress` set to true when toggling off");
+ yield waitUntilState(store, () => !getState().allocations.togglingInProgress);
+
+ equal(getState().allocations.recording, false, "now we are not recording");
+ ok(front.recordingAllocations, "front is not recording anymore");
+
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action_diffing_01.js b/devtools/client/memory/test/unit/test_action_diffing_01.js
new file mode 100644
index 000000000..24893581e
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action_diffing_01.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test toggling of diffing.
+
+const { toggleDiffing } = require("devtools/client/memory/actions/diffing");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ equal(getState().diffing, null, "not diffing by default");
+
+ dispatch(toggleDiffing());
+ ok(getState().diffing, "now diffing after toggling");
+
+ dispatch(toggleDiffing());
+ equal(getState().diffing, null, "not diffing again after toggling again");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action_diffing_02.js b/devtools/client/memory/test/unit/test_action_diffing_02.js
new file mode 100644
index 000000000..fc68cee65
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action_diffing_02.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that toggling diffing unselects all snapshots.
+
+const { snapshotState, censusState, viewState } = require("devtools/client/memory/constants");
+const { toggleDiffing } = require("devtools/client/memory/actions/diffing");
+const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ equal(getState().diffing, null, "not diffing by default");
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED,
+ censusState.SAVED,
+ censusState.SAVED]);
+
+ ok(getState().snapshots.some(s => s.selected),
+ "One of the new snapshots is selected");
+
+ dispatch(toggleDiffing());
+ ok(getState().diffing, "now diffing after toggling");
+
+ for (let s of getState().snapshots) {
+ ok(!s.selected,
+ "No snapshot should be selected after entering diffing mode");
+ }
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action_diffing_03.js b/devtools/client/memory/test/unit/test_action_diffing_03.js
new file mode 100644
index 000000000..3dd901724
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action_diffing_03.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test selecting snapshots for diffing.
+
+const { diffingState, snapshotState, viewState } = require("devtools/client/memory/constants");
+const {
+ toggleDiffing,
+ selectSnapshotForDiffing
+} = require("devtools/client/memory/actions/diffing");
+const { takeSnapshot } = require("devtools/client/memory/actions/snapshot");
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+// We test that you (1) cannot select a snapshot that is not in a diffable
+// state, and (2) cannot select more than 2 snapshots for diffing. Both attempts
+// trigger assertion failures.
+EXPECTED_DTU_ASSERT_FAILURE_COUNT = 2;
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+ equal(getState().diffing, null, "not diffing by default");
+
+ dispatch(takeSnapshot(front, heapWorker));
+ dispatch(takeSnapshot(front, heapWorker));
+ dispatch(takeSnapshot(front, heapWorker));
+
+ yield waitUntilSnapshotState(store, [snapshotState.SAVED,
+ snapshotState.SAVED,
+ snapshotState.SAVED]);
+ dispatch(takeSnapshot(front));
+
+ // Start diffing.
+ dispatch(toggleDiffing());
+ ok(getState().diffing, "now diffing after toggling");
+ equal(getState().diffing.firstSnapshotId, null,
+ "no first snapshot selected");
+ equal(getState().diffing.secondSnapshotId, null,
+ "no second snapshot selected");
+ equal(getState().diffing.state, diffingState.SELECTING,
+ "should be in diffing state SELECTING");
+
+ // Can't select a snapshot that is not in a diffable state.
+ equal(getState().snapshots[3].state, snapshotState.SAVING,
+ "the last snapshot is still in the process of being saved");
+ dumpn("Expecting exception:");
+ let threw = false;
+ try {
+ dispatch(selectSnapshotForDiffing(getState().snapshots[3]));
+ } catch (error) {
+ threw = true;
+ }
+ ok(threw, "Should not be able to select snapshots that aren't ready for diffing");
+
+ // Select first snapshot for diffing.
+ dispatch(selectSnapshotForDiffing(getState().snapshots[0]));
+ ok(getState().diffing, "now diffing after toggling");
+ equal(getState().diffing.firstSnapshotId, getState().snapshots[0].id,
+ "first snapshot selected");
+ equal(getState().diffing.secondSnapshotId, null,
+ "no second snapshot selected");
+ equal(getState().diffing.state, diffingState.SELECTING,
+ "should still be in diffing state SELECTING");
+
+ // Can't diff first snapshot with itself; this is a noop.
+ dispatch(selectSnapshotForDiffing(getState().snapshots[0]));
+ ok(getState().diffing, "still diffing");
+ equal(getState().diffing.firstSnapshotId, getState().snapshots[0].id,
+ "first snapshot still selected");
+ equal(getState().diffing.secondSnapshotId, null,
+ "still no second snapshot selected");
+ equal(getState().diffing.state, diffingState.SELECTING,
+ "should still be in diffing state SELECTING");
+
+ // Select second snapshot for diffing.
+ dispatch(selectSnapshotForDiffing(getState().snapshots[1]));
+ ok(getState().diffing, "still diffing");
+ equal(getState().diffing.firstSnapshotId, getState().snapshots[0].id,
+ "first snapshot still selected");
+ equal(getState().diffing.secondSnapshotId, getState().snapshots[1].id,
+ "second snapshot selected");
+
+ // Can't select more than two snapshots for diffing.
+ dumpn("Expecting exception:");
+ threw = false;
+ try {
+ dispatch(selectSnapshotForDiffing(getState().snapshots[2]));
+ } catch (error) {
+ threw = true;
+ }
+ ok(threw, "Can't select more than two snapshots for diffing");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action_diffing_04.js b/devtools/client/memory/test/unit/test_action_diffing_04.js
new file mode 100644
index 000000000..2c3cd098b
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action_diffing_04.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we compute census diffs.
+
+const {
+ diffingState,
+ snapshotState,
+ viewState
+} = require("devtools/client/memory/constants");
+const {
+ toggleDiffing,
+ selectSnapshotForDiffingAndRefresh
+} = require("devtools/client/memory/actions/diffing");
+const {
+ takeSnapshot,
+ readSnapshot
+} = require("devtools/client/memory/actions/snapshot");
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+ dispatch(changeView(viewState.CENSUS));
+
+ equal(getState().diffing, null, "not diffing by default");
+
+ const s1 = yield dispatch(takeSnapshot(front, heapWorker));
+ const s2 = yield dispatch(takeSnapshot(front, heapWorker));
+ const s3 = yield dispatch(takeSnapshot(front, heapWorker));
+ dispatch(readSnapshot(heapWorker, s1));
+ dispatch(readSnapshot(heapWorker, s2));
+ dispatch(readSnapshot(heapWorker, s3));
+ yield waitUntilSnapshotState(store, [snapshotState.READ,
+ snapshotState.READ,
+ snapshotState.READ]);
+
+ dispatch(toggleDiffing());
+ dispatch(selectSnapshotForDiffingAndRefresh(heapWorker,
+ getState().snapshots[0]));
+ dispatch(selectSnapshotForDiffingAndRefresh(heapWorker,
+ getState().snapshots[1]));
+
+ ok(getState().diffing, "We should be diffing.");
+ equal(getState().diffing.firstSnapshotId, getState().snapshots[0].id,
+ "First snapshot selected.");
+ equal(getState().diffing.secondSnapshotId, getState().snapshots[1].id,
+ "Second snapshot selected.");
+
+ yield waitUntilState(store,
+ state =>
+ state.diffing.state === diffingState.TAKING_DIFF);
+ ok(true,
+ "Selecting two snapshots for diffing should trigger computing a diff.");
+
+ yield waitUntilState(store,
+ state => state.diffing.state === diffingState.TOOK_DIFF);
+ ok(true, "And then the diff should complete.");
+ ok(getState().diffing.census, "And we should have a census.");
+ ok(getState().diffing.census.report, "And that census should have a report.");
+ equal(getState().diffing.census.display, getState().censusDisplay,
+ "And that census should have the correct display");
+ equal(getState().diffing.census.filter, getState().filter,
+ "And that census should have the correct filter");
+ equal(getState().diffing.census.display.inverted,
+ getState().censusDisplay.inverted,
+ "And that census should have the correct inversion");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_action_diffing_05.js b/devtools/client/memory/test/unit/test_action_diffing_05.js
new file mode 100644
index 000000000..e4a1491ac
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action_diffing_05.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we recompute census diffs at the appropriate times.
+
+const {
+ diffingState,
+ snapshotState,
+ censusDisplays,
+ viewState,
+} = require("devtools/client/memory/constants");
+const {
+ setCensusDisplayAndRefresh,
+} = require("devtools/client/memory/actions/census-display");
+const {
+ toggleDiffing,
+ selectSnapshotForDiffingAndRefresh,
+} = require("devtools/client/memory/actions/diffing");
+const {
+ setFilterStringAndRefresh,
+} = require("devtools/client/memory/actions/filter");
+const {
+ takeSnapshot,
+ readSnapshot,
+} = require("devtools/client/memory/actions/snapshot");
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+ dispatch(changeView(viewState.CENSUS));
+
+ yield dispatch(setCensusDisplayAndRefresh(heapWorker,
+ censusDisplays.allocationStack));
+ equal(getState().censusDisplay.inverted, false,
+ "not inverted at start");
+
+ equal(getState().diffing, null, "not diffing by default");
+
+ const s1 = yield dispatch(takeSnapshot(front, heapWorker));
+ const s2 = yield dispatch(takeSnapshot(front, heapWorker));
+ const s3 = yield dispatch(takeSnapshot(front, heapWorker));
+ dispatch(readSnapshot(heapWorker, s1));
+ dispatch(readSnapshot(heapWorker, s2));
+ dispatch(readSnapshot(heapWorker, s3));
+ yield waitUntilSnapshotState(store, [snapshotState.READ,
+ snapshotState.READ,
+ snapshotState.READ]);
+
+ yield dispatch(toggleDiffing());
+ dispatch(selectSnapshotForDiffingAndRefresh(heapWorker,
+ getState().snapshots[0]));
+ dispatch(selectSnapshotForDiffingAndRefresh(heapWorker,
+ getState().snapshots[1]));
+ yield waitUntilState(store,
+ state => state.diffing.state === diffingState.TOOK_DIFF);
+
+ const shouldTriggerRecompute = [
+ {
+ name: "toggling inversion",
+ func: () => dispatch(setCensusDisplayAndRefresh(
+ heapWorker,
+ censusDisplays.invertedAllocationStack))
+ },
+ {
+ name: "filtering",
+ func: () => dispatch(setFilterStringAndRefresh("scr", heapWorker))
+ },
+ {
+ name: "changing displays",
+ func: () =>
+ dispatch(setCensusDisplayAndRefresh(heapWorker,
+ censusDisplays.coarseType))
+ }
+ ];
+
+ for (let { name, func } of shouldTriggerRecompute) {
+ dumpn(`Testing that "${name}" triggers a diff recompute`);
+ func();
+
+ yield waitUntilState(store,
+ state =>
+ state.diffing.state === diffingState.TAKING_DIFF);
+ ok(true, "triggered diff recompute.");
+
+ yield waitUntilState(store,
+ state =>
+ state.diffing.state === diffingState.TOOK_DIFF);
+ ok(true, "And then the diff should complete.");
+ ok(getState().diffing.census, "And we should have a census.");
+ ok(getState().diffing.census.report,
+ "And that census should have a report.");
+ equal(getState().diffing.census.display,
+ getState().censusDisplay,
+ "And that census should have the correct display");
+ equal(getState().diffing.census.filter, getState().filter,
+ "And that census should have the correct filter");
+ equal(getState().diffing.census.display.inverted,
+ getState().censusDisplay.inverted,
+ "And that census should have the correct inversion");
+ }
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_01.js b/devtools/client/memory/test/unit/test_dominator_trees_01.js
new file mode 100644
index 000000000..5fcf441d4
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_01.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can compute and fetch the dominator tree for a snapshot.
+
+let {
+ snapshotState: states,
+ dominatorTreeState,
+ treeMapState,
+} = require("devtools/client/memory/constants");
+let {
+ takeSnapshotAndCensus,
+ computeAndFetchDominatorTree,
+} = require("devtools/client/memory/actions/snapshot");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.treeMap, [treeMapState.SAVED]);
+ ok(!getState().snapshots[0].dominatorTree,
+ "There shouldn't be a dominator tree model yet since it is not computed " +
+ "until we switch to the dominators view.");
+
+ // Change to the dominator tree view.
+ dispatch(computeAndFetchDominatorTree(heapWorker, getState().snapshots[0].id));
+ ok(getState().snapshots[0].dominatorTree,
+ "Should now have a dominator tree model for the selected snapshot");
+
+ // Wait for the dominator tree to start being computed.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.COMPUTING);
+ ok(true, "The dominator tree started computing");
+ ok(!getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is computing, we should not have its root");
+
+ // Wait for the dominator tree to finish computing and start being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
+ ok(true, "The dominator tree started fetching");
+ ok(!getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is fetching, we should not have its root");
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true, "The dominator tree was fetched");
+ ok(getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is loaded, we should have its root");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_02.js b/devtools/client/memory/test/unit/test_dominator_trees_02.js
new file mode 100644
index 000000000..b35961004
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_02.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that selecting the dominator tree view automatically kicks off fetching
+// and computing dominator trees.
+
+const {
+ snapshotState: states,
+ dominatorTreeState,
+ viewState,
+ treeMapState,
+} = require("devtools/client/memory/constants");
+const {
+ takeSnapshotAndCensus,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeViewAndRefresh
+} = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.treeMap, [treeMapState.SAVED]);
+ ok(!getState().snapshots[0].dominatorTree,
+ "There shouldn't be a dominator tree model yet since it is not computed " +
+ "until we switch to the dominators view.");
+
+ dispatch(changeViewAndRefresh(viewState.DOMINATOR_TREE, heapWorker));
+ ok(getState().snapshots[0].dominatorTree,
+ "Should now have a dominator tree model for the selected snapshot");
+
+ // Wait for the dominator tree to start being computed.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.COMPUTING);
+ ok(true, "The dominator tree started computing");
+ ok(!getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is computing, we should not have its root");
+
+ // Wait for the dominator tree to finish computing and start being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
+ ok(true, "The dominator tree started fetching");
+ ok(!getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is fetching, we should not have its root");
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true, "The dominator tree was fetched");
+ ok(getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is loaded, we should have its root");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_03.js b/devtools/client/memory/test/unit/test_dominator_trees_03.js
new file mode 100644
index 000000000..09e6477f4
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_03.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that selecting the dominator tree view and then taking a snapshot
+// properly kicks off fetching and computing dominator trees.
+
+const {
+ snapshotState: states,
+ dominatorTreeState,
+ viewState,
+} = require("devtools/client/memory/constants");
+const {
+ takeSnapshotAndCensus,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView
+} = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.DOMINATOR_TREE));
+ equal(getState().view.state, viewState.DOMINATOR_TREE,
+ "We should now be in the DOMINATOR_TREE view");
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+
+ // Wait for the dominator tree to start being computed.
+ yield waitUntilState(store, state =>
+ state.snapshots[0] && state.snapshots[0].dominatorTree);
+ equal(getState().snapshots[0].dominatorTree.state,
+ dominatorTreeState.COMPUTING,
+ "The dominator tree started computing");
+ ok(!getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is computing, we should not have its root");
+
+ // Wait for the dominator tree to finish computing and start being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
+ ok(true, "The dominator tree started fetching");
+ ok(!getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is fetching, we should not have its root");
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true, "The dominator tree was fetched");
+ ok(getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is loaded, we should have its root");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_04.js b/devtools/client/memory/test/unit/test_dominator_trees_04.js
new file mode 100644
index 000000000..ba375c354
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_04.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that selecting the dominator tree view while in the middle of taking a
+// snapshot properly kicks off fetching and computing dominator trees.
+
+const {
+ snapshotState: states,
+ dominatorTreeState,
+ viewState,
+} = require("devtools/client/memory/constants");
+const {
+ takeSnapshotAndCensus,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView
+} = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+
+ for (let intermediateSnapshotState of [states.SAVING,
+ states.READING,
+ states.READ]) {
+ dumpn(`Testing switching to the DOMINATOR_TREE view in the middle of the ${intermediateSnapshotState} snapshot state`);
+
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilSnapshotState(store, [intermediateSnapshotState]);
+
+ dispatch(changeView(viewState.DOMINATOR_TREE));
+ equal(getState().view.state, viewState.DOMINATOR_TREE,
+ "We should now be in the DOMINATOR_TREE view");
+
+ // Wait for the dominator tree to start being computed.
+ yield waitUntilState(store, state =>
+ state.snapshots[0] && state.snapshots[0].dominatorTree);
+ equal(getState().snapshots[0].dominatorTree.state,
+ dominatorTreeState.COMPUTING,
+ "The dominator tree started computing");
+ ok(!getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is computing, we should not have its root");
+
+ // Wait for the dominator tree to finish computing and start being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
+ ok(true, "The dominator tree started fetching");
+ ok(!getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is fetching, we should not have its root");
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true, "The dominator tree was fetched");
+ ok(getState().snapshots[0].dominatorTree.root,
+ "When the dominator tree is loaded, we should have its root");
+ }
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_05.js b/devtools/client/memory/test/unit/test_dominator_trees_05.js
new file mode 100644
index 000000000..42ea13e18
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_05.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that changing the currently selected snapshot to a snapshot that does
+// not have a dominator tree will automatically compute and fetch one for it.
+
+let {
+ snapshotState: states,
+ dominatorTreeState,
+ viewState,
+ treeMapState,
+} = require("devtools/client/memory/constants");
+let {
+ takeSnapshotAndCensus,
+ selectSnapshotAndRefresh,
+} = require("devtools/client/memory/actions/snapshot");
+
+let { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.treeMap, [treeMapState.SAVED,
+ treeMapState.SAVED]);
+
+ ok(getState().snapshots[1].selected, "The second snapshot is selected");
+
+ // Change to the dominator tree view.
+ dispatch(changeView(viewState.DOMINATOR_TREE));
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[1].dominatorTree &&
+ state.snapshots[1].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true, "The second snapshot's dominator tree was fetched");
+
+ // Select the first snapshot.
+ dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[0].id));
+
+ // And now the first snapshot should have its dominator tree fetched and
+ // computed because of the new selection.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree &&
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true, "The first snapshot's dominator tree was fetched");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_06.js b/devtools/client/memory/test/unit/test_dominator_trees_06.js
new file mode 100644
index 000000000..78f869102
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_06.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can incrementally fetch a subtree of a dominator tree.
+
+const {
+ snapshotState: states,
+ dominatorTreeState,
+ viewState,
+} = require("devtools/client/memory/constants");
+const {
+ takeSnapshotAndCensus,
+ selectSnapshotAndRefresh,
+ fetchImmediatelyDominated,
+} = require("devtools/client/memory/actions/snapshot");
+const DominatorTreeLazyChildren
+ = require("devtools/client/memory/dominator-tree-lazy-children");
+
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.DOMINATOR_TREE));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0] &&
+ state.snapshots[0].dominatorTree &&
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(getState().snapshots[0].dominatorTree.root,
+ "The dominator tree was fetched");
+
+ // Find a node that has children, but none of them are loaded.
+
+ function findNode(node) {
+ if (node.moreChildrenAvailable && !node.children) {
+ return node;
+ }
+
+ if (node.children) {
+ for (let child of node.children) {
+ const found = findNode(child);
+ if (found) {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ const oldRoot = getState().snapshots[0].dominatorTree.root;
+ const oldNode = findNode(oldRoot);
+ ok(oldNode,
+ "Should have found a node with children that are not loaded since we " +
+ "only send partial dominator trees across initially and load the rest " +
+ "on demand");
+ ok(oldNode !== oldRoot, "But the node should not be the root");
+
+ const lazyChildren = new DominatorTreeLazyChildren(oldNode.nodeId, 0);
+ dispatch(fetchImmediatelyDominated(heapWorker, getState().snapshots[0].id, lazyChildren));
+
+ equal(getState().snapshots[0].dominatorTree.state,
+ dominatorTreeState.INCREMENTAL_FETCHING,
+ "Fetching immediately dominated children should put us in the " +
+ "INCREMENTAL_FETCHING state");
+
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true,
+ "The dominator tree should go back to LOADED after the incremental " +
+ "fetching is done.");
+
+ const newRoot = getState().snapshots[0].dominatorTree.root;
+ ok(oldRoot !== newRoot,
+ "When we insert new nodes, we get a new tree");
+ equal(oldRoot.children.length, newRoot.children.length,
+ "The new tree's root should have the same number of children as the " +
+ "old root's");
+
+ let differentChildrenCount = 0;
+ for (let i = 0; i < oldRoot.children.length; i++) {
+ if (oldRoot.children[i] !== newRoot.children[i]) {
+ differentChildrenCount++;
+ }
+ }
+ equal(differentChildrenCount, 1,
+ "All subtrees except the subtree we inserted incrementally fetched " +
+ "children into should be the same because we use persistent updates");
+
+ // Find the new node which has the children inserted.
+
+ function findNewNode(node) {
+ if (node.nodeId === oldNode.nodeId) {
+ return node;
+ }
+
+ if (node.children) {
+ for (let child of node.children) {
+ const found = findNewNode(child);
+ if (found) {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ const newNode = findNewNode(newRoot);
+ ok(newNode, "Should find the node in the new tree again");
+ ok(newNode !== oldNode, "We did not mutate the old node in place, instead created a new node");
+ ok(newNode.children, "And the new node should have the children attached");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_07.js b/devtools/client/memory/test/unit/test_dominator_trees_07.js
new file mode 100644
index 000000000..9121fc878
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_07.js
@@ -0,0 +1,146 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can incrementally fetch two subtrees in the same dominator tree
+// concurrently. This exercises the activeFetchRequestCount machinery.
+
+const {
+ snapshotState: states,
+ dominatorTreeState,
+ viewState,
+} = require("devtools/client/memory/constants");
+const {
+ takeSnapshotAndCensus,
+ selectSnapshotAndRefresh,
+ fetchImmediatelyDominated,
+} = require("devtools/client/memory/actions/snapshot");
+const DominatorTreeLazyChildren
+ = require("devtools/client/memory/dominator-tree-lazy-children");
+
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.DOMINATOR_TREE));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0] &&
+ state.snapshots[0].dominatorTree &&
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(getState().snapshots[0].dominatorTree.root,
+ "The dominator tree was fetched");
+
+ // Find a node that has more children.
+
+ function findNode(node) {
+ if (node.moreChildrenAvailable && !node.children) {
+ return node;
+ }
+
+ if (node.children) {
+ for (let child of node.children) {
+ const found = findNode(child);
+ if (found) {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ const oldRoot = getState().snapshots[0].dominatorTree.root;
+ const oldNode = findNode(oldRoot);
+ ok(oldNode, "Should have found a node with more children.");
+
+ // Find another node that has more children.
+ function findNodeRev(node) {
+ if (node.moreChildrenAvailable && !node.children) {
+ return node;
+ }
+
+ if (node.children) {
+ for (let child of node.children.slice().reverse()) {
+ const found = findNodeRev(child);
+ if (found) {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ const oldNode2 = findNodeRev(oldRoot);
+ ok(oldNode2, "Should have found another node with more children.");
+ ok(oldNode !== oldNode2,
+ "The second node should not be the same as the first one");
+
+ // Fetch both subtrees concurrently.
+ dispatch(fetchImmediatelyDominated(heapWorker, getState().snapshots[0].id,
+ new DominatorTreeLazyChildren(oldNode.nodeId, 0)));
+ dispatch(fetchImmediatelyDominated(heapWorker, getState().snapshots[0].id,
+ new DominatorTreeLazyChildren(oldNode2.nodeId, 0)));
+
+ equal(getState().snapshots[0].dominatorTree.state,
+ dominatorTreeState.INCREMENTAL_FETCHING,
+ "Fetching immediately dominated children should put us in the " +
+ "INCREMENTAL_FETCHING state");
+
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true,
+ "The dominator tree should go back to LOADED after the incremental " +
+ "fetching is done.");
+
+ const newRoot = getState().snapshots[0].dominatorTree.root;
+ ok(oldRoot !== newRoot,
+ "When we insert new nodes, we get a new tree");
+
+ // Find the new node which has the children inserted.
+
+ function findNodeWithId(id, node) {
+ if (node.nodeId === id) {
+ return node;
+ }
+
+ if (node.children) {
+ for (let child of node.children) {
+ const found = findNodeWithId(id, child);
+ if (found) {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ const newNode = findNodeWithId(oldNode.nodeId, newRoot);
+ ok(newNode, "Should find the node in the new tree again");
+ ok(newNode !== oldNode,
+ "We did not mutate the old node in place, instead created a new node");
+ ok(newNode.children.length,
+ "And the new node should have the new children attached");
+
+ const newNode2 = findNodeWithId(oldNode2.nodeId, newRoot);
+ ok(newNode2, "Should find the second node in the new tree again");
+ ok(newNode2 !== oldNode2,
+ "We did not mutate the second old node in place, instead created a new node");
+ ok(newNode2.children,
+ "And the new node should have the new children attached");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_08.js b/devtools/client/memory/test/unit/test_dominator_trees_08.js
new file mode 100644
index 000000000..27f35180c
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_08.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can change the display with which we describe a dominator tree
+// and that the dominator tree is re-fetched.
+
+const {
+ snapshotState: states,
+ dominatorTreeState,
+ viewState,
+ labelDisplays,
+ treeMapState
+} = require("devtools/client/memory/constants");
+const {
+ setLabelDisplayAndRefresh
+} = require("devtools/client/memory/actions/label-display");
+const {
+ changeView,
+} = require("devtools/client/memory/actions/view");
+const {
+ takeSnapshotAndCensus,
+ computeAndFetchDominatorTree,
+} = require("devtools/client/memory/actions/snapshot");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.DOMINATOR_TREE));
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.treeMap, [treeMapState.SAVED]);
+ ok(!getState().snapshots[0].dominatorTree,
+ "There shouldn't be a dominator tree model yet since it is not computed " +
+ "until we switch to the dominators view.");
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0] &&
+ state.snapshots[0].dominatorTree &&
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+
+ ok(getState().labelDisplay,
+ "We have a default display for describing nodes in a dominator tree");
+ equal(getState().labelDisplay,
+ labelDisplays.coarseType,
+ "and the default is coarse type");
+ equal(getState().labelDisplay,
+ getState().snapshots[0].dominatorTree.display,
+ "and the newly computed dominator tree has that display");
+
+ // Switch to the allocationStack display.
+ dispatch(setLabelDisplayAndRefresh(
+ heapWorker,
+ labelDisplays.allocationStack));
+
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
+ ok(true,
+ "switching display types caused the dominator tree to be fetched " +
+ "again.");
+
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ equal(getState().snapshots[0].dominatorTree.display,
+ labelDisplays.allocationStack,
+ "The new dominator tree's display is allocationStack");
+ equal(getState().labelDisplay,
+ labelDisplays.allocationStack,
+ "as is our requested dominator tree display");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_09.js b/devtools/client/memory/test/unit/test_dominator_trees_09.js
new file mode 100644
index 000000000..c1d1f7495
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_09.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can change the display with which we describe a dominator tree
+// while the dominator tree is in the middle of being fetched.
+
+const {
+ snapshotState: states,
+ dominatorTreeState,
+ viewState,
+ labelDisplays,
+ treeMapState,
+} = require("devtools/client/memory/constants");
+const {
+ setLabelDisplayAndRefresh
+} = require("devtools/client/memory/actions/label-display");
+const {
+ changeView,
+} = require("devtools/client/memory/actions/view");
+const {
+ takeSnapshotAndCensus,
+ computeAndFetchDominatorTree,
+} = require("devtools/client/memory/actions/snapshot");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.DOMINATOR_TREE));
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.treeMap, [treeMapState.SAVED]);
+ ok(!getState().snapshots[0].dominatorTree,
+ "There shouldn't be a dominator tree model yet since it is not computed " +
+ "until we switch to the dominators view.");
+
+ // Wait for the dominator tree to start fetching.
+ yield waitUntilState(store, state =>
+ state.snapshots[0] &&
+ state.snapshots[0].dominatorTree &&
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
+
+ ok(getState().labelDisplay,
+ "We have a default display for describing nodes in a dominator tree");
+ equal(getState().labelDisplay,
+ labelDisplays.coarseType,
+ "and the default is coarse type");
+ equal(getState().labelDisplay,
+ getState().snapshots[0].dominatorTree.display,
+ "and the newly computed dominator tree has that display");
+
+ // Switch to the allocationStack display while we are still fetching the
+ // dominator tree.
+ dispatch(setLabelDisplayAndRefresh(
+ heapWorker,
+ labelDisplays.allocationStack));
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+
+ equal(getState().snapshots[0].dominatorTree.display,
+ labelDisplays.allocationStack,
+ "The new dominator tree's display is allocationStack");
+ equal(getState().labelDisplay,
+ labelDisplays.allocationStack,
+ "as is our requested dominator tree display");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_dominator_trees_10.js b/devtools/client/memory/test/unit/test_dominator_trees_10.js
new file mode 100644
index 000000000..f7cf86a10
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_dominator_trees_10.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we maintain focus of the selected dominator tree node across
+// changing breakdowns for labeling them.
+
+let {
+ snapshotState: states,
+ dominatorTreeState,
+ labelDisplays,
+ viewState,
+} = require("devtools/client/memory/constants");
+let {
+ takeSnapshotAndCensus,
+ focusDominatorTreeNode,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView,
+} = require("devtools/client/memory/actions/view");
+const {
+ setLabelDisplayAndRefresh,
+} = require("devtools/client/memory/actions/label-display");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.DOMINATOR_TREE));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+
+ // Wait for the dominator tree to finish being fetched.
+ yield waitUntilState(store, state =>
+ state.snapshots[0] &&
+ state.snapshots[0].dominatorTree &&
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true, "The dominator tree was fetched");
+
+ const root = getState().snapshots[0].dominatorTree.root;
+ ok(root, "When the dominator tree is loaded, we should have its root");
+
+ dispatch(focusDominatorTreeNode(getState().snapshots[0].id, root));
+ equal(root, getState().snapshots[0].dominatorTree.focused,
+ "The root should be focused.");
+
+ equal(getState().labelDisplay, labelDisplays.coarseType,
+ "Using labelDisplays.coarseType by default");
+ dispatch(setLabelDisplayAndRefresh(heapWorker,
+ labelDisplays.allocationStack));
+ equal(getState().labelDisplay, labelDisplays.allocationStack,
+ "Using labelDisplays.allocationStack now");
+
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
+ ok(true, "We started re-fetching the dominator tree");
+
+ yield waitUntilState(store, state =>
+ state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
+ ok(true, "The dominator tree was loaded again");
+
+ ok(getState().snapshots[0].dominatorTree.focused,
+ "Still have a focused node");
+ equal(getState().snapshots[0].dominatorTree.focused.nodeId, root.nodeId,
+ "Focused node is the same as before");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_individuals_01.js b/devtools/client/memory/test/unit/test_individuals_01.js
new file mode 100644
index 000000000..36971a7dc
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_individuals_01.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Basic test for switching to the individuals view.
+
+const {
+ censusState,
+ viewState,
+ individualsState,
+} = require("devtools/client/memory/constants");
+const {
+ fetchIndividuals,
+ takeSnapshotAndCensus,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView,
+} = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+const EXPECTED_INDIVIDUAL_STATES = [
+ individualsState.COMPUTING_DOMINATOR_TREE,
+ individualsState.FETCHING,
+ individualsState.FETCHED,
+];
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ equal(getState().individuals, null,
+ "no individuals state by default");
+
+ dispatch(changeView(viewState.CENSUS));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ const root = getState().snapshots[0].census.report;
+ ok(root, "Should have a census");
+
+ const reportLeafIndex = findReportLeafIndex(root);
+ ok(reportLeafIndex, "Should get a reportLeafIndex");
+
+ const snapshotId = getState().snapshots[0].id;
+ ok(snapshotId, "Should have a snapshot id");
+
+ const breakdown = getState().snapshots[0].census.display.breakdown;
+ ok(breakdown, "Should have a breakdown");
+
+ dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
+ reportLeafIndex));
+
+ // Wait for each expected state.
+ for (let state of EXPECTED_INDIVIDUAL_STATES) {
+ yield waitUntilState(store, s => {
+ return s.view.state === viewState.INDIVIDUALS &&
+ s.individuals &&
+ s.individuals.state === state;
+ });
+ ok(true, `Reached state = ${state}`);
+ }
+
+ ok(getState().individuals, "Should have individuals state");
+ ok(getState().individuals.nodes, "Should have individuals nodes");
+ ok(getState().individuals.nodes.length > 0,
+ "Should have a positive number of nodes");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_individuals_02.js b/devtools/client/memory/test/unit/test_individuals_02.js
new file mode 100644
index 000000000..9e23e2c4f
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_individuals_02.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test switching to the individuals view when we are in the middle of computing
+// a dominator tree.
+
+const {
+ censusState,
+ dominatorTreeState,
+ viewState,
+ individualsState,
+} = require("devtools/client/memory/constants");
+const {
+ fetchIndividuals,
+ takeSnapshotAndCensus,
+ computeDominatorTree,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView,
+} = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+const EXPECTED_INDIVIDUAL_STATES = [
+ individualsState.COMPUTING_DOMINATOR_TREE,
+ individualsState.FETCHING,
+ individualsState.FETCHED,
+];
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ equal(getState().individuals, null,
+ "no individuals state by default");
+
+ dispatch(changeView(viewState.CENSUS));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ const root = getState().snapshots[0].census.report;
+ ok(root, "Should have a census");
+
+ const reportLeafIndex = findReportLeafIndex(root);
+ ok(reportLeafIndex, "Should get a reportLeafIndex");
+
+ const snapshotId = getState().snapshots[0].id;
+ ok(snapshotId, "Should have a snapshot id");
+
+ const breakdown = getState().snapshots[0].census.display.breakdown;
+ ok(breakdown, "Should have a breakdown");
+
+ // Start computing a dominator tree.
+
+ dispatch(computeDominatorTree(heapWorker, snapshotId));
+ equal(getState().snapshots[0].dominatorTree.state,
+ dominatorTreeState.COMPUTING,
+ "Should be computing dominator tree");
+
+ // Fetch individuals in the middle of computing the dominator tree.
+
+ dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
+ reportLeafIndex));
+
+ // Wait for each expected state.
+ for (let state of EXPECTED_INDIVIDUAL_STATES) {
+ yield waitUntilState(store, s => {
+ return s.view.state === viewState.INDIVIDUALS &&
+ s.individuals &&
+ s.individuals.state === state;
+ });
+ ok(true, `Reached state = ${state}`);
+ }
+
+ ok(getState().individuals, "Should have individuals state");
+ ok(getState().individuals.nodes, "Should have individuals nodes");
+ ok(getState().individuals.nodes.length > 0,
+ "Should have a positive number of nodes");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_individuals_03.js b/devtools/client/memory/test/unit/test_individuals_03.js
new file mode 100644
index 000000000..54460b737
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_individuals_03.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test switching to the individuals view when we are in the diffing view.
+
+const {
+ censusState,
+ diffingState,
+ viewState,
+ individualsState,
+} = require("devtools/client/memory/constants");
+const {
+ fetchIndividuals,
+ takeSnapshotAndCensus,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView,
+ popViewAndRefresh,
+} = require("devtools/client/memory/actions/view");
+const {
+ selectSnapshotForDiffingAndRefresh,
+} = require("devtools/client/memory/actions/diffing");
+
+function run_test() {
+ run_next_test();
+}
+
+const EXPECTED_INDIVIDUAL_STATES = [
+ individualsState.COMPUTING_DOMINATOR_TREE,
+ individualsState.FETCHING,
+ individualsState.FETCHED,
+];
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ // Take two snapshots and diff them from each other.
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED,
+ censusState.SAVED]);
+
+ dispatch(changeView(viewState.DIFFING));
+ dispatch(selectSnapshotForDiffingAndRefresh(heapWorker, getState().snapshots[0]));
+ dispatch(selectSnapshotForDiffingAndRefresh(heapWorker, getState().snapshots[1]));
+
+ yield waitUntilState(store, state => {
+ return state.diffing &&
+ state.diffing.state === diffingState.TOOK_DIFF;
+ });
+ ok(getState().diffing.census);
+
+ // Fetch individuals.
+
+ const root = getState().diffing.census.report;
+ ok(root, "Should have a census");
+
+ const reportLeafIndex = findReportLeafIndex(root);
+ ok(reportLeafIndex, "Should get a reportLeafIndex");
+
+ const snapshotId = getState().diffing.secondSnapshotId;
+ ok(snapshotId, "Should have a snapshot id");
+
+ const breakdown = getState().censusDisplay.breakdown;
+ ok(breakdown, "Should have a breakdown");
+
+ dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
+ reportLeafIndex));
+
+ for (let state of EXPECTED_INDIVIDUAL_STATES) {
+ yield waitUntilState(store, s => {
+ return s.view.state === viewState.INDIVIDUALS &&
+ s.individuals &&
+ s.individuals.state === state;
+ });
+ ok(true, `Reached state = ${state}`);
+ }
+
+ ok(getState().individuals, "Should have individuals state");
+ ok(getState().individuals.nodes, "Should have individuals nodes");
+ ok(getState().individuals.nodes.length > 0,
+ "Should have a positive number of nodes");
+
+ // Pop the view back to the diffing.
+
+ dispatch(popViewAndRefresh(heapWorker));
+
+ yield waitUntilState(store, state => {
+ return state.diffing &&
+ state.diffing.state === diffingState.TOOK_DIFF;
+ });
+
+ ok(getState().diffing.census.report,
+ "We have our census diff again after popping back to the last view");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_individuals_04.js b/devtools/client/memory/test/unit/test_individuals_04.js
new file mode 100644
index 000000000..c160d555f
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_individuals_04.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test showing individual Array objects.
+
+const {
+ censusState,
+ viewState,
+ individualsState,
+} = require("devtools/client/memory/constants");
+const {
+ fetchIndividuals,
+ takeSnapshotAndCensus,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView,
+} = require("devtools/client/memory/actions/view");
+const {
+ setFilterString,
+} = require("devtools/client/memory/actions/filter");
+
+function run_test() {
+ run_next_test();
+}
+
+const EXPECTED_INDIVIDUAL_STATES = [
+ individualsState.COMPUTING_DOMINATOR_TREE,
+ individualsState.FETCHING,
+ individualsState.FETCHED,
+];
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+ dispatch(setFilterString("Array"));
+
+ // Take a snapshot and wait for the census to finish.
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ // Fetch individuals.
+
+ const root = getState().snapshots[0].census.report;
+ ok(root, "Should have a census");
+
+ const reportLeafIndex = findReportLeafIndex(root, "Array");
+ ok(reportLeafIndex, "Should get a reportLeafIndex for Array");
+
+ const snapshotId = getState().snapshots[0].id;
+ ok(snapshotId, "Should have a snapshot id");
+
+ const breakdown = getState().censusDisplay.breakdown;
+ ok(breakdown, "Should have a breakdown");
+
+ dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
+ reportLeafIndex));
+
+ for (let state of EXPECTED_INDIVIDUAL_STATES) {
+ yield waitUntilState(store, s => {
+ return s.view.state === viewState.INDIVIDUALS &&
+ s.individuals &&
+ s.individuals.state === state;
+ });
+ ok(true, `Reached state = ${state}`);
+ }
+
+ ok(getState().individuals, "Should have individuals state");
+ ok(getState().individuals.nodes, "Should have individuals nodes");
+ ok(getState().individuals.nodes.length > 0,
+ "Should have a positive number of nodes");
+
+ // Assert that all the individuals are `Array`s.
+
+ for (let node of getState().individuals.nodes) {
+ dumpn("Checking node: " + node.label.join(" > "));
+ ok(node.label.find(part => part === "Array"),
+ "The node should be an Array node");
+ }
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_individuals_05.js b/devtools/client/memory/test/unit/test_individuals_05.js
new file mode 100644
index 000000000..1f8873826
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_individuals_05.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test showing individual objects that do not have allocation stacks.
+
+const {
+ censusState,
+ viewState,
+ individualsState,
+ censusDisplays,
+} = require("devtools/client/memory/constants");
+const {
+ fetchIndividuals,
+ takeSnapshotAndCensus,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView,
+} = require("devtools/client/memory/actions/view");
+const {
+ setCensusDisplay,
+} = require("devtools/client/memory/actions/census-display");
+
+function run_test() {
+ run_next_test();
+}
+
+const EXPECTED_INDIVIDUAL_STATES = [
+ individualsState.COMPUTING_DOMINATOR_TREE,
+ individualsState.FETCHING,
+ individualsState.FETCHED,
+];
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+ dispatch(setCensusDisplay(censusDisplays.invertedAllocationStack));
+
+ // Take a snapshot and wait for the census to finish.
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ // Fetch individuals.
+
+ const root = getState().snapshots[0].census.report;
+ ok(root, "Should have a census");
+
+ const reportLeafIndex = findReportLeafIndex(root, "noStack");
+ ok(reportLeafIndex, "Should get a reportLeafIndex for noStack");
+
+ const snapshotId = getState().snapshots[0].id;
+ ok(snapshotId, "Should have a snapshot id");
+
+ const breakdown = getState().censusDisplay.breakdown;
+ ok(breakdown, "Should have a breakdown");
+
+ dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
+ reportLeafIndex));
+
+ for (let state of EXPECTED_INDIVIDUAL_STATES) {
+ yield waitUntilState(store, s => {
+ return s.view.state === viewState.INDIVIDUALS &&
+ s.individuals &&
+ s.individuals.state === state;
+ });
+ ok(true, `Reached state = ${state}`);
+ }
+
+ ok(getState().individuals, "Should have individuals state");
+ ok(getState().individuals.nodes, "Should have individuals nodes");
+ ok(getState().individuals.nodes.length > 0,
+ "Should have a positive number of nodes");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_individuals_06.js b/devtools/client/memory/test/unit/test_individuals_06.js
new file mode 100644
index 000000000..6147b8181
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_individuals_06.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that clearing the current individuals' snapshot leaves the individuals
+// view.
+
+const {
+ censusState,
+ viewState,
+ individualsState,
+} = require("devtools/client/memory/constants");
+const {
+ fetchIndividuals,
+ takeSnapshotAndCensus,
+ clearSnapshots,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView,
+} = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+const EXPECTED_INDIVIDUAL_STATES = [
+ individualsState.COMPUTING_DOMINATOR_TREE,
+ individualsState.FETCHING,
+ individualsState.FETCHED,
+];
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ // Take a snapshot and wait for the census to finish.
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ // Fetch individuals.
+
+ const root = getState().snapshots[0].census.report;
+ ok(root, "Should have a census");
+
+ const reportLeafIndex = findReportLeafIndex(root);
+ ok(reportLeafIndex, "Should get a reportLeafIndex");
+
+ const snapshotId = getState().snapshots[0].id;
+ ok(snapshotId, "Should have a snapshot id");
+
+ const breakdown = getState().censusDisplay.breakdown;
+ ok(breakdown, "Should have a breakdown");
+
+ dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
+ reportLeafIndex));
+
+ for (let state of EXPECTED_INDIVIDUAL_STATES) {
+ yield waitUntilState(store, s => {
+ return s.view.state === viewState.INDIVIDUALS &&
+ s.individuals &&
+ s.individuals.state === state;
+ });
+ ok(true, `Reached state = ${state}`);
+ }
+
+ ok(getState().individuals, "Should have individuals state");
+ ok(getState().individuals.nodes, "Should have individuals nodes");
+ ok(getState().individuals.nodes.length > 0,
+ "Should have a positive number of nodes");
+
+ dispatch(clearSnapshots(heapWorker));
+
+ equal(getState().view.state, viewState.CENSUS,
+ "Went back to census view");
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_pop_view_01.js b/devtools/client/memory/test/unit/test_pop_view_01.js
new file mode 100644
index 000000000..9e6a17095
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_pop_view_01.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test popping views from each intermediate individuals model state.
+
+const {
+ censusState,
+ viewState,
+ individualsState,
+} = require("devtools/client/memory/constants");
+const {
+ fetchIndividuals,
+ takeSnapshotAndCensus,
+} = require("devtools/client/memory/actions/snapshot");
+const {
+ changeView,
+ popViewAndRefresh,
+} = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+const TEST_STATES = [
+ individualsState.COMPUTING_DOMINATOR_TREE,
+ individualsState.FETCHING,
+ individualsState.FETCHED,
+];
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ const { getState, dispatch } = store;
+
+ equal(getState().individuals, null,
+ "no individuals state by default");
+
+ dispatch(changeView(viewState.CENSUS));
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ const root = getState().snapshots[0].census.report;
+ ok(root, "Should have a census");
+
+ const reportLeafIndex = findReportLeafIndex(root);
+ ok(reportLeafIndex, "Should get a reportLeafIndex");
+
+ const snapshotId = getState().snapshots[0].id;
+ ok(snapshotId, "Should have a snapshot id");
+
+ const breakdown = getState().snapshots[0].census.display.breakdown;
+ ok(breakdown, "Should have a breakdown");
+
+ for (let state of TEST_STATES) {
+ dumpn(`Testing popping back to the old view from state = ${state}`);
+
+ dispatch(fetchIndividuals(heapWorker, snapshotId, breakdown,
+ reportLeafIndex));
+
+ // Wait for the expected test state.
+ yield waitUntilState(store, s => {
+ return s.view.state === viewState.INDIVIDUALS &&
+ s.individuals &&
+ s.individuals.state === state;
+ });
+ ok(true, `Reached state = ${state}`);
+
+ // Pop back to the CENSUS state.
+ dispatch(popViewAndRefresh(heapWorker));
+ yield waitUntilState(store, s => {
+ return s.view.state === viewState.CENSUS;
+ });
+ ok(!getState().individuals, "Should no longer have individuals");
+ }
+
+ heapWorker.destroy();
+ yield front.detach();
+});
diff --git a/devtools/client/memory/test/unit/test_tree-map-01.js b/devtools/client/memory/test/unit/test_tree-map-01.js
new file mode 100644
index 000000000..15d9a765a
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_tree-map-01.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { drawBox } = require("devtools/client/memory/components/tree-map/draw");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let fillRectValues, strokeRectValues;
+ let ctx = {
+ fillRect: (...args) => fillRectValues = args,
+ strokeRect: (...args) => strokeRectValues = args
+ };
+ let node = {
+ x: 20,
+ y: 30,
+ dx: 50,
+ dy: 70,
+ type: "other",
+ depth: 2
+ };
+ let padding = [10, 10];
+ let borderWidth = () => 1;
+ let dragZoom = {
+ offsetX: 0,
+ offsetY: 0,
+ zoom: 0
+ };
+ drawBox(ctx, node, borderWidth, dragZoom, padding);
+ ok(true, JSON.stringify([ctx, fillRectValues, strokeRectValues]));
+ equal(ctx.fillStyle, "hsl(210,60%,70%)", "The fillStyle is set");
+ equal(ctx.strokeStyle, "hsl(210,60%,35%)", "The strokeStyle is set");
+ equal(ctx.lineWidth, 1, "The lineWidth is set");
+ deepEqual(fillRectValues, [10.5, 20.5, 49, 69], "Draws a filled rectangle");
+ deepEqual(strokeRectValues, [10.5, 20.5, 49, 69], "Draws a stroked rectangle");
+
+
+ dragZoom.zoom = 0.5;
+
+ drawBox(ctx, node, borderWidth, dragZoom, padding);
+ ok(true, JSON.stringify([ctx, fillRectValues, strokeRectValues]));
+ deepEqual(fillRectValues, [15.5, 30.5, 74, 104],
+ "Draws a zoomed filled rectangle");
+ deepEqual(strokeRectValues, [15.5, 30.5, 74, 104],
+ "Draws a zoomed stroked rectangle");
+
+ dragZoom.offsetX = 110;
+ dragZoom.offsetY = 130;
+
+ drawBox(ctx, node, borderWidth, dragZoom, padding);
+ deepEqual(fillRectValues, [-94.5, -99.5, 74, 104],
+ "Draws a zoomed and offset filled rectangle");
+ deepEqual(strokeRectValues, [-94.5, -99.5, 74, 104],
+ "Draws a zoomed and offset stroked rectangle");
+});
diff --git a/devtools/client/memory/test/unit/test_tree-map-02.js b/devtools/client/memory/test/unit/test_tree-map-02.js
new file mode 100644
index 000000000..4f7200f0a
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_tree-map-02.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { drawText } = require("devtools/client/memory/components/tree-map/draw");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ // Mock out the Canvas2dContext
+ let ctx = {
+ fillText: (...args) => fillTextValues.push(args),
+ measureText: (text) => {
+ let width = text ? text.length * 10 : 0;
+ return { width };
+ }
+ };
+ let node = {
+ x: 20,
+ y: 30,
+ dx: 500,
+ dy: 70,
+ name: "Example Node",
+ totalBytes: 1200,
+ totalCount: 100
+ };
+ let ratio = 0;
+ let borderWidth = () => 1;
+ let dragZoom = {
+ offsetX: 0,
+ offsetY: 0,
+ zoom: 0
+ };
+ let fillTextValues = [];
+ let padding = [10, 10];
+
+ drawText(ctx, node, borderWidth, ratio, dragZoom, padding);
+ deepEqual(fillTextValues[0], ["Example Node", 11.5, 21.5],
+ "Fills in the full node name");
+ deepEqual(fillTextValues[1], ["1KiB 100 count", 141.5, 21.5],
+ "Includes the full byte and count information");
+
+ fillTextValues = [];
+ node.dx = 250;
+ drawText(ctx, node, borderWidth, ratio, dragZoom, padding);
+
+ deepEqual(fillTextValues[0], ["Example Node", 11.5, 21.5],
+ "Fills in the full node name");
+ deepEqual(fillTextValues[1], undefined,
+ "Drops off the byte and count information if not enough room");
+
+ fillTextValues = [];
+ node.dx = 100;
+ drawText(ctx, node, borderWidth, ratio, dragZoom, padding);
+
+ deepEqual(fillTextValues[0], ["Exampl...", 11.5, 21.5],
+ "Cuts the name with ellipsis");
+ deepEqual(fillTextValues[1], undefined,
+ "Drops off the byte and count information if not enough room");
+
+ fillTextValues = [];
+ node.dx = 40;
+ drawText(ctx, node, borderWidth, ratio, dragZoom, padding);
+
+ deepEqual(fillTextValues[0], ["...", 11.5, 21.5],
+ "Shows only ellipsis when smaller");
+ deepEqual(fillTextValues[1], undefined,
+ "Drops off the byte and count information if not enough room");
+
+ fillTextValues = [];
+ node.dx = 20;
+ drawText(ctx, node, borderWidth, ratio, dragZoom, padding);
+
+ deepEqual(fillTextValues[0], undefined,
+ "Draw nothing when not enough room");
+ deepEqual(fillTextValues[1], undefined,
+ "Drops off the byte and count information if not enough room");
+});
diff --git a/devtools/client/memory/test/unit/test_utils-get-snapshot-totals.js b/devtools/client/memory/test/unit/test_utils-get-snapshot-totals.js
new file mode 100644
index 000000000..c4560fb07
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_utils-get-snapshot-totals.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests that we use the correct snapshot aggregate value
+ * in `utils.getSnapshotTotals(snapshot)`
+ */
+
+const { censusDisplays, snapshotState: states, viewState, censusState } = require("devtools/client/memory/constants");
+const { getSnapshotTotals } = require("devtools/client/memory/utils");
+const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
+const { setCensusDisplayAndRefresh } = require("devtools/client/memory/actions/census-display");
+const { changeView } = require("devtools/client/memory/actions/view");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let front = new StubbedMemoryFront();
+ let heapWorker = new HeapAnalysesClient();
+ yield front.attach();
+ let store = Store();
+ let { getState, dispatch } = store;
+
+ dispatch(changeView(viewState.CENSUS));
+
+ yield dispatch(setCensusDisplayAndRefresh(heapWorker,
+ censusDisplays.allocationStack));
+
+ dispatch(takeSnapshotAndCensus(front, heapWorker));
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+
+ ok(!getState().snapshots[0].census.display.inverted, "Snapshot is not inverted");
+
+ let census = getState().snapshots[0].census;
+ let result = aggregate(census.report);
+ let totalBytes = result.bytes;
+ let totalCount = result.count;
+
+ ok(totalBytes > 0, "counted up bytes in the census");
+ ok(totalCount > 0, "counted up count in the census");
+
+ result = getSnapshotTotals(getState().snapshots[0].census);
+ equal(totalBytes, result.bytes, "getSnapshotTotals reuslted in correct bytes");
+ equal(totalCount, result.count, "getSnapshotTotals reuslted in correct count");
+
+ dispatch(setCensusDisplayAndRefresh(heapWorker,
+ censusDisplays.invertedAllocationStack));
+
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVING]);
+ yield waitUntilCensusState(store, s => s.census, [censusState.SAVED]);
+ ok(getState().snapshots[0].census.display.inverted, "Snapshot is inverted");
+
+ result = getSnapshotTotals(getState().snapshots[0].census);
+ equal(totalBytes, result.bytes,
+ "getSnapshotTotals reuslted in correct bytes when inverted");
+ equal(totalCount, result.count,
+ "getSnapshotTotals reuslted in correct count when inverted");
+});
+
+function aggregate(report) {
+ let totalBytes = report.bytes;
+ let totalCount = report.count;
+ for (let child of (report.children || [])) {
+ let { bytes, count } = aggregate(child);
+ totalBytes += bytes;
+ totalCount += count;
+ }
+ return { bytes: totalBytes, count: totalCount };
+}
diff --git a/devtools/client/memory/test/unit/test_utils.js b/devtools/client/memory/test/unit/test_utils.js
new file mode 100644
index 000000000..f6deddfc9
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_utils.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests the task creator `takeSnapshotAndCensus()` for the whole flow of
+ * taking a snapshot, and its sub-actions. Tests the formatNumber and
+ * formatPercent methods.
+ */
+
+let utils = require("devtools/client/memory/utils");
+let { snapshotState: states, viewState } = require("devtools/client/memory/constants");
+let { Preferences } = require("resource://gre/modules/Preferences.jsm");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let s1 = utils.createSnapshot({ view: { state: viewState.CENSUS } });
+ let s2 = utils.createSnapshot({ view: { state: viewState.CENSUS } });
+ equal(s1.state, states.SAVING, "utils.createSnapshot() creates snapshot in saving state");
+ ok(s1.id !== s2.id, "utils.createSnapshot() creates snapshot with unique ids");
+
+ let custom = { by: "internalType", then: { by: "count", bytes: true }};
+ Preferences.set("devtools.memory.custom-census-displays", JSON.stringify({ "My Display": custom }));
+
+ equal(utils.getCustomCensusDisplays()["My Display"].by, custom.by,
+ "utils.getCustomCensusDisplays() returns custom displays");
+
+ ok(true, "test formatNumber util functions");
+ equal(utils.formatNumber(12), "12", "formatNumber returns 12 for 12");
+
+ equal(utils.formatNumber(0), "0", "formatNumber returns 0 for 0");
+ equal(utils.formatNumber(-0), "0", "formatNumber returns 0 for -0");
+ equal(utils.formatNumber(+0), "0", "formatNumber returns 0 for +0");
+
+ equal(utils.formatNumber(1234567), "1 234 567",
+ "formatNumber adds a space every 3rd digit");
+ equal(utils.formatNumber(12345678), "12 345 678",
+ "formatNumber adds a space every 3rd digit");
+ equal(utils.formatNumber(123456789), "123 456 789",
+ "formatNumber adds a space every 3rd digit");
+
+ equal(utils.formatNumber(12, true), "+12",
+ "formatNumber can display number sign");
+ equal(utils.formatNumber(-12, true), "-12",
+ "formatNumber can display number sign (negative)");
+
+ ok(true, "test formatPercent util functions");
+ equal(utils.formatPercent(12), "12%", "formatPercent returns 12% for 12");
+ equal(utils.formatPercent(12345), "12 345%",
+ "formatPercent returns 12 345% for 12345");
+
+ equal(utils.formatAbbreviatedBytes(12), "12B", "Formats bytes");
+ equal(utils.formatAbbreviatedBytes(12345), "12KiB", "Formats kilobytes");
+ equal(utils.formatAbbreviatedBytes(12345678), "11MiB", "Formats megabytes");
+ equal(utils.formatAbbreviatedBytes(12345678912), "11GiB", "Formats gigabytes");
+
+ equal(utils.hslToStyle(0.5, 0.6, 0.7),
+ "hsl(180,60%,70%)", "hslToStyle converts an array to a style string");
+ equal(utils.hslToStyle(0, 0, 0),
+ "hsl(0,0%,0%)", "hslToStyle converts an array to a style string");
+ equal(utils.hslToStyle(1, 1, 1),
+ "hsl(360,100%,100%)", "hslToStyle converts an array to a style string");
+
+ equal(utils.lerp(5, 7, 0), 5, "lerp return first number for 0");
+ equal(utils.lerp(5, 7, 1), 7, "lerp return second number for 1");
+ equal(utils.lerp(5, 7, 0.5), 6, "lerp interpolates the numbers for 0.5");
+});
diff --git a/devtools/client/memory/test/unit/xpcshell.ini b/devtools/client/memory/test/unit/xpcshell.ini
new file mode 100644
index 000000000..dade269c3
--- /dev/null
+++ b/devtools/client/memory/test/unit/xpcshell.ini
@@ -0,0 +1,56 @@
+[DEFAULT]
+tags = devtools devtools-memory
+head = head.js ../../../framework/test/shared-redux-head.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_action_diffing_01.js]
+[test_action_diffing_02.js]
+[test_action_diffing_03.js]
+[test_action_diffing_04.js]
+[test_action_diffing_05.js]
+[test_action-clear-snapshots_01.js]
+[test_action-clear-snapshots_02.js]
+[test_action-clear-snapshots_03.js]
+[test_action-clear-snapshots_04.js]
+[test_action-clear-snapshots_05.js]
+[test_action-clear-snapshots_06.js]
+[test_action-export-snapshot.js]
+[test_action-filter-01.js]
+[test_action-filter-02.js]
+[test_action-filter-03.js]
+[test_action-import-snapshot-and-census.js]
+[test_action-import-snapshot-dominator-tree.js]
+[test_action-select-snapshot.js]
+[test_action-set-display.js]
+[test_action-set-display-and-refresh-01.js]
+[test_action-set-display-and-refresh-02.js]
+[test_action-take-census.js]
+[test_action-take-snapshot.js]
+[test_action-take-snapshot-and-census.js]
+[test_action-toggle-inverted.js]
+[test_action-toggle-inverted-and-refresh-01.js]
+[test_action-toggle-inverted-and-refresh-02.js]
+[test_action-toggle-recording-allocations.js]
+[test_dominator_trees_01.js]
+[test_dominator_trees_02.js]
+[test_dominator_trees_03.js]
+[test_dominator_trees_04.js]
+[test_dominator_trees_05.js]
+[test_dominator_trees_06.js]
+[test_dominator_trees_07.js]
+[test_dominator_trees_08.js]
+[test_dominator_trees_09.js]
+[test_dominator_trees_10.js]
+[test_individuals_01.js]
+[test_individuals_02.js]
+[test_individuals_03.js]
+[test_individuals_04.js]
+[test_individuals_05.js]
+[test_individuals_06.js]
+[test_pop_view_01.js]
+[test_tree-map-01.js]
+[test_tree-map-02.js]
+[test_utils.js]
+[test_utils-get-snapshot-totals.js]