Bug 1472828 [wpt PR 11758] - Network Error Logging: Add web platform tests, a=testonly
authorDouglas Creager <dcreager@chromium.org>
Wed, 22 Aug 2018 13:18:42 +0000
changeset 433095 3d9ba45c5fc51add474e6639b49d94dc6314a0c9
parent 433094 e02d5a5d5498376588d1162f36a8440e8f4ec76b
child 433096 17b39e682fe103ce049ea7b4950d18273b776a13
push id34499
push usercsabou@mozilla.com
push dateThu, 23 Aug 2018 21:40:51 +0000
treeherdermozilla-central@49b70f7e6817 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1472828, 11758, 748549, 1106518, 584093
milestone63.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 1472828 [wpt PR 11758] - Network Error Logging: Add web platform tests, a=testonly Automatic update from web-platform-testsNetwork Error Logging: Add web platform tests This adds web-platform-tests for Network Error Logging. They should be suitable to upstream into the cross-platform WPT repo. Bug: 748549 Change-Id: I24098aeec8488c34a5f9239309264e9534dca6e4 Reviewed-on: https://chromium-review.googlesource.com/1106518 Reviewed-by: Peter Beverloo <peter@chromium.org> Reviewed-by: Philip J├Ągenstedt <foolip@chromium.org> Commit-Queue: Douglas Creager <dcreager@chromium.org> Cr-Commit-Position: refs/heads/master@{#584093} -- wpt-commits: d0b86dfcab07829407425bad98287757818f96c5 wpt-pr: 11758
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/network-error-logging/META.yml
testing/web-platform/tests/network-error-logging/README.md
testing/web-platform/tests/network-error-logging/no-report-on-failed-cors-preflight.https.html
testing/web-platform/tests/network-error-logging/no-report-on-subdomain-404.https.html
testing/web-platform/tests/network-error-logging/no-report-on-subdomain-success.https.html
testing/web-platform/tests/network-error-logging/reports-are-not-observable.https.html
testing/web-platform/tests/network-error-logging/sends-report-on-404.https.html
testing/web-platform/tests/network-error-logging/sends-report-on-subdomain-dns-failure.https.html
testing/web-platform/tests/network-error-logging/sends-report-on-success-with-subdomain-policy.https.html
testing/web-platform/tests/network-error-logging/sends-report-on-success.https.html
testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png
testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png.sub.headers
testing/web-platform/tests/network-error-logging/support/lock.py
testing/web-platform/tests/network-error-logging/support/nel.sub.js
testing/web-platform/tests/network-error-logging/support/no-policy-pass.png
testing/web-platform/tests/network-error-logging/support/pass.png
testing/web-platform/tests/network-error-logging/support/pass.png.sub.headers
testing/web-platform/tests/network-error-logging/support/report.py
testing/web-platform/tests/network-error-logging/support/subdomains-pass.png
testing/web-platform/tests/network-error-logging/support/subdomains-pass.png.sub.headers
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -291683,16 +291683,76 @@
      {}
     ]
    ],
    "netinfo/META.yml": [
     [
      {}
     ]
    ],
+   "network-error-logging/META.yml": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/README.md": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/clear-policy-pass.png": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/clear-policy-pass.png.sub.headers": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/lock.py": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/nel.sub.js": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/no-policy-pass.png": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/pass.png": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/pass.png.sub.headers": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/report.py": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/subdomains-pass.png": [
+    [
+     {}
+    ]
+   ],
+   "network-error-logging/support/subdomains-pass.png.sub.headers": [
+    [
+     {}
+    ]
+   ],
    "notifications/META.yml": [
     [
      {}
     ]
    ],
    "notifications/common.js": [
     [
      {}
@@ -368846,16 +368906,64 @@
     ]
    ],
    "netinfo/netinfo-basics.html": [
     [
      "/netinfo/netinfo-basics.html",
      {}
     ]
    ],
