Bug 1120715 - Part 4: Add tests for Request.cache; r=bkelly
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 01 Mar 2016 17:34:31 -0500
changeset 288315 f58317eb7f23
parent 288314 1308f7beeaa1
child 288316 1a97c346298f
push id73383
push usereakhgari@mozilla.com
push dateFri, 11 Mar 2016 19:41:28 +0000
treeherdermozilla-inbound@53638cfd818d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs1120715
milestone48.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 1120715 - Part 4: Add tests for Request.cache; r=bkelly
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/fetch/api/request/request-idl.html.ini
testing/web-platform/meta/fetch/api/request/request-structure.html.ini
testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js
testing/web-platform/tests/fetch/api/request/request-cache.html
testing/web-platform/tests/fetch/api/request/resources/cache.py
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -15525,16 +15525,20 @@
         "path": "fetch/api/redirect/redirect-mode-worker.html",
         "url": "/fetch/api/redirect/redirect-mode-worker.html"
       },
       {
         "path": "fetch/api/redirect/redirect-mode.html",
         "url": "/fetch/api/redirect/redirect-mode.html"
       },
       {
+        "path": "fetch/api/request/request-cache.html",
+        "url": "/fetch/api/request/request-cache.html"
+      },
+      {
         "path": "fetch/api/request/request-clone.sub.html",
         "url": "/fetch/api/request/request-clone.sub.html"
       },
       {
         "path": "fetch/api/request/request-consume.html",
         "url": "/fetch/api/request/request-consume.html"
       },
       {
@@ -34730,16 +34734,23 @@
     "deleted": [],
     "items": {
       "testharness": {
         "dom/collections/HTMLCollection-supported-property-indices.html": [
           {
             "path": "dom/collections/HTMLCollection-supported-property-indices.html",
             "url": "/dom/collections/HTMLCollection-supported-property-indices.html"
           }
+        ],
+        "fetch/api/request/request-cache.html": [
+          {
+            "path": "fetch/api/request/request-cache.html",
+            "timeout": "long",
+            "url": "/fetch/api/request/request-cache.html"
+          }
         ]
       }
     },
     "reftest_nodes": {}
   },
   "reftest_nodes": {
     "2dcontext/building-paths/canvas_complexshapes_arcto_001.htm": [
       {
--- a/testing/web-platform/meta/fetch/api/request/request-idl.html.ini
+++ b/testing/web-platform/meta/fetch/api/request/request-idl.html.ini
@@ -1,26 +1,20 @@
 [request-idl.html]
   type: testharness
   [Request interface: attribute type]
     expected: FAIL
 
   [Request interface: attribute destination]
     expected: FAIL
 
-  [Request interface: attribute cache]
-    expected: FAIL
-
   [Request interface: attribute integrity]
     expected: FAIL
 
   [Request interface: new Request("") must inherit property "type" with the proper type (3)]
     expected: FAIL
 
   [Request interface: new Request("") must inherit property "destination" with the proper type (4)]
     expected: FAIL
 
-  [Request interface: new Request("") must inherit property "cache" with the proper type (9)]
-    expected: FAIL
-
   [Request interface: new Request("") must inherit property "integrity" with the proper type (11)]
     expected: FAIL
 
--- a/testing/web-platform/meta/fetch/api/request/request-structure.html.ini
+++ b/testing/web-platform/meta/fetch/api/request/request-structure.html.ini
@@ -1,14 +1,11 @@
 [request-structure.html]
   type: testharness
   [Check type attribute]
     expected: FAIL
 
   [Check destination attribute]
     expected: FAIL
 
-  [Check cache attribute]
-    expected: FAIL
-
   [Check integrity attribute]
     expected: FAIL
 
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html
@@ -422,10 +422,45 @@ async_test(function(t) {
             'Fragment Not Found',
             'Service worker should not expose URL fragments.');
           frame.remove();
           return service_worker_unregister_and_done(t, scope);
         })
       .catch(unreached_rejection(t));
   }, 'Service Worker must not expose FetchEvent URL fragments.');
 
