forked from mirrors/gecko-dev
Bug 1869842 - Extend MockRegistry to support types and subkeys/children r=nrishel
Differential Revision: https://phabricator.services.mozilla.com/D199434
This commit is contained in:
parent
ddec4dc371
commit
441bf7db9f
2 changed files with 327 additions and 85 deletions
|
|
@ -4,82 +4,232 @@
|
||||||
|
|
||||||
import { MockRegistrar } from "resource://testing-common/MockRegistrar.sys.mjs";
|
import { MockRegistrar } from "resource://testing-common/MockRegistrar.sys.mjs";
|
||||||
|
|
||||||
|
class MockWindowsRegKey {
|
||||||
|
key = null;
|
||||||
|
|
||||||
|
// --- Overridden nsISupports interface functions ---
|
||||||
|
QueryInterface = ChromeUtils.generateQI(["nsIWindowsRegKey"]);
|
||||||
|
|
||||||
|
#assertKey() {
|
||||||
|
if (this.key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw Components.Exception("invalid registry path", Cr.NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#findOrMaybeCreateKey(root, path, mode, maybeCreateCallback) {
|
||||||
|
let rootKey = MockRegistry.getRoot(root);
|
||||||
|
let parts = path.split("\\");
|
||||||
|
if (parts.some(part => !part.length)) {
|
||||||
|
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = rootKey;
|
||||||
|
for (let part of parts) {
|
||||||
|
if (!key.subkeys.has(part)) {
|
||||||
|
maybeCreateCallback(key.subkeys, part);
|
||||||
|
}
|
||||||
|
key = key.subkeys.get(part);
|
||||||
|
}
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Overridden nsIWindowsRegKey interface functions ---
|
||||||
|
open(root, path, mode) {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
this.#findOrMaybeCreateKey(root, path, mode, (subkeys, part) => {
|
||||||
|
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
create(root, path, mode) {
|
||||||
|
this.#findOrMaybeCreateKey(root, path, mode, (subkeys, part) =>
|
||||||
|
subkeys.set(part, { subkeys: new Map(), values: new Map() })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.key = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get valueCount() {
|
||||||
|
this.#assertKey();
|
||||||
|
return this.key.values.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasValue(name) {
|
||||||
|
this.#assertKey();
|
||||||
|
return this.key.values.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#getValuePair(name, expectedType = null) {
|
||||||
|
this.#assertKey();
|
||||||
|
if (!this.key.values.has(name)) {
|
||||||
|
throw Components.Exception("invalid value name", Cr.NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
let [value, type] = this.key.values.get(name);
|
||||||
|
if (expectedType && type !== expectedType) {
|
||||||
|
throw Components.Exception("unexpected value type", Cr.NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
return [value, type];
|
||||||
|
}
|
||||||
|
|
||||||
|
getValueType(name) {
|
||||||
|
let [, type] = this.#getValuePair(name);
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValueName(index) {
|
||||||
|
if (!this.key || index >= this.key.values.size) {
|
||||||
|
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
let names = Array.from(this.key.values.keys());
|
||||||
|
return names[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
readStringValue(name) {
|
||||||
|
let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_STRING);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
readIntValue(name) {
|
||||||
|
let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_INT);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
readInt64Value(name) {
|
||||||
|
let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_INT64);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
readBinaryValue(name) {
|
||||||
|
let [value] = this.#getValuePair(name, Ci.nsIWindowsRegKey.TYPE_BINARY);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#writeValuePair(name, value, type) {
|
||||||
|
this.#assertKey();
|
||||||
|
this.key.values.set(name, [value, type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeStringValue(name, value) {
|
||||||
|
this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeIntValue(name, value) {
|
||||||
|
this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeInt64Value(name, value) {
|
||||||
|
this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_INT64);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBinaryValue(name, value) {
|
||||||
|
this.#writeValuePair(name, value, Ci.nsIWindowsRegKey.TYPE_BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeValue(name) {
|
||||||
|
this.#assertKey();
|
||||||
|
this.key.values.delete(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
get childCount() {
|
||||||
|
this.#assertKey();
|
||||||
|
return this.key.subkeys.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildName(index) {
|
||||||
|
if (!this.key || index >= this.key.values.size) {
|
||||||
|
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
let names = Array.from(this.key.subkeys.keys());
|
||||||
|
return names[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChild(name) {
|
||||||
|
this.#assertKey();
|
||||||
|
return this.key.subkeys.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeChild(name) {
|
||||||
|
this.#assertKey();
|
||||||
|
let child = this.key.subkeys.get(name);
|
||||||
|
|
||||||
|
if (!child) {
|
||||||
|
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
if (child.subkeys.size > 0) {
|
||||||
|
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.key.subkeys.delete(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#findOrMaybeCreateChild(name, mode, maybeCreateCallback) {
|
||||||
|
this.#assertKey();
|
||||||
|
if (name.split("\\").length > 1) {
|
||||||
|
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
if (!this.key.subkeys.has(name)) {
|
||||||
|
maybeCreateCallback(this.key.subkeys, name);
|
||||||
|
}
|
||||||
|
// This won't wrap in the same way as `Cc["@mozilla.org/windows-registry-key;1"].createInstance(nsIWindowsRegKey);`.
|
||||||
|
let subKey = new MockWindowsRegKey();
|
||||||
|
subKey.key = this.key.subkeys.get(name);
|
||||||
|
return subKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
openChild(name, mode) {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
return this.#findOrMaybeCreateChild(name, mode, (subkeys, part) => {
|
||||||
|
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createChild(name, mode) {
|
||||||
|
return this.#findOrMaybeCreateChild(name, mode, (subkeys, part) =>
|
||||||
|
subkeys.set(part, { subkeys: new Map(), values: new Map() })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class MockRegistry {
|
export class MockRegistry {
|
||||||
|
// All instances of `MockRegistry` share a single data-store; this is
|
||||||
|
// conceptually parallel to the Windows registry, which is a shared global
|
||||||
|
// resource. It would be possible to have separate data-stores for separate
|
||||||
|
// instances with a little adjustment to `MockWindowsRegKey`.
|
||||||
|
//
|
||||||
|
// Top-level map is indexed by roots. A "key" is an object that has
|
||||||
|
// `subkeys` and `values`, both maps indexed by strings. Subkey items are
|
||||||
|
// again "key" objects. Value items are `[value, type]` pairs.
|
||||||
|
//
|
||||||
|
// In pseudo-code:
|
||||||
|
//
|
||||||
|
// {Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER:
|
||||||
|
// {subkeys:
|
||||||
|
// {child: {subkeys: {}, values: {key: ["string_value", Ci.nsIWindowsRegKey.TYPE_STRING]}}},
|
||||||
|
// values: {}
|
||||||
|
// },
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
static roots;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Three level structure of Maps pointing to Maps pointing to Maps
|
MockRegistry.roots = new Map([
|
||||||
// this.roots is the top of the structure and has ROOT_KEY_* values
|
[
|
||||||
// as keys. Maps at the second level are the values of the first
|
Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
|
||||||
// level Map, they have registry keys (also called paths) as keys.
|
{ subkeys: new Map(), values: new Map() },
|
||||||
// Third level maps are the values in second level maps, they have
|
],
|
||||||
// map registry names to corresponding values (which in this implementation
|
[
|
||||||
// are always strings).
|
Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
|
||||||
this.roots = new Map([
|
{ subkeys: new Map(), values: new Map() },
|
||||||
[Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, new Map()],
|
],
|
||||||
[Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, new Map()],
|
[
|
||||||
[Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT, new Map()],
|
Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT,
|
||||||
|
{ subkeys: new Map(), values: new Map() },
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let registry = this;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a mock nsIWindowsRegistry implementation. It only implements a
|
|
||||||
* subset of the interface used in tests. In particular, only values
|
|
||||||
* of type string are supported.
|
|
||||||
*/
|
|
||||||
function MockWindowsRegKey() {}
|
|
||||||
MockWindowsRegKey.prototype = {
|
|
||||||
values: null,
|
|
||||||
|
|
||||||
// --- Overridden nsISupports interface functions ---
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIWindowsRegKey"]),
|
|
||||||
|
|
||||||
// --- Overridden nsIWindowsRegKey interface functions ---
|
|
||||||
open(root, path) {
|
|
||||||
let rootKey = registry.getRoot(root);
|
|
||||||
if (!rootKey.has(path)) {
|
|
||||||
rootKey.set(path, new Map());
|
|
||||||
}
|
|
||||||
this.values = rootKey.get(path);
|
|
||||||
},
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.values = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
get valueCount() {
|
|
||||||
if (!this.values) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
|
||||||
}
|
|
||||||
return this.values.size;
|
|
||||||
},
|
|
||||||
|
|
||||||
hasValue(name) {
|
|
||||||
if (!this.values) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.values.has(name);
|
|
||||||
},
|
|
||||||
|
|
||||||
getValueType() {
|
|
||||||
return Ci.nsIWindowsRegKey.TYPE_STRING;
|
|
||||||
},
|
|
||||||
|
|
||||||
getValueName(index) {
|
|
||||||
if (!this.values || index >= this.values.size) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
|
|
||||||
}
|
|
||||||
let names = Array.from(this.values.keys());
|
|
||||||
return names[index];
|
|
||||||
},
|
|
||||||
|
|
||||||
readStringValue(name) {
|
|
||||||
if (!this.values) {
|
|
||||||
throw new Error("invalid registry path");
|
|
||||||
}
|
|
||||||
return this.values.get(name);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// See bug 1688838 - nsNotifyAddrListener::CheckAdaptersAddresses might
|
// See bug 1688838 - nsNotifyAddrListener::CheckAdaptersAddresses might
|
||||||
// attempt to use the registry off the main thread, so we disable that
|
// attempt to use the registry off the main thread, so we disable that
|
||||||
// feature while the mock registry is active.
|
// feature while the mock registry is active.
|
||||||
|
|
@ -100,7 +250,7 @@ export class MockRegistry {
|
||||||
|
|
||||||
this.cid = MockRegistrar.register(
|
this.cid = MockRegistrar.register(
|
||||||
"@mozilla.org/windows-registry-key;1",
|
"@mozilla.org/windows-registry-key;1",
|
||||||
MockWindowsRegKey
|
() => new MockWindowsRegKey()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +271,7 @@ export class MockRegistry {
|
||||||
this.cid = null;
|
this.cid = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRoot(root) {
|
static getRoot(root) {
|
||||||
if (!this.roots.has(root)) {
|
if (!this.roots.has(root)) {
|
||||||
throw new Error(`No such root ${root}`);
|
throw new Error(`No such root ${root}`);
|
||||||
}
|
}
|
||||||
|
|
@ -129,17 +279,58 @@ export class MockRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(root, path, name, value) {
|
setValue(root, path, name, value) {
|
||||||
let rootKey = this.getRoot(root);
|
let key = new MockWindowsRegKey();
|
||||||
|
key.create(root, path, Ci.nsIWindowsRegKey.ACCESS_ALL);
|
||||||
if (!rootKey.has(path)) {
|
|
||||||
rootKey.set(path, new Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
let pathmap = rootKey.get(path);
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
pathmap.delete(name);
|
try {
|
||||||
|
key.removeValue(name);
|
||||||
|
} catch (e) {
|
||||||
|
if (
|
||||||
|
!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)
|
||||||
|
) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
pathmap.set(name, value);
|
key.writeStringValue(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump given `key` (or, if not given, all roots), and all its value and its
|
||||||
|
* subkeys recursively, using the given function to `printOneLine`.
|
||||||
|
*/
|
||||||
|
static dump(key = null, indent = "", printOneLine = console.log) {
|
||||||
|
let types = new Map([
|
||||||
|
[1, "REG_SZ"],
|
||||||
|
[3, "REG_BINARY"],
|
||||||
|
[4, "REG_DWORD"],
|
||||||
|
[11, "REG_QWORD"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
let roots = [
|
||||||
|
"ROOT_KEY_LOCAL_MACHINE",
|
||||||
|
"ROOT_KEY_CURRENT_USER",
|
||||||
|
"ROOT_KEY_CLASSES_ROOT",
|
||||||
|
];
|
||||||
|
for (let root of roots) {
|
||||||
|
printOneLine(indent + root);
|
||||||
|
this.dump(
|
||||||
|
this.roots.get(Ci.nsIWindowsRegKey[root]),
|
||||||
|
" " + indent,
|
||||||
|
printOneLine
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let [k, v] of key.values.entries()) {
|
||||||
|
let [value, type] = v;
|
||||||
|
printOneLine(`${indent}${k}: ${value} (${types.get(type)})`);
|
||||||
|
}
|
||||||
|
for (let [k, child] of key.subkeys.entries()) {
|
||||||
|
printOneLine(indent + k);
|
||||||
|
this.dump(child, " " + indent, printOneLine);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,47 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
const { MockRegistry } = ChromeUtils.importESModule(
|
||||||
|
"resource://testing-common/MockRegistry.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
const nsIWindowsRegKey = Ci.nsIWindowsRegKey;
|
const nsIWindowsRegKey = Ci.nsIWindowsRegKey;
|
||||||
let regKeyComponent = Cc["@mozilla.org/windows-registry-key;1"];
|
let regKeyComponent = Cc["@mozilla.org/windows-registry-key;1"];
|
||||||
|
|
||||||
function run_test() {
|
// We run these tests twice: once against the native Windows registry, and a
|
||||||
|
// second time against `MockRegistry`. This gives some confidence that
|
||||||
|
// `MockRegistry` implements the same APIs as the native Windows registry.
|
||||||
|
|
||||||
|
// This order is important: the native registry test must run first, because
|
||||||
|
// otherwise we would end up running the `MockRegistry` test twice without
|
||||||
|
// checking it against the native Windows registry.
|
||||||
|
|
||||||
|
add_task(function test_native_registry() {
|
||||||
|
info("Running test for native Windows registry");
|
||||||
|
run_one_test();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function test_MockRegistry() {
|
||||||
|
let registry = new MockRegistry();
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
registry.shutdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Before, there's nothing -- just the 3 roots (HKLM, HKCU, HKCR).
|
||||||
|
let linesBefore = [];
|
||||||
|
MockRegistry.dump(null, "", linesBefore.push.bind(linesBefore));
|
||||||
|
strictEqual(linesBefore.length, 3);
|
||||||
|
|
||||||
|
info("Running test for MockRegistry");
|
||||||
|
run_one_test({ cleanup: false });
|
||||||
|
|
||||||
|
// After, there's something -- more than just the roots.
|
||||||
|
let linesAfter = [];
|
||||||
|
MockRegistry.dump(null, "", linesAfter.push.bind(linesAfter));
|
||||||
|
strictEqual(linesAfter.length, 8);
|
||||||
|
});
|
||||||
|
|
||||||
|
function run_one_test({ cleanup = true } = {}) {
|
||||||
//* create a key structure in a spot that's normally writable (somewhere under HKCU).
|
//* create a key structure in a spot that's normally writable (somewhere under HKCU).
|
||||||
let testKey = regKeyComponent.createInstance(nsIWindowsRegKey);
|
let testKey = regKeyComponent.createInstance(nsIWindowsRegKey);
|
||||||
|
|
||||||
|
|
@ -28,11 +65,16 @@ function run_test() {
|
||||||
//* check that the get* functions fail with the right exception codes if we ask for the wrong type or if the value name doesn't exist at all
|
//* check that the get* functions fail with the right exception codes if we ask for the wrong type or if the value name doesn't exist at all
|
||||||
test_invalidread_functions(testKey);
|
test_invalidread_functions(testKey);
|
||||||
|
|
||||||
|
//* check that removing/deleting values works
|
||||||
|
test_remove_functions(testKey);
|
||||||
|
|
||||||
//* check that creating/enumerating/deleting child keys works
|
//* check that creating/enumerating/deleting child keys works
|
||||||
test_childkey_functions(testKey);
|
test_childkey_functions(testKey);
|
||||||
|
|
||||||
//* clean up
|
//* clean up
|
||||||
cleanup_test_run(testKey, keyName);
|
if (cleanup) {
|
||||||
|
cleanup_test_run(testKey, keyName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup_test_run(testKey, keyName) {
|
function setup_test_run(testKey, keyName) {
|
||||||
|
|
@ -152,6 +194,15 @@ function test_invalidread_functions(testKey) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_remove_functions(testKey) {
|
||||||
|
strictEqual(testKey.valueCount, 4);
|
||||||
|
testKey.removeValue(TESTDATA_INT64NAME);
|
||||||
|
strictEqual(testKey.valueCount, 3);
|
||||||
|
|
||||||
|
testKey.removeValue(TESTDATA_INT64NAME);
|
||||||
|
strictEqual(testKey.valueCount, 3);
|
||||||
|
}
|
||||||
|
|
||||||
function test_childkey_functions(testKey) {
|
function test_childkey_functions(testKey) {
|
||||||
strictEqual(testKey.childCount, 0);
|
strictEqual(testKey.childCount, 0);
|
||||||
strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false);
|
strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue