diff options
Diffstat (limited to 'netwerk/test/unit/test_race_cache_with_network.js')
-rw-r--r-- | netwerk/test/unit/test_race_cache_with_network.js | 273 |
1 files changed, 273 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..3e34c6af46 --- /dev/null +++ b/netwerk/test/unit/test_race_cache_with_network.js @@ -0,0 +1,273 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { HttpServer } = ChromeUtils.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +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 | 200: ${g200Counter}, 304: ${g304Counter}` + ); + equal( + g304Counter, + 4, + `check number of 304 responses | 200: ${g200Counter}, 304: ${g304Counter}` + ); + + // 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); +} |