+async_test(function(t) {
+    var scope = 'resources/simple.html?cache';
+    var frame;
+    var cacheTypes = [
+      undefined, 'default', 'no-store', 'reload', 'no-cache', 'force-cache'
+    ];
+    service_worker_unregister_and_register(t, worker, scope)
+      .then(function(reg) {
+          return wait_for_state(t, reg.installing, 'activated');
+        })
+      .then(function() { return with_iframe(scope); })
+      .then(function(f) {
+          frame = f;
+          var tests = cacheTypes.map(function(type) {
+            return new Promise(function(resolve) {
+                return frame.contentWindow.fetch(scope + '=' + type,
+                                                 {cache: type})
+                  .then(function(response) { return response.text(); })
+                  .then(function(response_text) {
+                      var expected = (type === undefined) ? 'default' : type;
+                      assert_equals(response_text, expected,
+                                    'Service Worker should respond to fetch with the correct type');
+                    })
+                  .then(resolve);
+              });
+          });
+          return Promise.all(tests);
+        })
+      .then(function() {
+          frame.remove();
+          return service_worker_unregister_and_done(t, scope);
+        })
+      .catch(unreached_rejection(t));
+  }, 'Service Worker responds to fetch event with the correct cache types');
+
 </script>
 </body>
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js
@@ -86,32 +86,37 @@ function handleFragmentCheck(event) {
   if (event.request.url.indexOf('#') === -1) {
     body = 'Fragment Not Found';
   } else {
     body = 'Fragment Found';
   }
   event.respondWith(new Response(body));
 }
 
