Bug 1508287 - Refactor xpcshell tests using custom principal. r=yulia
authorAlexandre Poirot <poirot.alex@gmail.com>
Thu, 22 Nov 2018 15:23:24 +0000
changeset 504188 fc126a74993d098fb394a1204877833c458c2570
parent 504187 c56b523e11e6fc73804ede8264aed1045a627ab1
child 504189 14c07af09b66f52379a25519edcebf2d8e6e0178
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyulia
bugs1508287
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1508287 - Refactor xpcshell tests using custom principal. r=yulia MozReview-Commit-ID: 3a9IB4XFjff Depends on D12310 Differential Revision: https://phabricator.services.mozilla.com/D12311
devtools/server/tests/unit/head_dbg.js
devtools/server/tests/unit/test_objectgrips-17.js
devtools/server/tests/unit/test_objectgrips-21.js
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -840,25 +840,34 @@ async function setupTestFromUrl(url) {
  *        This test function is called with a dictionary:
  *        - Sandbox debuggee
  *          The custom JS debuggee created for this test. This is a Sandbox using system
  *           principals by default.
  *        - ThreadClient threadClient
  *          A reference to a ThreadClient instance that is attached to the debuggee.
  *        - DebuggerClient client
  *          A reference to the DebuggerClient used to communicated with the RDP server.
+ * @param Object options
+ *        Optional arguments to tweak test environment
+ *        - JSPrincipal principal
+ *          Principal to use for the debuggee.
  */
-function threadClientTest(test) {
+function threadClientTest(test, options = {}) {
+  let { principal } = options;
+  if (!principal) {
+    principal = systemPrincipal;
+  }
+
   async function runThreadClientTestWithServer(server, test) {
     // Setup a server and connect a client to it.
     initTestDebuggerServer(server);
 
     // Create a custom debuggee and register it to the server.
     // We are using a custom Sandbox as debuggee.
-    const debuggee = Cu.Sandbox(systemPrincipal);
+    const debuggee = Cu.Sandbox(principal);
     const scriptName = "debuggee.js";
     debuggee.__name = scriptName;
     server.addTestGlobal(debuggee);
 
     const client = new DebuggerClient(server.connectPipe());
     await client.connect();
 
     // Attach to the fake tab target and retrieve the ThreadClient instance.
--- a/devtools/server/tests/unit/test_objectgrips-17.js
+++ b/devtools/server/tests/unit/test_objectgrips-17.js
@@ -1,240 +1,215 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-shadow, max-nested-callbacks */
 
 "use strict";
 
-var gServer;
-var gDebuggee;
-var gDebuggeeHasXrays;
-var gClient;
-var gThreadClient;
-var gGlobal;
-var gGlobalIsInvisible;
-var gSubsumes;
-var gIsOpaque;
-
-Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
-
-registerCleanupFunction(() => {
-  Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
-});
-
-function run_test() {
-  run_test_with_server(DebuggerServer, function() {
-    run_test_with_server(WorkerDebuggerServer, do_test_finished);
-  });
-  do_test_pending();
-}
-
-async function run_test_with_server(server, callback) {
-  gServer = server;
-  initTestDebuggerServer(server);
-
-  // Run tests with a system principal debuggee.
-  await run_tests_in_principal(systemPrincipal, "test-grips-system-principal");
-
-  // Run tests with a null principal debuggee.
-  await run_tests_in_principal(null, "test-grips-null-principal");
-
-  callback();
-}
-
-async function run_tests_in_principal(debuggeePrincipal, title) {
-  for (gDebuggeeHasXrays of [true, false]) {
-    // Prepare the debuggee.
-    const fullTitle = gDebuggeeHasXrays ? title + "-with-xrays" : title;
-    gDebuggee = Cu.Sandbox(debuggeePrincipal, {wantXrays: gDebuggeeHasXrays});
-    gDebuggee.__name = fullTitle;
-    gServer.addTestGlobal(gDebuggee);
-    gDebuggee.eval(function stopMe() {
-      debugger;
-    }.toString());
-    gClient = new DebuggerClient(gServer.connectPipe());
-    await gClient.connect();
-    const [,, threadClient] = await attachTestTabAndResume(gClient, fullTitle);
-    gThreadClient = threadClient;
-
-    // Test objects created in the debuggee.
-    await testPrincipal(undefined);
-
-    // Test objects created in a system principal new global.
-    await testPrincipal(systemPrincipal);
-
-    // Test objects created in a cross-origin null principal new global.
-    await testPrincipal(null);
-
-    if (debuggeePrincipal === null) {
-      // Test objects created in a same-origin null principal new global.
-      await testPrincipal(Cu.getObjectPrincipal(gDebuggee));
-    }
-
-    // Finish.
-    await gClient.close();
-  }
-}
-
-async function testPrincipal(globalPrincipal) {
+async function testPrincipal(options, globalPrincipal, debuggeeHasXrays) {
+  const { debuggee } = options;
+  let global, subsumes, isOpaque, globalIsInvisible;
   // Create a global object with the specified security principal.
   // If none is specified, use the debuggee.
   if (globalPrincipal === undefined) {
-    gGlobal = gDebuggee;
-    gSubsumes = true;
-    gIsOpaque = false;
-    gGlobalIsInvisible = false;
-    await test();
+    global = debuggee;
+    subsumes = true;
+    isOpaque = false;
+    globalIsInvisible = false;
+    await test(options, { global, subsumes, isOpaque, globalIsInvisible });
     return;
   }
 
-  const debuggeePrincipal = Cu.getObjectPrincipal(gDebuggee);
+  const debuggeePrincipal = Cu.getObjectPrincipal(debuggee);
   const sameOrigin = debuggeePrincipal === globalPrincipal;
-  gSubsumes = sameOrigin || debuggeePrincipal === systemPrincipal;
+  subsumes = sameOrigin || debuggeePrincipal === systemPrincipal;
   for (const globalHasXrays of [true, false]) {
-    gIsOpaque = gSubsumes && globalPrincipal !== systemPrincipal
-                && (sameOrigin && gDebuggeeHasXrays || globalHasXrays);
-    for (gGlobalIsInvisible of [true, false]) {
-      gGlobal = Cu.Sandbox(globalPrincipal, {
+    isOpaque = subsumes && globalPrincipal !== systemPrincipal
+                && (sameOrigin && debuggeeHasXrays || globalHasXrays);
+    for (globalIsInvisible of [true, false]) {
+      global = Cu.Sandbox(globalPrincipal, {
         wantXrays: globalHasXrays,
-        invisibleToDebugger: gGlobalIsInvisible,
+        invisibleToDebugger: globalIsInvisible,
       });
       // Previously, the Sandbox constructor would (bizarrely) waive xrays on
       // the return Sandbox if wantXrays was false. This has now been fixed,
       // but we need to mimic that behavior here to make the test continue
       // to pass.
       if (!globalHasXrays) {
-        gGlobal = Cu.waiveXrays(gGlobal);
+        global = Cu.waiveXrays(global);
       }
-      await test();
+      await test(options, { global, subsumes, isOpaque, globalIsInvisible });
     }
   }
 }
 
-function test() {
+function test({ threadClient, debuggee }, testOptions) {
+  const { global } = testOptions;
   return new Promise(function(resolve) {
-    gThreadClient.addOneTimeListener("paused", async function(event, packet) {
+    threadClient.addOneTimeListener("paused", async function(event, packet) {
       // Get the grips.
       const [proxyGrip, inheritsProxyGrip, inheritsProxy2Grip] = packet.frame.arguments;
 
       // Check the grip of the proxy object.
-      check_proxy_grip(proxyGrip);
+      check_proxy_grip(debuggee, testOptions, proxyGrip);
 
       // Check the prototype and properties of the proxy object.
-      const proxyClient = gThreadClient.pauseGrip(proxyGrip);
+      const proxyClient = threadClient.pauseGrip(proxyGrip);
       const proxyResponse = await proxyClient.getPrototypeAndProperties();
-      check_properties(proxyResponse.ownProperties, true, false);
-      check_prototype(proxyResponse.prototype, true, false);
+      check_properties(testOptions, proxyResponse.ownProperties, true, false);
+      check_prototype(debuggee, testOptions, proxyResponse.prototype, true, false);
 
       // Check the prototype and properties of the object which inherits from the proxy.
-      const inheritsProxyClient = gThreadClient.pauseGrip(inheritsProxyGrip);
+      const inheritsProxyClient = threadClient.pauseGrip(inheritsProxyGrip);
       const inheritsProxyResponse = await inheritsProxyClient.getPrototypeAndProperties();
-      check_properties(inheritsProxyResponse.ownProperties, false, false);
-      check_prototype(inheritsProxyResponse.prototype, false, false);
+      check_properties(testOptions, inheritsProxyResponse.ownProperties, false, false);
+      check_prototype(debuggee, testOptions, inheritsProxyResponse.prototype, false,
+        false);
 
       // The prototype chain was not iterated if the object was inaccessible, so now check
       // another object which inherits from the proxy, but was created in the debuggee.
-      const inheritsProxy2Client = gThreadClient.pauseGrip(inheritsProxy2Grip);
+      const inheritsProxy2Client = threadClient.pauseGrip(inheritsProxy2Grip);
       const inheritsProxy2Response =
         await inheritsProxy2Client.getPrototypeAndProperties();
-      check_properties(inheritsProxy2Response.ownProperties, false, true);
-      check_prototype(inheritsProxy2Response.prototype, false, true);
+      check_properties(testOptions, inheritsProxy2Response.ownProperties, false, true);
+      check_prototype(debuggee, testOptions, inheritsProxy2Response.prototype, false,
+        true);
 
       // Check that none of the above ran proxy traps.
-      strictEqual(gGlobal.trapDidRun, false, "No proxy trap did run.");
+      strictEqual(global.trapDidRun, false, "No proxy trap did run.");
 
       // Resume the debugger and finish the current test.
-      await gThreadClient.resume();
+      await threadClient.resume();
       resolve();
     });
 
-    // Create objects in `gGlobal`, and debug them in `gDebuggee`. They may get various
+    // Create objects in `global`, and debug them in `debuggee`. They may get various
     // kinds of security wrappers, or no wrapper at all.
     // To detect that no proxy trap runs, the proxy handler should define all possible
     // traps, but the list is long and may change. Therefore a second proxy is used as
     // the handler, so that a single `get` trap suffices.
-    gGlobal.eval(`
+    global.eval(`
       var trapDidRun = false;
       var proxy = new Proxy({}, new Proxy({}, {get: (_, trap) => {
         trapDidRun = true;
         throw new Error("proxy trap '" + trap + "' was called.");
       }}));
       var inheritsProxy = Object.create(proxy, {x:{value:1}});
     `);
-    const data = Cu.createObjectIn(gDebuggee, {defineAs: "data"});
-    data.proxy = gGlobal.proxy;
-    data.inheritsProxy = gGlobal.inheritsProxy;
-    gDebuggee.eval(`
+    const data = Cu.createObjectIn(debuggee, {defineAs: "data"});
+    data.proxy = global.proxy;
+    data.inheritsProxy = global.inheritsProxy;
+    debuggee.eval(`
       var inheritsProxy2 = Object.create(data.proxy, {x:{value:1}});
       stopMe(data.proxy, data.inheritsProxy, inheritsProxy2);
     `);
   });
 }
 
-function check_proxy_grip(grip) {
+function check_proxy_grip(debuggee, testOptions, grip) {
+  const { global, isOpaque, subsumes, globalIsInvisible } = testOptions;
   const {preview} = grip;
 
-  if (gGlobal === gDebuggee) {
+  if (global === debuggee) {
     // The proxy has no security wrappers.
     strictEqual(grip.class, "Proxy", "The grip has a Proxy class.");
     ok(grip.proxyTarget, "There is a [[ProxyTarget]] grip.");
     ok(grip.proxyHandler, "There is a [[ProxyHandler]] grip.");
     strictEqual(preview.ownPropertiesLength, 2, "The preview has 2 properties.");
     const target = preview.ownProperties["<target>"].value;
     strictEqual(target, grip.proxyTarget, "<target> contains the [[ProxyTarget]].");
     const handler = preview.ownProperties["<handler>"].value;
     strictEqual(handler, grip.proxyHandler, "<handler> contains the [[ProxyHandler]].");
-  } else if (gIsOpaque) {
+  } else if (isOpaque) {
     // The proxy has opaque security wrappers.
     strictEqual(grip.class, "Opaque", "The grip has an Opaque class.");
     strictEqual(grip.ownPropertyLength, 0, "The grip has no properties.");
-  } else if (!gSubsumes) {
+  } else if (!subsumes) {
     // The proxy belongs to compartment not subsumed by the debuggee.
     strictEqual(grip.class, "Restricted", "The grip has an Restricted class.");
     ok(!("ownPropertyLength" in grip), "The grip doesn't know the number of properties.");
-  } else if (gGlobalIsInvisible) {
+  } else if (globalIsInvisible) {
     // The proxy belongs to an invisible-to-debugger compartment.
     strictEqual(grip.class, "InvisibleToDebugger: Object",
                 "The grip has an InvisibleToDebugger class.");
     ok(!("ownPropertyLength" in grip), "The grip doesn't know the number of properties.");
   } else {
     // The proxy has non-opaque security wrappers.
     strictEqual(grip.class, "Proxy", "The grip has a Proxy class.");
     ok(!("proxyTarget" in grip), "There is no [[ProxyTarget]] grip.");
     ok(!("proxyHandler" in grip), "There is no [[ProxyHandler]] grip.");
     strictEqual(preview.ownPropertiesLength, 0, "The preview has no properties.");
     ok(!("<target>" in preview), "The preview has no <target> property.");
     ok(!("<handler>" in preview), "The preview has no <handler> property.");
   }
 }
 
-function check_properties(props, isProxy, createdInDebuggee) {
+function check_properties(testOptions, props, isProxy, createdInDebuggee) {
+  const { subsumes, globalIsInvisible } = testOptions;
   const ownPropertiesLength = Reflect.ownKeys(props).length;
 
-  if (createdInDebuggee || !isProxy && gSubsumes && !gGlobalIsInvisible) {
+  if (createdInDebuggee || !isProxy && subsumes && !globalIsInvisible) {
     // The debuggee can access the properties.
     strictEqual(ownPropertiesLength, 1, "1 own property was retrieved.");
     strictEqual(props.x.value, 1, "The property has the right value.");
   } else {
     // The debuggee is not allowed to access the object.
     strictEqual(ownPropertiesLength, 0, "No own property could be retrieved.");
   }
 }
 
-function check_prototype(proto, isProxy, createdInDebuggee) {
-  if (gIsOpaque && !gGlobalIsInvisible && !createdInDebuggee) {
+function check_prototype(debuggee, testOptions, proto, isProxy, createdInDebuggee) {
+  const { global, isOpaque, subsumes, globalIsInvisible } = testOptions;
+  if (isOpaque && !globalIsInvisible && !createdInDebuggee) {
     // The object is or inherits from a proxy with opaque security wrappers.
     // The debuggee sees `Object.prototype` when retrieving the prototype.
     strictEqual(proto.class, "Object", "The prototype has a Object class.");
-  } else if (isProxy && gIsOpaque && gGlobalIsInvisible) {
+  } else if (isProxy && isOpaque && globalIsInvisible) {
     // The object is a proxy with opaque security wrappers in an invisible global.
     // The debuggee sees an inaccessible `Object.prototype` when retrieving the prototype.
     strictEqual(proto.class, "InvisibleToDebugger: Object",
                 "The prototype has an InvisibleToDebugger class.");
-  } else if (createdInDebuggee || !isProxy && gSubsumes && !gGlobalIsInvisible) {
+  } else if (createdInDebuggee || !isProxy && subsumes && !globalIsInvisible) {
     // The object inherits from a proxy and has no security wrappers or non-opaque ones.
     // The debuggee sees the proxy when retrieving the prototype.
-    check_proxy_grip(proto);
+    check_proxy_grip(debuggee, { global, isOpaque, subsumes, globalIsInvisible }, proto);
   } else {
     // The debuggee is not allowed to access the object. It sees a null prototype.
     strictEqual(proto.type, "null", "The prototype is null.");
   }
 }
+
+async function run_tests_in_principal(options, debuggeePrincipal, debuggeeHasXrays) {
+  const { debuggee } = options;
+  debuggee.eval(function stopMe(arg1, arg2) {
+    debugger;
+  }.toString());
+
+  // Test objects created in the debuggee.
+  await testPrincipal(options, undefined, debuggeeHasXrays);
+
+  // Test objects created in a system principal new global.
+  await testPrincipal(options, systemPrincipal, debuggeeHasXrays);
+
+  // Test objects created in a cross-origin null principal new global.
+  await testPrincipal(options, null, debuggeeHasXrays);
+
+  if (debuggeePrincipal === null) {
+    // Test objects created in a same-origin null principal new global.
+    await testPrincipal(options, Cu.getObjectPrincipal(debuggee), debuggeeHasXrays);
+  }
+}
+
+// threadClientTest uses systemPrincipal by default, but let's be explicit here.
+add_task(threadClientTest(options => {
+  return run_tests_in_principal(options, systemPrincipal, true);
+}, { principal: systemPrincipal, wantXrays: true }));
+add_task(threadClientTest(options => {
+  return run_tests_in_principal(options, systemPrincipal, false);
+}, { principal: systemPrincipal, wantXrays: false }));
+
+const nullPrincipal = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal);
+add_task(threadClientTest(options => {
+  return run_tests_in_principal(options, nullPrincipal, true);
+}, { principal: nullPrincipal, wantXrays: true }));
+add_task(threadClientTest(options => {
+  return run_tests_in_principal(options, nullPrincipal, false);
+}, { principal: nullPrincipal, wantXrays: false }));
--- a/devtools/server/tests/unit/test_objectgrips-21.js
+++ b/devtools/server/tests/unit/test_objectgrips-21.js
@@ -1,52 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-shadow, max-nested-callbacks */
 
 "use strict";
 
-var gDebuggee;
-var gThreadClient;
-
-Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
-
-registerCleanupFunction(() => {
-  Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
-});
-
-function run_test() {
-  run_test_with_server(DebuggerServer, function() {
-    run_test_with_server(WorkerDebuggerServer, do_test_finished);
-  });
-  do_test_pending();
-}
-
-async function run_test_with_server(server, callback) {
-  initTestDebuggerServer(server);
-  const principals = [
-    ["test-grips-system-principal", systemPrincipal, systemPrincipalTests],
-    ["test-grips-null-principal", null, nullPrincipalTests],
-  ];
-  for (const [title, principal, tests] of principals) {
-    gDebuggee = Cu.Sandbox(principal);
-    gDebuggee.__name = title;
-    server.addTestGlobal(gDebuggee);
-    gDebuggee.eval(function stopMe(arg1, arg2) {
-      debugger;
-    }.toString());
-    const client = new DebuggerClient(server.connectPipe());
-    await client.connect();
-    const [,, threadClient] = await attachTestTabAndResume(client, title);
-    gThreadClient = threadClient;
-    await test_unsafe_grips(principal, tests);
-    await client.close();
-  }
-  callback();
-}
+// Run test_unsafe_grips twice, one against a system principal debuggee
+// and another time with a null principal debuggee
 
 // The following tests work like this:
 // - The specified code is evaluated in a system principal.
 //   `Cu`, `systemPrincipal` and `Services` are provided as global variables.
 // - The resulting object is debugged in a system or null principal debuggee,
 //   depending on in which list the test is placed.
 //   It is tested according to the specified test parameters.
 // - An ordinary object that inherits from the resulting one is also debugged.
@@ -218,32 +182,35 @@ function descriptor(descr) {
   return Object.assign({
     configurable: false,
     writable: false,
     enumerable: false,
     value: undefined,
   }, descr);
 }
 
-async function test_unsafe_grips(principal, tests) {
+async function test_unsafe_grips({ threadClient, debuggee, client }, tests, principal) {
+  debuggee.eval(function stopMe(arg1, arg2) {
+    debugger;
+  }.toString());
   for (let data of tests) {
     await new Promise(function(resolve) {
-      gThreadClient.addOneTimeListener("paused", async function(event, packet) {
+      threadClient.addOneTimeListener("paused", async function(event, packet) {
         const [objGrip, inheritsGrip] = packet.frame.arguments;
         for (const grip of [objGrip, inheritsGrip]) {
           const isUnsafe = grip === objGrip;
           // If `isUnsafe` is true, the parameters in `data` will be used to assert
           // against `objGrip`, the grip of the object `obj` created by the test.
           // Otherwise, the grip will refer to `inherits`, an ordinary object which
           // inherits from `obj`. Then all checks are hardcoded because in every test
           // all methods are expected to work the same on `inheritsGrip`.
 
           check_grip(grip, data, isUnsafe);
 
-          let objClient = gThreadClient.pauseGrip(grip);
+          let objClient = threadClient.pauseGrip(grip);
           let response, slice;
 
           response = await objClient.getPrototypeAndProperties();
           check_properties(response.ownProperties, data, isUnsafe);
           check_symbols(response.ownSymbols, data, isUnsafe);
           check_prototype(response.prototype, data, isUnsafe);
 
           response = await objClient.enumProperties({ignoreIndexedProperties: true});
@@ -276,44 +243,44 @@ async function test_unsafe_grips(princip
           if (data.isFunction && isUnsafe) {
             // For function-related methods, object-client.js checks that the class
             // of the grip is "Function", and if it's not, the method in object.js
             // is not called. But some tests have a grip with a class that is not
             // "Function" (e.g. it's "Proxy") but the DebuggerObject has a "Function"
             // class because the object is callable (despite not being a Function object).
             // So the grip class is changed in order to test the object.js method.
             grip.class = "Function";
-            objClient = gThreadClient.pauseGrip(grip);
+            objClient = threadClient.pauseGrip(grip);
             try {
               response = await objClient.getParameterNames();
               ok(true, "getParameterNames passed. DebuggerObject.class is 'Function'"
                 + "on the object actor");
             } catch (e) {
               ok(false, "getParameterNames failed. DebuggerObject.class may not be"
                 + " 'Function' on the object actor");
             }
           }
         }
 
-        await gThreadClient.resume();
+        await threadClient.resume();
         resolve();
       });
 
       data = {...defaults, ...data};
 
       // Run the code and test the results.
       const sandbox = Cu.Sandbox(systemPrincipal);
       Object.assign(sandbox, {Services, systemPrincipal, Cu});
       sandbox.eval(data.code);
-      gDebuggee.obj = sandbox.obj;
+      debuggee.obj = sandbox.obj;
       const inherits = `Object.create(obj, {
         x: {value: 1},
         [Symbol.for("x")]: {value: 2}
       })`;
-      gDebuggee.eval(`stopMe(obj, ${inherits});`);
+      debuggee.eval(`stopMe(obj, ${inherits});`);
       ok(sandbox.eval(data.afterTest), "Check after test passes");
     });
   }
 }
 
 function check_grip(grip, data, isUnsafe) {
   if (isUnsafe) {
     strictEqual(grip.class, data.class, "The grip has the proper class.");
@@ -387,8 +354,18 @@ function check_prototype(proto, data, is
 
 function check_display_string(str, data, isUnsafe) {
   if (isUnsafe) {
     strictEqual(str, data.string, "The object stringifies correctly.");
   } else {
     strictEqual(str, "[object Object]", "The object stringifies correctly.");
   }
 }
+
+// threadClientTest uses systemPrincipal by default, but let's be explicit here.
+add_task(threadClientTest(options => {
+  return test_unsafe_grips(options, systemPrincipalTests, "system");
+}, { principal: systemPrincipal }));
+
+const nullPrincipal = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal);
+add_task(threadClientTest(options => {
+  return test_unsafe_grips(options, nullPrincipalTests, "null");
+}, { principal: nullPrincipal }));