+   "network-error-logging/no-report-on-failed-cors-preflight.https.html": [
+    [
+     "/network-error-logging/no-report-on-failed-cors-preflight.https.html",
+     {}
+    ]
+   ],
+   "network-error-logging/no-report-on-subdomain-404.https.html": [
+    [
+     "/network-error-logging/no-report-on-subdomain-404.https.html",
+     {}
+    ]
+   ],
+   "network-error-logging/no-report-on-subdomain-success.https.html": [
+    [
+     "/network-error-logging/no-report-on-subdomain-success.https.html",
+     {}
+    ]
+   ],
+   "network-error-logging/reports-are-not-observable.https.html": [
+    [
+     "/network-error-logging/reports-are-not-observable.https.html",
+     {}
+    ]
+   ],
+   "network-error-logging/sends-report-on-404.https.html": [
+    [
+     "/network-error-logging/sends-report-on-404.https.html",
+     {}
+    ]
+   ],
+   "network-error-logging/sends-report-on-subdomain-dns-failure.https.html": [
+    [
+     "/network-error-logging/sends-report-on-subdomain-dns-failure.https.html",
+     {}
+    ]
+   ],
+   "network-error-logging/sends-report-on-success-with-subdomain-policy.https.html": [
+    [
+     "/network-error-logging/sends-report-on-success-with-subdomain-policy.https.html",
+     {}
+    ]
+   ],
+   "network-error-logging/sends-report-on-success.https.html": [
+    [
+     "/network-error-logging/sends-report-on-success.https.html",
+     {}
+    ]
+   ],
    "notifications/constructor-basic.html": [
     [
      "/notifications/constructor-basic.html",
      {}
     ]
    ],
    "notifications/constructor-invalid.html": [
     [
@@ -610623,16 +610731,96 @@
   "netinfo/idlharness.any.js": [
    "4610508b69b1e30db574e04f2651b40cb8b646f2",
    "testharness"
   ],
   "netinfo/netinfo-basics.html": [
    "7d08e12280a881508c862ebaaeaa40a099cf8e35",
    "testharness"
   ],
+  "network-error-logging/META.yml": [
+   "bc063177d1d14febb1c4bdb86e70cfdce8ca0b5b",
+   "support"
+  ],
+  "network-error-logging/README.md": [
+   "7cf2c6fdceed95b3911deb69542a6820acda479d",
+   "support"
+  ],
+  "network-error-logging/no-report-on-failed-cors-preflight.https.html": [
+   "3a35651b4ef549a0510a83df923fd6b9d642b97c",
+   "testharness"
+  ],
+  "network-error-logging/no-report-on-subdomain-404.https.html": [
+   "462f99e842317cf36c4d7a76ad13af00b1692d15",
+   "testharness"
+  ],
+  "network-error-logging/no-report-on-subdomain-success.https.html": [
+   "5fd6d4fb41231c5ca5f345b890927c5d1b9411ab",
+   "testharness"
+  ],
+  "network-error-logging/reports-are-not-observable.https.html": [
+   "35ab4f3c23507617c4f26981339741d9b3c385be",
+   "testharness"
+  ],
+  "network-error-logging/sends-report-on-404.https.html": [
+   "38bdc014501e90f5a99bae1ac0d433191f557afb",
+   "testharness"
+  ],
+  "network-error-logging/sends-report-on-subdomain-dns-failure.https.html": [
+   "8913857af8acb01760589b6a7546a110a359f192",
+   "testharness"
+  ],
+  "network-error-logging/sends-report-on-success-with-subdomain-policy.https.html": [
+   "fce12cd3e96cf327222faea4bdaeba5835f4f4ce",
+   "testharness"
+  ],
+  "network-error-logging/sends-report-on-success.https.html": [
+   "68fddaa0c70b8dd0fd22194b351ba1157f836bdc",
+   "testharness"
+  ],
+  "network-error-logging/support/clear-policy-pass.png": [
+   "2fa1e0ac0663a65deae6602621521cc2844b93de",
+   "support"
+  ],
+  "network-error-logging/support/clear-policy-pass.png.sub.headers": [
+   "1085b8a987c56fd8b0412f4032f6957e58447ace",
+   "support"
+  ],
+  "network-error-logging/support/lock.py": [
+   "8c88250bde00b4a62cc99131fdaa09e97f716369",
+   "support"
+  ],
+  "network-error-logging/support/nel.sub.js": [
+   "c6b4783bd94c00579047627b6c2b137478ae1c2e",
+   "support"
+  ],
+  "network-error-logging/support/no-policy-pass.png": [
+   "2fa1e0ac0663a65deae6602621521cc2844b93de",
+   "support"
+  ],
+  "network-error-logging/support/pass.png": [
+   "2fa1e0ac0663a65deae6602621521cc2844b93de",
+   "support"
+  ],
+  "network-error-logging/support/pass.png.sub.headers": [
+   "70796e913ace97d4d1a21ac0b1c19f6fbb6d01fc",
+   "support"
+  ],
+  "network-error-logging/support/report.py": [
+   "7c05b51b9eb011f4d32bd5e774f6a0d3ead2cd9c",
+   "support"
+  ],
+  "network-error-logging/support/subdomains-pass.png": [
+   "2fa1e0ac0663a65deae6602621521cc2844b93de",
+   "support"
+  ],
+  "network-error-logging/support/subdomains-pass.png.sub.headers": [
+   "50124b8cfcdfd6efe91a0613ec34d3c8ca0a10dc",
+   "support"
+  ],
   "notifications/META.yml": [
    "2cb7972705c7b9ef00375dfa4258e92edb15fb21",
    "support"
   ],
   "notifications/body-basic-manual.html": [
    "a479917aeef9f3b777d152148a3b75cb8df7f494",
    "manual"
   ],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+  - dcreager
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/README.md
@@ -0,0 +1,74 @@
+# Network Error Logging
+
+The tests in this directory exercise the user agent's implementation of [Network
+Error Logging](https://w3c.github.io/network-error-logging/) and
+[Reporting](https://w3c.github.io/reporting/).
+
+## Collector
+
+Each test case generates a unique `reportID` that is used to distinguish the NEL
+reports generated by that test case.
+
+The [support/report.py][] file is a [Python file handler][] that can be used as
+a Reporting collector.  Its default operation is to save any reports that it
+receives into the [stash][].  If you pass in the optional `op` URL parameter,
+with a value of `retrieve_report`, it will instead return a list of all of the
+reports received for a particular `reportID`.
+
+[Python file handler]: https://wptserve.readthedocs.io/en/latest/handlers.html#python-file-handlers
+[stash]: https://wptserve.readthedocs.io/en/latest/stash.html
+[support/report.py]: support/report.py
+
+## Installing NEL policies
+
+NEL reports are only generated if the user agent has received a NEL policy for
+the origin of the request.  The current request counts; if its response contains
+a policy, that policy is used for the current request and all future requests,
+modulo the policy's `max_age` field.
+
+Most of the test cases will therefore make a request or two to install NEL
+policies, and then make another request that should or should not be covered by
+those policies.  It will then assert that NEL reports were or were not created,
+as required by the spec.
+
+The [support][] directory contains several images, each of which defines a
+particular "kind" of NEL policy (e.g., `include_subdomains` set vs unset, no
+policy at all, etc.).  The [support/nel.sub.js][] file contains helper
+JavaScript methods for requesting those images, so that the test cases
+themselves are more descriptive.
+
+[support]: support
+[support/nel.sub.js]: support/nel.sub.js
+
+## Avoiding spurious reports
+
+NEL policies apply to **all** future requests to the origin.  We therefore serve
+all of the test case's "infrastructure" (the test case itself,
+[support/report.py][] and [support/nel.sub.js][]) on a different origin than
+the requests that exercise the NEL implementation.  That ensures that we don't
+have to wade through NEL reports about the infrastructure when verifying the NEL
+reports about the requests that we care about.
+
+## Browser configuration
+
+You must configure your browser's Reporting implementation to upload reports for
+a request immediately.  The test cases do not currently have any timeouts; they
+assume that as soon as the Fetch API promise resolves, any NEL reports for the
+request have already been uploaded.
+
+## Test parallelism
+
+Because NEL policies are stored in a global cache in the user agent, we need to
+run the tests in this directory serially instead of in parallel.  We implement a
+simple spin-lock in [support/lock.py][] to ensure that only one test is allowed
+to perform any NEL-related requests at a time.
+
+[support/lock.py]: support/lock.py
+
+## CORS preflights
+
+Reporting uploads are subject to CORS preflights.  We want to test normal
+operation (when preflight requests succeed) as well as failures of the CORS
+preflight logic in the user agent.  To support this, our test collector is
+configured to always reject the CORS preflight for a single domain (www2), and
+to always grant the CORS preflight for all other test subdomains.
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/no-report-on-failed-cors-preflight.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>
+    Test that NEL reports are not sent if the CORS preflight fails
+  </title>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+  <script>
+    nel_test(async t => {
+      // Make a request to a resource whose response headers include a NEL
+      // policy, but where the report uploader will reject the CORS preflight.
+      await fetchResourceWithBasicPolicy('www2');
+      // Because the CORS preflight is rejected, we should never receive a
+      // report about the request.
+      assert_false(await reportExists({
+        url: getURLForResourceWithBasicPolicy('www2'),
+        type: "network-error",
+      }));
+    });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/no-report-on-subdomain-404.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>
+    Test that include_subdomains policies do NOT report HTTP errors
+  </title>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+  <script>
+    nel_test(async t => {
+      // Make a request to a resource whose response headers include an
+      // include_subdomains NEL policy.
+      await fetchResourceWithIncludeSubdomainsPolicy();
+      // Make a request to another resource on a subdomain of the above.  This
+      // resource doesn't exist, so the server should return a 404.
+      await fetchMissingResource('www');
+      // The include_subdomains policy that we just received should NOT cover
+      // the second request, since include_subdomains policies can only report
+      // on DNS errors.
+      assert_false(await reportExists({
+        url: getURLForMissingResource('www'),
+        type: "network-error",
+      }));
+    });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/no-report-on-subdomain-success.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>
+    Test that include_subdomains policies do NOT report successful requests
+  </title>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+  <script>
+    nel_test(async t => {
+      // Make a request to a resource whose response headers include an
+      // include_subdomains NEL policy.
+      await fetchResourceWithIncludeSubdomainsPolicy();
+      // Make a request to another resource on a subdomain of the above, which
+      // does not define its own NEL policy.
+      await fetchResourceWithNoPolicy('www');
+      // The include_subdomains policy that we just received should NOT cover
+      // the second request, since include_subdomains policies can only report
+      // on DNS errors.
+      assert_false(await reportExists({
+        url: getURLForResourceWithNoPolicy('www'),
+        type: "network-error",
+      }));
+    });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/reports-are-not-observable.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>
+    Test that NEL reports are not observable from JavaScript
+  </title>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+  <script>
+    nel_test(async t => {
+      // Register an observer for NEL reports.
+      var observer = new ReportingObserver((reports, _) => {
+        assert_unreached("NEL reports should not be observable");
+      }, {"types": ["network-error"]});
+      observer.observe();
+      // Make a request to a resource whose response headers include a NEL
+      // policy.  If NEL reports are observable, this will queue a task that
+      // calls the observer function above (which we don't want).
+      await fetchResourceWithBasicPolicy();
+      // Wait for one second to give any observer callback task a chance to
+      // fire.
+      await new Promise(resolve => t.step_timeout(resolve, 1000 /* msec */));
+    });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-404.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>
+    Test that NEL reports are sent for HTTP errors
+  </title>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+  <script>
+    nel_test(async t => {
+      // Make a request to a resource whose response headers include a NEL
+      // policy.
+      await fetchResourceWithBasicPolicy();
+      // Make a request to another resource on the same domain.  This resource
+      // doesn't exist, so the server should return a 404.
+      await fetchMissingResource();
+      // The 404 won't contain its own NEL policy, but the policy we received in
+      // the first request should cover the second request, too, since they're
+      // at the same origin, so the collector should have received a report
+      // about it.
+      assert_true(await reportExists({
+        url: getURLForMissingResource(),
+        user_agent: navigator.userAgent,
+        type: "network-error",
+        body: {
+          method: "GET",
+          sampling_fraction: 1.0,
+          status_code: 404,
+          phase: "application",
+          type: "http.error",
+        },
+        metadata: {
+          content_type: "application/reports+json",
+        },
+      }));
+    });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-subdomain-dns-failure.https.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>
+    Test that include_subdomains policies report DNS failures for subdomains
+  </title>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+  <script>
+    nel_test(async t => {
+      // Make a request to a resource whose response headers include an
+      // include_subdomains NEL policy.
+      await fetchResourceWithIncludeSubdomainsPolicy();
+      // Make a request to another resource on a nonexistent subdomain of the
+      // above.  Since the subdomain doesn't exist, the request should fail with
+      // a DNS error.
+      await fetchResourceWithNoPolicy('nonexistent').then((response) => {
+        assert_unreached("Request to nonexistent domain should fail");
+      }, (err) => {
+        // Silence the error, since it's expected.
+      });
+      // The include_subdomains policy that we just received should cover the
+      // second request, since include_subdomains policies can report on DNS
+      // errors, so the collector should have received a report about it.
+      assert_true(await reportExists({
+        url: getURLForResourceWithNoPolicy('nonexistent'),
+        user_agent: navigator.userAgent,
+        type: "network-error",
+        body: {
+          method: "GET",
+          sampling_fraction: 1.0,
+          status_code: 0,
+          phase: "dns",
+          type: "dns.name_not_resolved",
+        },
+        metadata: {
+          content_type: "application/reports+json",
+        },
+      }));
+    });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-success-with-subdomain-policy.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>
+    Test that NEL reports are sent for successful requests
+  </title>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+  <script>
+    nel_test(async t => {
+      // Make a request to a resource whose response headers include an
+      // include_subdomains NEL policy.
+      await fetchResourceWithIncludeSubdomainsPolicy();
+      // That policy should apply to the request that delivered it.  Even though
+      // the policy has include_subdomains set, it SHOULD generate a full,
+      // non-downgraded report about the request, since the request has the
+      // same origin as the policy.  (I.e., since the origins are the same, the
+      // include_subdomains setting is irrelevant.)
+      assert_true(await reportExists({
+        url: getURLForResourceWithIncludeSubdomainsPolicy(),
+        user_agent: navigator.userAgent,
+        type: "network-error",
+        body: {
+          method: "GET",
+          sampling_fraction: 1.0,
+          status_code: 200,
+          phase: "application",
+          type: "ok",
+        },
+        metadata: {
+          content_type: "application/reports+json",
+        },
+      }));
+    });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-success.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>
+    Test that NEL reports are sent for successful requests
+  </title>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+  <script>
+    nel_test(async t => {
+      // Make a request to a resource whose response headers include a NEL
+      // policy.
+      await fetchResourceWithBasicPolicy();
+      // That policy should apply to the request that delivered it, so the
+      // collector should have received a report about the request.
+      assert_true(await reportExists({
+        url: getURLForResourceWithBasicPolicy(),
+        user_agent: navigator.userAgent,
+        type: "network-error",
+        body: {
+          method: "GET",
+          sampling_fraction: 1.0,
+          status_code: 200,
+          phase: "application",
+          type: "ok",
+        },
+        metadata: {
+          content_type: "application/reports+json",
+        },
+      }));
+    });
+  </script>
+</body>
+</html>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2fa1e0ac0663a65deae6602621521cc2844b93de
GIT binary patch
literal 1689
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
Vg^jxe1<?-yj`nVL%{HMK{{w#RF>?R_
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Report-To: { "group": "nel-group", "max_age": 0, "endpoints": [] }
+NEL: {"max_age": 0}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/lock.py
@@ -0,0 +1,38 @@
+_LOCK_KEY = "network-error-logging:lock"
+_TIMEOUT = 5  # seconds
+
+def wait_for_lock(request):
+  t0 = time.time()
+  while time.time() - t0 < _TIMEOUT:
+    time.sleep(0.5)
+    value = request.server.stash.take(key=_LOCK_KEY)
+    if value is None:
+      return True
+  return False
+
+def lock(request, report_id):
+  with request.server.stash.lock:
+    # Loop until the lock is free
+    if not wait_for_lock(request):
+      return (503, [], "Cannot obtain lock")
+    request.server.stash.put(key=_LOCK_KEY, value=report_id)
+    return "Obtained lock for %s" % report_id
+
+def unlock(request, report_id):
+  with request.server.stash.lock:
+    lock_holder = request.server.stash.take(key=_LOCK_KEY)
+    if lock_holder != request_id:
+      # Return the lock holder to the stash
+      request.server.stash.put(key=_LOCK_KEY, value=lock_holder)
+      return (503, [], "Cannot release lock held by %s" % lock_holder)
+  return "Released lock for %s" % report_id
+
+def main(request, response):
+  op = request.GET.first("op")
+  report_id = request.GET.first("reportID")
+  if op == "lock":
+    return lock(request, report_id)
+  elif op == "unlock":
+    return unlock(request, report_id)
+  else:
+    return (400, [], "Invalid op")
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/nel.sub.js
@@ -0,0 +1,169 @@
+const reportID = "{{$id:uuid()}}";
+
+/*
+ * NEL tests have to run serially, since the user agent maintains a global cache
+ * of Reporting and NEL policies, and we don't want the policies for multiple
+ * tests to interfere with each other.  These functions (along with a Python
+ * handler in lock.py) implement a simple spin lock.
+ */
+
+function obtainNELLock() {
+  return fetch("/network-error-logging/support/lock.py?op=lock&reportID=" + reportID);
+}
+
+function releaseNELLock() {
+  return fetch("/network-error-logging/support/lock.py?op=unlock&reportID=" + reportID);
+}
+
+function nel_test(callback, name, properties) {
+  promise_test(async t => {
+    await obtainNELLock();
+    await clearReportingAndNELConfigurations();
+    await callback(t);
+    await releaseNELLock();
+  }, name, properties);
+}
+
+/*
+ * Helper functions for constructing domain names that contain NEL policies.
+ */
+function _monitoredDomain(subdomain) {
+  if (subdomain == "www") {
+    return "{{hosts[alt][www]}}"
+  } else if (subdomain == "www1") {
+    return "{{hosts[alt][www1]}}"
+  } else if (subdomain == "www2") {
+    return "{{hosts[alt][www2]}}"
+  } else if (subdomain == "nonexistent") {
+    return "{{hosts[alt][nonexistent]}}"
+  } else {
+    return "{{hosts[alt][]}}"
+  }
+}
+
+function _getNELResourceURL(subdomain, suffix) {
+  return "https://" + _monitoredDomain(subdomain) +
+    ":{{ports[https][0]}}/network-error-logging/support/" + suffix;
+}
+
+/*
+ * Fetches a resource whose headers define a basic NEL policy (i.e., with no
+ * include_subdomains flag).  We ensure that we request the resource from a
+ * different origin than is used for the main test case HTML file or for report
+ * uploads.  This minimizes the number of reports that are generated for this
+ * policy.
+ */
+
+function getURLForResourceWithBasicPolicy(subdomain) {
+  return _getNELResourceURL(subdomain, "pass.png?id="+reportID);
+}
+
+function fetchResourceWithBasicPolicy(subdomain) {
+  const url = getURLForResourceWithBasicPolicy(subdomain);
+  return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches a resource whose headers define an include_subdomains NEL policy.
+ */
+
+function getURLForResourceWithIncludeSubdomainsPolicy(subdomain) {
+  return _getNELResourceURL(subdomain, "subdomains-pass.png?id="+reportID);
+}
+
+function fetchResourceWithIncludeSubdomainsPolicy(subdomain) {
+  const url = getURLForResourceWithIncludeSubdomainsPolicy(subdomain);
+  return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches a resource whose headers do NOT define a NEL policy.  This may or may
+ * not generate a NEL report, depending on whether you've already successfully
+ * requested a resource from the same origin that included a NEL policy.
+ */
+
+function getURLForResourceWithNoPolicy(subdomain) {
+  return _getNELResourceURL(subdomain, "no-policy-pass.png");
+}
+
+function fetchResourceWithNoPolicy(subdomain) {
+  const url = getURLForResourceWithNoPolicy(subdomain);
+  return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches a resource that doesn't exist.  This may or may not generate a NEL
+ * report, depending on whether you've already successfully requested a resource
+ * from the same origin that included a NEL policy.
+ */
+
+function getURLForMissingResource(subdomain) {
+  return _getNELResourceURL(subdomain, "nonexistent.png");
+}
+
+function fetchMissingResource(subdomain) {
+  const url = getURLForMissingResource(subdomain);
+  return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches resources that clear out any existing Reporting or NEL configurations
+ * for all origins that any test case might use.
+ */
+
+function getURLForClearingConfiguration(subdomain) {
+  return _getNELResourceURL(subdomain, "clear-pass.png?id="+reportID);
+}
+
+async function clearReportingAndNELConfigurations(subdomain) {
+  await Promise.all([
+    fetch(getURLForClearingConfiguration(""), {mode: "no-cors"}),
+    fetch(getURLForClearingConfiguration("www"), {mode: "no-cors"}),
+    fetch(getURLForClearingConfiguration("www1"), {mode: "no-cors"}),
+    fetch(getURLForClearingConfiguration("www2"), {mode: "no-cors"}),
+  ]);
+  return;
+}
+
+/*
+ * Returns whether all of the fields in obj1 also exist in obj2 with the same
+ * values.  (Put another way, returns whether obj1 and obj2 are equal, ignoring
+ * any extra fields in obj2.)
+ */
+
+function _isSubsetOf(obj1, obj2) {
+  for (const prop in obj1) {
+    if (typeof obj1[prop] === 'object') {
+      if (typeof obj2[prop] !== 'object') {
+        return false;
+      }
+      if (!_isSubsetOf(obj1[prop], obj2[prop])) {
+        return false;
+      }
+    } else if (obj1[prop] != obj2[prop]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/*
+ * Verifies that a report was uploaded that contains all of the fields in
+ * expected.
+ */
+
+async function reportExists(expected) {
+  var timeout =
+    document.querySelector("meta[name=timeout][content=long]") ? 50 : 1;
+  var reportLocation =
+    "/network-error-logging/support/report.py?op=retrieve_report&timeout=" +
+    timeout + "&reportID=" + reportID;
+  const response = await fetch(reportLocation);
+  const json = await response.json();
+  for (const report of json) {
+    if (_isSubsetOf(expected, report)) {
+      return true;
+    }
+  }
+  return false;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2fa1e0ac0663a65deae6602621521cc2844b93de
GIT binary patch
literal 1689
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
Vg^jxe1<?-yj`nVL%{HMK{{w#RF>?R_
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2fa1e0ac0663a65deae6602621521cc2844b93de
GIT binary patch
literal 1689
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
Vg^jxe1<?-yj`nVL%{HMK{{w#RF>?R_
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/pass.png.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Report-To: { "group": "nel-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/network-error-logging/support/report.py?op=put&reportID={{GET[id]}}" }] }
+NEL: {"report_to": "nel-group", "max_age": 10886400, "success_fraction": 1.0}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/report.py
@@ -0,0 +1,52 @@
+import time
+import json
+import re
+
+def retrieve_from_stash(request, key, timeout, default_value):
+  t0 = time.time()
+  while time.time() - t0 < timeout:
+    time.sleep(0.5)
+    value = request.server.stash.take(key=key)
+    if value is not None:
+      return json.dumps(value)
+
+  return default_value
+
+def main(request, response):
+  # Handle CORS preflight requests
+  if request.method == 'OPTIONS':
+    # Always reject preflights for one subdomain
+    if "www2" in request.headers["Origin"]:
+      return (400, [], "CORS preflight rejected for www2")
+    return [
+      ("Content-Type", "text/plain"),
+      ("Access-Control-Allow-Origin", "*"),
+      ("Access-Control-Allow-Methods", "post"),
+      ("Access-Control-Allow-Headers", "Content-Type"),
+    ], "CORS allowed"
+
+  op = request.GET.first("op");
+  key = request.GET.first("reportID")
+
+  if op == "retrieve_report":
+    try:
+      timeout = float(request.GET.first("timeout"))
+    except:
+      timeout = 0.5
+    return [("Content-Type", "application/json")], retrieve_from_stash(request, key, timeout, '[]')
+
+  # append new reports
+  new_reports = json.loads(request.body)
+  for report in new_reports:
+    report["metadata"] = {
+      "content_type": request.headers["Content-Type"],
+    }
+  with request.server.stash.lock:
+    reports = request.server.stash.take(key=key)
+    if reports is None:
+      reports = []
+    reports.extend(new_reports)
+    request.server.stash.put(key=key, value=reports)
+
+  # return acknowledgement report
+  return [("Content-Type", "text/plain")], "Recorded report"
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2fa1e0ac0663a65deae6602621521cc2844b93de
GIT binary patch
literal 1689
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
Vg^jxe1<?-yj`nVL%{HMK{{w#RF>?R_
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/subdomains-pass.png.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Report-To: { "group": "nel-group", "max_age": 10886400, "include_subdomains": true, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/network-error-logging/support/report.py?op=put&reportID={{GET[id]}}" }] }
+NEL: {"report_to": "nel-group", "max_age": 10886400, "include_subdomains": true, "success_fraction": 1.0}