summaryrefslogtreecommitdiffstats
path: root/browser/components/doh/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/doh/test/unit')
-rw-r--r--browser/components/doh/test/unit/head.js101
-rw-r--r--browser/components/doh/test/unit/test_DNSLookup.js62
-rw-r--r--browser/components/doh/test/unit/test_LookupAggregator.js162
-rw-r--r--browser/components/doh/test/unit/test_TRRRacer.js209
-rw-r--r--browser/components/doh/test/unit/test_heuristics.js80
-rw-r--r--browser/components/doh/test/unit/xpcshell.ini12
6 files changed, 626 insertions, 0 deletions
diff --git a/browser/components/doh/test/unit/head.js b/browser/components/doh/test/unit/head.js
new file mode 100644
index 0000000000..eb19dac8a1
--- /dev/null
+++ b/browser/components/doh/test/unit/head.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+let h2Port, trrServer1, trrServer2, trrList;
+let DNSLookup, LookupAggregator, TRRRacer;
+
+function readFile(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(file, -1, 0, 0);
+ let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+ fstream.close();
+ return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+ let certFile = do_get_file(filename, false);
+ let pem = readFile(certFile)
+ .replace(/-----BEGIN CERTIFICATE-----/, "")
+ .replace(/-----END CERTIFICATE-----/, "")
+ .replace(/[\r\n]/g, "");
+ certdb.addCertFromBase64(pem, trustString);
+}
+
+function ensureNoTelemetry() {
+ let events =
+ Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).parent || [];
+ events = events.filter(e => e[1] == "security.doh.trrPerformance");
+ Assert.ok(!events.length);
+}
+
+function setup() {
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.http.http2.enabled", true);
+
+ // use the h2 server as DOH provider
+ trrServer1 = `https://foo.example.com:${h2Port}/doh?responseIP=1.1.1.1`;
+ trrServer2 = `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`;
+ trrList = [trrServer1, trrServer2];
+ // make all native resolve calls "secretly" resolve localhost instead
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ Services.prefs.setIntPref("doh-rollout.trrRace.randomSubdomainCount", 2);
+
+ Services.prefs.setCharPref(
+ "doh-rollout.trrRace.popularDomains",
+ "foo.example.com., bar.example.com."
+ );
+
+ Services.prefs.setCharPref(
+ "doh-rollout.trrRace.canonicalDomain",
+ "firefox-dns-perf-test.net."
+ );
+
+ let TRRPerformance = ChromeUtils.importESModule(
+ "resource:///modules/TRRPerformance.sys.mjs"
+ );
+
+ DNSLookup = TRRPerformance.DNSLookup;
+ LookupAggregator = TRRPerformance.LookupAggregator;
+ TRRRacer = TRRPerformance.TRRRacer;
+
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.http.http2.enabled");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ });
+}
diff --git a/browser/components/doh/test/unit/test_DNSLookup.js b/browser/components/doh/test/unit/test_DNSLookup.js
new file mode 100644
index 0000000000..5951445f13
--- /dev/null
+++ b/browser/components/doh/test/unit/test_DNSLookup.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+add_task(setup);
+
+add_task(async function test_SuccessfulRandomDNSLookup() {
+ let deferred = PromiseUtils.defer();
+ let lookup = new DNSLookup(
+ null,
+ trrServer1,
+ (request, record, status, usedDomain, retryCount) => {
+ deferred.resolve({ request, record, status, usedDomain, retryCount });
+ }
+ );
+ lookup.doLookup();
+ let result = await deferred.promise;
+ Assert.ok(result.usedDomain.endsWith(".firefox-dns-perf-test.net."));
+ Assert.equal(result.status, Cr.NS_OK);
+ Assert.ok(result.record.QueryInterface(Ci.nsIDNSAddrRecord));
+ Assert.ok(result.record.IsTRR());
+ Assert.greater(result.record.trrFetchDuration, 0);
+ Assert.equal(result.retryCount, 1);
+});
+
+add_task(async function test_SuccessfulSpecifiedDNSLookup() {
+ let deferred = PromiseUtils.defer();
+ let lookup = new DNSLookup(
+ "foo.example.com",
+ trrServer1,
+ (request, record, status, usedDomain, retryCount) => {
+ deferred.resolve({ request, record, status, usedDomain, retryCount });
+ }
+ );
+ lookup.doLookup();
+ let result = await deferred.promise;
+ Assert.equal(result.usedDomain, "foo.example.com");
+ Assert.equal(result.status, Cr.NS_OK);
+ Assert.ok(result.record.QueryInterface(Ci.nsIDNSAddrRecord));
+ Assert.ok(result.record.IsTRR());
+ Assert.greater(result.record.trrFetchDuration, 0);
+ Assert.equal(result.retryCount, 1);
+});
+
+add_task(async function test_FailedDNSLookup() {
+ let deferred = PromiseUtils.defer();
+ let lookup = new DNSLookup(
+ null,
+ `https://foo.example.com:${h2Port}/doh?responseIP=none`,
+ (request, record, status, usedDomain, retryCount) => {
+ deferred.resolve({ request, record, status, usedDomain, retryCount });
+ }
+ );
+ lookup.doLookup();
+ let result = await deferred.promise;
+ Assert.ok(result.usedDomain.endsWith(".firefox-dns-perf-test.net."));
+ Assert.notEqual(result.status, Cr.NS_OK);
+ Assert.equal(result.record, null);
+ Assert.equal(result.retryCount, 3);
+});
diff --git a/browser/components/doh/test/unit/test_LookupAggregator.js b/browser/components/doh/test/unit/test_LookupAggregator.js
new file mode 100644
index 0000000000..c2050a8ca8
--- /dev/null
+++ b/browser/components/doh/test/unit/test_LookupAggregator.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+add_task(setup);
+
+async function helper_SuccessfulLookupAggregator(
+ networkUnstable = false,
+ captivePortal = false
+) {
+ let deferred = PromiseUtils.defer();
+ let aggregator = new LookupAggregator(() => deferred.resolve(), trrList);
+ // The aggregator's domain list should correctly reflect our set
+ // prefs for number of random subdomains (2) and the list of
+ // popular domains.
+ Assert.equal(aggregator.domains[0], null);
+ Assert.equal(aggregator.domains[1], null);
+ Assert.equal(aggregator.domains[2], "foo.example.com.");
+ Assert.equal(aggregator.domains[3], "bar.example.com.");
+ Assert.equal(aggregator.totalLookups, 8); // 2 TRRs * 4 domains.
+
+ if (networkUnstable) {
+ aggregator.markUnstableNetwork();
+ }
+ if (captivePortal) {
+ aggregator.markCaptivePortal();
+ }
+ aggregator.run();
+ await deferred.promise;
+ Assert.ok(!aggregator.aborted);
+ Assert.equal(aggregator.networkUnstable, networkUnstable);
+ Assert.equal(aggregator.captivePortal, captivePortal);
+ Assert.equal(aggregator.results.length, aggregator.totalLookups);
+
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).parent;
+ Assert.ok(events);
+ events = events.filter(e => e[1] == "security.doh.trrPerformance");
+ Assert.equal(events.length, aggregator.totalLookups);
+
+ for (let event of events) {
+ info(JSON.stringify(event));
+ Assert.equal(event[1], "security.doh.trrPerformance");
+ Assert.equal(event[2], "resolved");
+ Assert.equal(event[3], "record");
+ Assert.equal(event[4], "success");
+ }
+
+ // We only need to check the payload of each event from here on.
+ events = events.map(e => e[5]);
+
+ for (let trr of [trrServer1, trrServer2]) {
+ // There should be two results for random subdomains.
+ let results = aggregator.results.filter(r => {
+ return r.trr == trr && r.domain.endsWith(".firefox-dns-perf-test.net.");
+ });
+ Assert.equal(results.length, 2);
+
+ for (let result of results) {
+ Assert.ok(result.domain.endsWith(".firefox-dns-perf-test.net."));
+ Assert.equal(result.trr, trr);
+ Assert.ok(Components.isSuccessCode(result.status));
+ Assert.greater(result.time, 0);
+ Assert.equal(result.retryCount, 1);
+
+ let matchingEvents = events.filter(
+ e => e.domain == result.domain && e.trr == result.trr
+ );
+ Assert.equal(matchingEvents.length, 1);
+ let e = matchingEvents.pop();
+ for (let key of Object.keys(result)) {
+ Assert.equal(e[key], result[key].toString());
+ }
+ Assert.equal(e.networkUnstable, networkUnstable.toString());
+ Assert.equal(e.captivePortal, captivePortal.toString());
+ }
+
+ // There should be two results for the popular domains.
+ results = aggregator.results.filter(r => {
+ return r.trr == trr && !r.domain.endsWith(".firefox-dns-perf-test.net.");
+ });
+ Assert.equal(results.length, 2);
+
+ Assert.ok(
+ [results[0].domain, results[1].domain].includes("foo.example.com.")
+ );
+ Assert.ok(
+ [results[0].domain, results[1].domain].includes("bar.example.com.")
+ );
+ for (let result of results) {
+ Assert.equal(result.trr, trr);
+ Assert.equal(result.status, Cr.NS_OK);
+ Assert.greater(result.time, 0);
+ Assert.equal(result.retryCount, 1);
+
+ let matchingEvents = events.filter(
+ e => e.domain == result.domain && e.trr == result.trr
+ );
+ Assert.equal(matchingEvents.length, 1);
+ let e = matchingEvents.pop();
+ for (let key of Object.keys(result)) {
+ Assert.equal(e[key], result[key].toString());
+ }
+ Assert.equal(e.networkUnstable, networkUnstable.toString());
+ Assert.equal(e.captivePortal, captivePortal.toString());
+ }
+ }
+
+ Services.telemetry.clearEvents();
+}
+
+add_task(async function test_SuccessfulLookupAggregator() {
+ await helper_SuccessfulLookupAggregator(false, false);
+ await helper_SuccessfulLookupAggregator(false, true);
+ await helper_SuccessfulLookupAggregator(true, false);
+ await helper_SuccessfulLookupAggregator(true, true);
+});
+
+add_task(async function test_AbortedLookupAggregator() {
+ let deferred = PromiseUtils.defer();
+ let aggregator = new LookupAggregator(() => deferred.resolve(), trrList);
+ // The aggregator's domain list should correctly reflect our set
+ // prefs for number of random subdomains (2) and the list of
+ // popular domains.
+ Assert.equal(aggregator.domains[0], null);
+ Assert.equal(aggregator.domains[1], null);
+ Assert.equal(aggregator.domains[2], "foo.example.com.");
+ Assert.equal(aggregator.domains[3], "bar.example.com.");
+ Assert.equal(aggregator.totalLookups, 8); // 2 TRRs * 4 domains.
+
+ // The aggregator should never call the onComplete callback. To test
+ // this, race the deferred promise with a 3 second timeout. The timeout
+ // should win, since the deferred promise should never resolve.
+ let timeoutPromise = new Promise(resolve => {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => resolve("timeout"), 3000);
+ });
+ aggregator.run();
+ aggregator.abort();
+ let winner = await Promise.race([deferred.promise, timeoutPromise]);
+ Assert.equal(winner, "timeout");
+ Assert.ok(aggregator.aborted);
+ Assert.ok(!aggregator.networkUnstable);
+ Assert.ok(!aggregator.captivePortal);
+
+ // Ensure we send no telemetry for an aborted run!
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).parent;
+ Assert.ok(
+ !events || !events.filter(e => e[1] == "security.doh.trrPerformance").length
+ );
+});
diff --git a/browser/components/doh/test/unit/test_TRRRacer.js b/browser/components/doh/test/unit/test_TRRRacer.js
new file mode 100644
index 0000000000..d9a0455ba0
--- /dev/null
+++ b/browser/components/doh/test/unit/test_TRRRacer.js
@@ -0,0 +1,209 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+add_task(setup);
+
+add_task(async function test_TRRRacer_cleanRun() {
+ let deferred = PromiseUtils.defer();
+ let racer = new TRRRacer(() => {
+ deferred.resolve();
+ deferred.resolved = true;
+ }, trrList);
+ racer.run();
+
+ await deferred.promise;
+ Assert.equal(racer._retryCount, 1);
+
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).parent;
+ Assert.ok(events);
+ events = events.filter(e => e[1] == "security.doh.trrPerformance");
+ Assert.equal(events.length, racer._aggregator.totalLookups);
+
+ Services.telemetry.clearEvents();
+
+ // Simulate network changes and ensure no re-runs since it's already complete.
+ async function testNetworkChange(captivePortal = false) {
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login");
+ } else {
+ Services.obs.notifyObservers(null, "network:link-status-changed", "down");
+ }
+
+ Assert.ok(!racer._aggregator.aborted);
+
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login-success");
+ } else {
+ Services.obs.notifyObservers(null, "network:link-status-changed", "up");
+ }
+
+ Assert.equal(racer._retryCount, 1);
+ ensureNoTelemetry();
+
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login-abort");
+ }
+ }
+
+ testNetworkChange(false);
+ testNetworkChange(true);
+});
+
+async function test_TRRRacer_networkFlux_helper(captivePortal = false) {
+ let deferred = PromiseUtils.defer();
+ let racer = new TRRRacer(() => {
+ deferred.resolve();
+ deferred.resolved = true;
+ }, trrList);
+ racer.run();
+
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login");
+ } else {
+ Services.obs.notifyObservers(null, "network:link-status-changed", "down");
+ }
+
+ Assert.ok(racer._aggregator.aborted);
+ ensureNoTelemetry();
+ Assert.equal(racer._retryCount, 1);
+ Assert.ok(!deferred.resolved);
+
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login-success");
+ } else {
+ Services.obs.notifyObservers(null, "network:link-status-changed", "up");
+ }
+
+ Assert.ok(!racer._aggregator.aborted);
+ await deferred.promise;
+
+ Assert.equal(racer._retryCount, 2);
+
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).parent;
+ Assert.ok(events);
+ events = events.filter(e => e[1] == "security.doh.trrPerformance");
+ Assert.equal(events.length, racer._aggregator.totalLookups);
+
+ Services.telemetry.clearEvents();
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login-abort");
+ }
+}
+
+add_task(async function test_TRRRacer_networkFlux() {
+ await test_TRRRacer_networkFlux_helper(false);
+ await test_TRRRacer_networkFlux_helper(true);
+});
+
+async function test_TRRRacer_maxRetries_helper(captivePortal = false) {
+ let deferred = PromiseUtils.defer();
+ let racer = new TRRRacer(() => {
+ deferred.resolve();
+ deferred.resolved = true;
+ }, trrList);
+ racer.run();
+ info("ran new racer");
+ // Start at i = 1 since we're already at retry #1.
+ for (let i = 1; i < 5; ++i) {
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login");
+ } else {
+ Services.obs.notifyObservers(null, "network:link-status-changed", "down");
+ }
+
+ info("notified observers");
+
+ Assert.ok(racer._aggregator.aborted);
+ ensureNoTelemetry();
+ Assert.equal(racer._retryCount, i);
+ Assert.ok(!deferred.resolved);
+
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login-success");
+ } else {
+ Services.obs.notifyObservers(null, "network:link-status-changed", "up");
+ }
+ }
+
+ // Simulate a "down" network event and ensure we still send telemetry
+ // since we've maxed out our retry count.
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login");
+ } else {
+ Services.obs.notifyObservers(null, "network:link-status-changed", "down");
+ }
+ Assert.ok(!racer._aggregator.aborted);
+ await deferred.promise;
+ Assert.equal(racer._retryCount, 5);
+
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ true
+ ).parent;
+ Assert.ok(events);
+ events = events.filter(e => e[1] == "security.doh.trrPerformance");
+ Assert.equal(events.length, racer._aggregator.totalLookups);
+
+ Services.telemetry.clearEvents();
+ if (captivePortal) {
+ Services.obs.notifyObservers(null, "captive-portal-login-abort");
+ }
+}
+
+add_task(async function test_TRRRacer_maxRetries() {
+ await test_TRRRacer_maxRetries_helper(false);
+ await test_TRRRacer_maxRetries_helper(true);
+});
+
+add_task(async function test_TRRRacer_getFastestTRRFromResults() {
+ let results = [
+ { trr: "trr1", time: 10 },
+ { trr: "trr2", time: 100 },
+ { trr: "trr1", time: 1000 },
+ { trr: "trr2", time: 110 },
+ { trr: "trr3", time: -1 },
+ { trr: "trr4", time: -1 },
+ { trr: "trr4", time: -1 },
+ { trr: "trr4", time: 1 },
+ { trr: "trr4", time: 1 },
+ { trr: "trr5", time: 10 },
+ { trr: "trr5", time: 20 },
+ { trr: "trr5", time: 1000 },
+ ];
+ let racer = new TRRRacer(undefined, trrList);
+ let fastest = racer._getFastestTRRFromResults(results);
+ // trr1's geometric mean is 100
+ // trr2's geometric mean is 110
+ // trr3 has no valid times, excluded
+ // trr4 has 50% invalid times, excluded
+ // trr5's geometric mean is ~58.5, it's the winner.
+ Assert.equal(fastest, "trr5");
+
+ // When no valid entries are available, undefined is the default output.
+ results = [
+ { trr: "trr1", time: -1 },
+ { trr: "trr2", time: -1 },
+ ];
+
+ fastest = racer._getFastestTRRFromResults(results);
+ Assert.equal(fastest, undefined);
+
+ // When passing `returnRandomDefault = true`, verify that both TRRs are
+ // possible outputs. The probability that the randomization is working
+ // correctly and we consistently get the same output after 50 iterations is
+ // 0.5^50 ~= 8.9*10^-16.
+ let firstResult = racer._getFastestTRRFromResults(results, true);
+ while (racer._getFastestTRRFromResults(results, true) == firstResult) {
+ continue;
+ }
+ Assert.ok(true, "Both TRRs were possible outputs when all results invalid.");
+});
diff --git a/browser/components/doh/test/unit/test_heuristics.js b/browser/components/doh/test/unit/test_heuristics.js
new file mode 100644
index 0000000000..a6a6c9b6c9
--- /dev/null
+++ b/browser/components/doh/test/unit/test_heuristics.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+let cid;
+
+async function SetMockParentalControlEnabled(aEnabled) {
+ if (cid) {
+ MockRegistrar.unregister(cid);
+ }
+
+ let parentalControlsService = {
+ parentalControlsEnabled: aEnabled,
+ QueryInterface: ChromeUtils.generateQI(["nsIParentalControlsService"]),
+ };
+ cid = MockRegistrar.register(
+ "@mozilla.org/parental-controls-service;1",
+ parentalControlsService
+ );
+}
+
+registerCleanupFunction(() => {
+ if (cid) {
+ MockRegistrar.unregister(cid);
+ }
+});
+
+add_task(setup);
+
+add_task(async function test_parentalControls() {
+ let DoHHeuristics = ChromeUtils.importESModule(
+ "resource:///modules/DoHHeuristics.sys.mjs"
+ );
+
+ let parentalControls = DoHHeuristics.parentalControls;
+
+ Assert.equal(
+ await parentalControls(),
+ "enable_doh",
+ "By default, parental controls should be disabled and doh should be enabled"
+ );
+
+ SetMockParentalControlEnabled(false);
+
+ Assert.equal(
+ await parentalControls(),
+ "enable_doh",
+ "Mocked parental controls service is disabled; doh is enabled"
+ );
+
+ SetMockParentalControlEnabled(true);
+
+ Assert.equal(
+ await parentalControls(),
+ "enable_doh",
+ "Default value of mocked parental controls service is disabled; doh is enabled"
+ );
+
+ SetMockParentalControlEnabled(false);
+
+ Assert.equal(
+ await parentalControls(),
+ "enable_doh",
+ "Mocked parental controls service is disabled; doh is enabled"
+ );
+
+ MockRegistrar.unregister(cid);
+
+ Assert.equal(
+ await parentalControls(),
+ "enable_doh",
+ "By default, parental controls should be disabled and doh should be enabled"
+ );
+});
diff --git a/browser/components/doh/test/unit/xpcshell.ini b/browser/components/doh/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..7b3d7e31cf
--- /dev/null
+++ b/browser/components/doh/test/unit/xpcshell.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+skip-if = toolkit == 'android' # bug 1730213
+head = head.js
+firefox-appdir = browser
+support-files =
+ ../../../../../netwerk/test/unit/http2-ca.pem
+
+[test_heuristics.js]
+[test_DNSLookup.js]
+skip-if = debug # Bug 1617845
+[test_LookupAggregator.js]
+[test_TRRRacer.js]