+function handleCache(event) {
+  event.respondWith(new Response(event.request.cache));
+}
+
 self.addEventListener('fetch', function(event) {
     var url = event.request.url;
     var handlers = [
       { pattern: '?string', fn: handleString },
       { pattern: '?blob', fn: handleBlob },
       { pattern: '?referrerFull', fn: handleReferrerFull },
       { pattern: '?referrerPolicy', fn: handleReferrerPolicy },
       { pattern: '?referrer', fn: handleReferrer },
       { pattern: '?clientId', fn: handleClientId },
       { pattern: '?ignore', fn: function() {} },
       { pattern: '?null', fn: handleNullBody },
       { pattern: '?fetch', fn: handleFetch },
       { pattern: '?form-post', fn: handleFormPost },
       { pattern: '?multiple-respond-with', fn: handleMultipleRespondWith },
       { pattern: '?used-check', fn: handleUsedCheck },
-      { pattern: '?fragment-check', fn: handleFragmentCheck }
+      { pattern: '?fragment-check', fn: handleFragmentCheck },
+      { pattern: '?cache', fn: handleCache },
     ];
 
     var handler = null;
     for (var i = 0; i < handlers.length; ++i) {
       if (url.indexOf(handlers[i].pattern) != -1) {
         handler = handlers[i];
         break;
       }
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/request-cache.html
@@ -0,0 +1,306 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Request cache</title>
+    <meta name="help" href="https://fetch.spec.whatwg.org/#request">
+    <meta name="timeout" content="long">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script>
+    var tests = [
+      {
+        name: 'RequestCache "default" mode checks the cache for previously cached content and goes to the network for stale responses',
+        state: "stale",
+        request_cache: ["default", "default"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "default" mode checks the cache for previously cached content and avoids going to the network if a fresh response exists',
+        state: "fresh",
+        request_cache: ["default", "default"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "no-cache" mode revalidates stale responses found in the cache',
+        state: "stale",
+        request_cache: ["default", "no-cache"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+        expected_max_age_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "no-cache" mode revalidates fresh responses found in the cache',
+        state: "fresh",
+        request_cache: ["default", "no-cache"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+        expected_max_age_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for stale responses',
+        state: "stale",
+        request_cache: ["default", "force-cache"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for fresh responses',
+        state: "fresh",
+        request_cache: ["default", "force-cache"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found',
+        state: "stale",
+        request_cache: ["force-cache"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found',
+        state: "fresh",
+        request_cache: ["force-cache"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary',
+        state: "stale",
+        vary: "*",
+        request_cache: ["default", "force-cache"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary',
+        state: "fresh",
+        vary: "*",
+        request_cache: ["default", "force-cache"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network',
+        state: "stale",
+        request_cache: ["force-cache", "default"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network',
+        state: "fresh",
+        request_cache: ["force-cache", "default"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [false],
+      },
+      {
+        name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless',
+        state: "stale",
+        request_cache: ["default", "no-store"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless',
+        state: "fresh",
+        request_cache: ["default", "no-store"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "no-store" mode does not store the response in the cache',
+        state: "stale",
+        request_cache: ["no-store", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [true, false],
+      },
+      {
+        name: 'RequestCache "no-store" mode does not store the response in the cache',
+        state: "fresh",
+        request_cache: ["no-store", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [true, false],
+      },
+      {
+        name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache',
+        state: "stale",
+        cache_control: "no-store",
+        request_cache: ["default", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache',
+        state: "fresh",
+        cache_control: "no-store",
+        request_cache: ["default", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, false],
+      },
+      {
+        name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless',
+        state: "stale",
+        request_cache: ["default", "reload"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless',
+        state: "fresh",
+        request_cache: ["default", "reload"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+      {
+        name: 'RequestCache "reload" mode does store the response in the cache',
+        state: "stale",
+        request_cache: ["reload", "default"],
+        expected_validation_headers: [false, true],
+        expected_no_cache_headers: [true, false],
+      },
+      {
+        name: 'RequestCache "reload" mode does store the response in the cache',
+        state: "fresh",
+        request_cache: ["reload", "default"],
+        expected_validation_headers: [false],
+        expected_no_cache_headers: [true],
+      },
+      {
+        name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored',
+        state: "stale",
+        request_cache: ["default", "reload", "default"],
+        expected_validation_headers: [false, false, true],
+        expected_no_cache_headers: [false, true, false],
+      },
+      {
+        name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored',
+        state: "fresh",
+        request_cache: ["default", "reload", "default"],
+        expected_validation_headers: [false, false],
+        expected_no_cache_headers: [false, true],
+      },
+    ];
+    function make_url(uuid, id, value, content, info) {
+      var now = new Date();
+      var dates = {
+        fresh: new Date(now.getFullYear() + 1, now.getMonth(), now.getDay()).toGMTString(),
+        stale: new Date(now.getFullYear() - 1, now.getMonth(), now.getDay()).toGMTString(),
+      };
+      var vary = "";
+      if ("vary" in info) {
+        vary = "&vary=" + info.vary;
+      }
+      var cache_control = "";
+      if ("cache_control" in info) {
+        cache_control = "&cache_control=" + info.cache_control;
+      }
+      return "resources/cache.py?token=" + uuid +
+             "&content=" + content +
+             "&" + id + "=" + value +
+             "&expires=" + dates[info.state] + vary + cache_control;
+    }
+    function server_state(uuid) {
+      return fetch("resources/cache.py?querystate&token=" + uuid)
+        .then(function(response) {
+          return response.text();
+        }).then(function(text) {
+          return JSON.parse(text);
+        });
+    }
+    function populate_cache(url, content, cache) {
+      return fetch(url, {cache: cache})
+        .then(function(response) {
+          assert_equals(response.status, 200);
+          assert_equals(response.statusText, "OK");
+          return response.text();
+        }).then(function(text) {
+          assert_equals(text, content);
+        });
+    }
+    function make_test(type, info) {
+      return function(test) {
+        var uuid = token();
+        var identifier = (type == "tag" ? Math.random() : new Date().toGMTString());
+        var content = Math.random().toString();
+        var url = make_url(uuid, type, identifier, content, info);
+        var fetch_functions = [function() {
+          return populate_cache(url, content, info.request_cache[0]);
+        }];
+        for (var i = 1; i < info.request_cache.length; ++i) {
+          fetch_functions.push(function(idx) {
+            return fetch(url, {cache: info.request_cache[idx]})
+              .then(function(response) {
+                assert_equals(response.status, 200);
+                assert_equals(response.statusText, "OK");
+                return response.text();
+              }).then(function(text) {
+                assert_equals(text, content);
+              });
+          });
+        }
+        var i = 0;
+        function run_next_step() {
+          if (fetch_functions.length) {
+            return fetch_functions.shift()(i++)
+              .then(run_next_step);
+          } else {
+            return Promise.resolve();
+          }
+        }
+        return run_next_step()
+          .then(function() {
+            // Now, query the server state
+            return server_state(uuid);
+          }).then(function(state) {
+            var expectedState = [];
+            info.expected_validation_headers.forEach(function (validate) {
+              if (validate) {
+                if (type == "tag") {
+                  expectedState.push({"If-None-Match": '"' + identifier + '"'});
+                } else {
+                  expectedState.push({"If-Modified-Since": identifier});
+                }
+              } else {
+                expectedState.push({});
+              }
+            });
+            for (var i = 0; i < info.expected_no_cache_headers.length; ++i) {
+              if (info.expected_no_cache_headers[i]) {
+                expectedState[i]["Pragma"] = "no-cache";
+                expectedState[i]["Cache-Control"] = "no-cache";
+              }
+            }
+            if ("expected_max_age_headers" in info) {
+              for (var i = 0; i < info.expected_max_age_headers.length; ++i) {
+                if (info.expected_max_age_headers[i]) {
+                  expectedState[i]["Cache-Control"] = "max-age=0";
+                }
+              }
+            }
+            assert_equals(state.length, expectedState.length);
+            for (var i = 0; i < state.length; ++i) {
+              for (var header in state[i]) {
+                assert_equals(state[i][header], expectedState[i][header]);
+                delete expectedState[i][header];
+              }
+              for (var header in expectedState[i]) {
+                assert_false(header in state[i]);
+              }
+            }
+          });
+      };
+    }
+    tests.forEach(function(info) {
+      promise_test(make_test("tag", info), info.name + " with Etag and " + info.state + " response");
+      promise_test(make_test("date", info), info.name + " with date and " + info.state + " response");
+    });
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/request/resources/cache.py
@@ -0,0 +1,51 @@
+def main(request, response):
+    token = request.GET.first("token", None)
+    if "querystate" in request.GET:
+        from json import JSONEncoder
+        response.headers.set("Content-Type", "text/plain")
+        return JSONEncoder().encode(request.server.stash.take(token))
+    content = request.GET.first("content", None)
+    tag = request.GET.first("tag", None)
+    date = request.GET.first("date", None)
+    expires = request.GET.first("expires", None)
+    vary = request.GET.first("vary", None)
+    cc = request.GET.first("cache_control", None)
+    inm = request.headers.get("If-None-Match", None)
+    ims = request.headers.get("If-Modified-Since", None)
+    pragma = request.headers.get("Pragma", None)
+    cache_control = request.headers.get("Cache-Control", None)
+
+    server_state = request.server.stash.take(token)
+    if not server_state:
+        server_state = []
+    state = dict()
+    if inm:
+        state["If-None-Match"] = inm
+    if ims:
+        state["If-Modified-Since"] = ims
+    if pragma:
+        state["Pragma"] = pragma
+    if cache_control:
+        state["Cache-Control"] = cache_control
+    server_state.append(state)
+    request.server.stash.put(token, server_state)
+
+    if tag:
+        response.headers.set("ETag", '"%s"' % tag)
+    elif date:
+        response.headers.set("Last-Modified", date)
+    if expires:
+        response.headers.set("Expires", expires)
+    if vary:
+        response.headers.set("Vary", vary)
+    if cc:
+        response.headers.set("Cache-Control", cc)
+
+    if ((inm is not None and inm == tag) or
+        (ims is not None and ims == date)):
+        response.status = (304, "Not Modified")
+        return ""
+    else:
+        response.status = (200, "OK")
+        response.headers.set("Content-Type", "text/plain")
+        return content