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";
|
||||
|
||||
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 {
|
||||
// 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() {
|
||||
// Three level structure of Maps pointing to Maps pointing to Maps
|
||||
// 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
|
||||
// level Map, they have registry keys (also called paths) as keys.
|
||||
// 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).
|
||||
this.roots = 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()],
|
||||
MockRegistry.roots = new Map([
|
||||
[
|
||||
Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
|
||||
{ subkeys: new Map(), values: new Map() },
|
||||
],
|
||||
[
|
||||
Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
|
||||
{ subkeys: new Map(), values: 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
|
||||
// attempt to use the registry off the main thread, so we disable that
|
||||
// feature while the mock registry is active.
|
||||
|
|
@ -100,7 +250,7 @@ export class MockRegistry {
|
|||
|
||||
this.cid = MockRegistrar.register(
|
||||
"@mozilla.org/windows-registry-key;1",
|
||||
MockWindowsRegKey
|
||||
() => new MockWindowsRegKey()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +271,7 @@ export class MockRegistry {
|
|||
this.cid = null;
|
||||
}
|
||||
|
||||
getRoot(root) {
|
||||
static getRoot(root) {
|
||||
if (!this.roots.has(root)) {
|
||||
throw new Error(`No such root ${root}`);
|
||||
}
|
||||
|
|
@ -129,17 +279,58 @@ export class MockRegistry {
|
|||
}
|
||||
|
||||
setValue(root, path, name, value) {
|
||||
let rootKey = this.getRoot(root);
|
||||
|
||||
if (!rootKey.has(path)) {
|
||||
rootKey.set(path, new Map());
|
||||
}
|
||||
|
||||
let pathmap = rootKey.get(path);
|
||||
let key = new MockWindowsRegKey();
|
||||
key.create(root, path, Ci.nsIWindowsRegKey.ACCESS_ALL);
|
||||
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 {
|
||||
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,
|
||||
* 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;
|
||||
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).
|
||||
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
|
||||
test_invalidread_functions(testKey);
|
||||
|
||||
//* check that removing/deleting values works
|
||||
test_remove_functions(testKey);
|
||||
|
||||
//* check that creating/enumerating/deleting child keys works
|
||||
test_childkey_functions(testKey);
|
||||
|
||||
//* clean up
|
||||
cleanup_test_run(testKey, keyName);
|
||||
if (cleanup) {
|
||||
cleanup_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) {
|
||||
strictEqual(testKey.childCount, 0);
|
||||
strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false);
|
||||
|
|
|
|||
Loading…
Reference in a new issue