summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/test_race_cache_with_network.js
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/test/unit/test_race_cache_with_network.js')
-rw-r--r--netwerk/test/unit/test_race_cache_with_network.js263
1 files changed, 263 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_race_cache_with_network.js b/netwerk/test/unit/test_race_cache_with_network.js
new file mode 100644
index 0000000000..9bb6939558
--- /dev/null
+++ b/netwerk/test/unit/test_race_cache_with_network.js
@@ -0,0 +1,263 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+const PORT = httpserver.identity.primaryPort;
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+let gResponseBody = "blahblah";
+let g200Counter = 0;
+let g304Counter = 0;
+function test_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("ETag", "test-etag1");
+
+ let etag;
+ try {
+ etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ etag = "";
+ }
+
+ if (etag == "test-etag1") {
+ response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+ g304Counter++;
+ } else {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
+ g200Counter++;
+ }
+}
+
+function cached_handler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "max-age=3600");
+ response.setHeader("ETag", "test-etag1");
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
+
+ g200Counter++;
+}
+
+let gResponseCounter = 0;
+let gIsFromCache = 0;
+function checkContent(request, buffer, context, isFromCache) {
+ Assert.equal(buffer, gResponseBody);
+ info(
+ "isRacing: " +
+ request.QueryInterface(Ci.nsICacheInfoChannel).isRacing() +
+ "\n"
+ );
+ gResponseCounter++;
+ if (isFromCache) {
+ gIsFromCache++;
+ }
+ executeSoon(() => {
+ testGenerator.next();
+ });
+}
+
+function run_test() {
+ do_get_profile();
+ // In this test, we manually use |TriggerNetwork| to prove we could send
+ // net and cache reqeust simultaneously. Therefore we should disable
+ // racing in the HttpChannel first.
+ Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
+ httpserver.registerPathHandler("/rcwn", test_handler);
+ httpserver.registerPathHandler("/rcwn_cached", cached_handler);
+ testGenerator.next();
+ do_test_pending();
+}
+
+let testGenerator = testSteps();
+function* testSteps() {
+ /*
+ * In this test, we have a relatively low timeout of 200ms and an assertion that
+ * the timer works properly by checking that the time was greater than 200ms.
+ * With a timer precision of 100ms (for example) we will clamp downwards to 200
+ * and cause the assertion to fail. To resolve this, we hardcode a precision of
+ * 20ms.
+ */
+ Services.prefs.setBoolPref("privacy.reduceTimerPrecision", true);
+ Services.prefs.setIntPref(
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
+ 20000
+ );
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.reduceTimerPrecision");
+ Services.prefs.clearUserPref(
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
+ );
+ });
+
+ // Initial request. Stores the response in the cache.
+ let channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 1);
+ equal(g200Counter, 1, "check number of 200 responses");
+ equal(g304Counter, 0, "check number of 304 responses");
+
+ // Checks that response is returned from the cache, after a 304 response.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 2);
+ equal(g200Counter, 1, "check number of 200 responses");
+ equal(g304Counter, 1, "check number of 304 responses");
+
+ // Checks that delaying the response from the cache works.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(200);
+ let startTime = Date.now();
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ greaterOrEqual(
+ Date.now() - startTime,
+ 200,
+ "Check that timer works properly"
+ );
+ equal(gResponseCounter, 3);
+ equal(g200Counter, 1, "check number of 200 responses");
+ equal(g304Counter, 2, "check number of 304 responses");
+
+ // Checks that we can trigger the cache open immediately, even if the cache delay is set very high.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ do_timeout(50, function () {
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_triggerDelayedOpenCacheEntry();
+ });
+ yield undefined;
+ equal(gResponseCounter, 4);
+ equal(g200Counter, 1, "check number of 200 responses");
+ equal(g304Counter, 3, "check number of 304 responses");
+
+ // Sets a high delay for the cache fetch, and triggers the network activity.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ // Trigger network after 50 ms.
+ yield undefined;
+ equal(gResponseCounter, 5);
+ equal(g200Counter, 2, "check number of 200 responses");
+ equal(g304Counter, 3, "check number of 304 responses");
+
+ // Sets a high delay for the cache fetch, and triggers the network activity.
+ // While the network response is produced, we trigger the cache fetch.
+ // Because the network response was the first, a non-conditional request is sent.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 6);
+ equal(g200Counter, 3, "check number of 200 responses");
+ equal(g304Counter, 3, "check number of 304 responses");
+
+ // Triggers cache open before triggering network.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(5000);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_triggerDelayedOpenCacheEntry();
+ yield undefined;
+ equal(gResponseCounter, 7);
+ equal(g200Counter, 3, "check number of 200 responses");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Load the cached handler so we don't need to revalidate
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 8);
+ equal(g200Counter, 4, "check number of 200 responses");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Make sure response is loaded from the cache, not the network
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 9);
+ equal(g200Counter, 4, "check number of 200 responses");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Cache times out, so we trigger the network
+ gIsFromCache = 0;
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ // trigger network after 50 ms
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 10);
+ equal(gIsFromCache, 0, "should be from the network");
+ equal(g200Counter, 5, "check number of 200 responses");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Cache callback comes back right after network is triggered.
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(55);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ yield undefined;
+ equal(gResponseCounter, 11);
+ info("IsFromCache: " + gIsFromCache + "\n");
+ info("Number of 200 responses: " + g200Counter + "\n");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Set an increasingly high timeout to trigger opening the cache entry
+ // This way we ensure that some of the entries we will get from the network,
+ // and some we will get from the cache.
+ gIsFromCache = 0;
+ for (var i = 0; i < 50; i++) {
+ channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(i * 100);
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(10);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ // This may be racy. The delay was chosen because the distribution of net-cache
+ // results was around 25-25 on my machine.
+ yield undefined;
+ }
+
+ greater(gIsFromCache, 0, "Some of the responses should be from the cache");
+ less(gIsFromCache, 50, "Some of the responses should be from the net");
+
+ httpserver.stop(do_test_finished);
+}