Update web-platform-tests to revision 5e3ea8f49fee68c327388bfd1dd1375a8ce12a0e.

This commit is contained in:
Ms2ger 2015-07-09 14:05:01 +02:00
parent 12195a5c4a
commit bfb96b9448
1166 changed files with 35123 additions and 900 deletions

View file

@ -0,0 +1,360 @@
/**
* @fileoverview Utilities for mixed-content in Web Platform Tests.
* @author burnik@google.com (Kristijan Burnik)
* Disclaimer: Some methods of other authors are annotated in the corresponding
* method's JSDoc.
*/
/**
* Normalizes the target port for use in a URL. For default ports, this is the
* empty string (omitted port), otherwise it's a colon followed by the port
* number. Ports 80, 443 and an empty string are regarded as default ports.
* @param {number} targetPort The port to use
* @return {string} The port portion for using as part of a URL.
*/
function getNormalizedPort(targetPort) {
return ([80, 443, ""].indexOf(targetPort) >= 0) ? "" : ":" + targetPort;
}
/**
* Creates a GUID.
* See: https://en.wikipedia.org/wiki/Globally_unique_identifier
* Original author: broofa (http://www.broofa.com/)
* Sourced from: http://stackoverflow.com/a/2117523/4949715
* @return {string} A pseudo-random GUID.
*/
function guid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
/**
* Initiates a new XHR via GET.
* @param {string} url The endpoint URL for the XHR.
* @param {string} responseType Optional - how should the response be parsed.
* Default is "json".
* See: https://xhr.spec.whatwg.org/#dom-xmlhttprequest-responsetype
* @return {Promise} A promise wrapping the success and error events.
*/
function xhrRequest(url, responseType) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = responseType || "json";
xhr.addEventListener("error", function() {
reject(Error("Network Error"));
});
xhr.addEventListener("load", function() {
if (xhr.status != 200)
return reject(Error(xhr.statusText));
resolve(xhr.response);
});
xhr.send();
});
}
/**
* Sets attributes on a given DOM element.
* @param {DOMElement} element The element on which to set the attributes.
* @param {object} An object with keys (serving as attribute names) and values.
*/
function setAttributes(el, attrs) {
attrs = attrs || {}
for (var attr in attrs)
el.setAttribute(attr, attrs[attr]);
}
/**
* Binds to success and error events of an object wrapping them into a promise
* available through {@code element.eventPromise}. The success event
* resolves and error event rejects.
* @param {object} element An object supporting events on which to bind the
* promise.
* @param {string} resolveEventName [="load"] The event name to bind resolve to.
* @param {string} rejectEventName [="error"] The event name to bind reject to.
*/
function bindEvents(element, resolveEventName, rejectEventName) {
element.eventPromise = new Promise(function(resolve, reject) {
element.addEventListener(resolveEventName || "load", resolve);
element.addEventListener(rejectEventName || "error", reject);
});
}
/**
* Creates a new DOM element.
* @param {string} tagName The type of the DOM element.
* @param {object} attrs A JSON with attributes to apply to the element.
* @param {DOMElement} parent Optional - an existing DOM element to append to
* If not provided, the returned element will remain orphaned.
* @param {boolean} doBindEvents Optional - Whether to bind to load and error
* events and provide the promise wrapping the events via the element's
* {@code eventPromise} property. Default value evaluates to false.
* @return {DOMElement} The newly created DOM element.
*/
function createElement(tagName, attrs, parent, doBindEvents) {
var element = document.createElement(tagName);
if (doBindEvents)
bindEvents(element);
// We set the attributes after binding to events to catch any
// event-triggering attribute changes. E.g. form submission.
setAttributes(element, attrs);
if (parent)
parent.appendChild(element);
return element;
}
function createRequestViaElement(tagName, attrs, parent) {
return createElement(tagName, attrs, parent, true).eventPromise;
}
/**
* Creates a new empty iframe and appends it to {@code document.body} .
* @param {string} name The name and ID of the new iframe.
* @param {boolean} doBindEvents Whether to bind load and error events.
* @return {DOMElement} The newly created iframe.
*/
function createHelperIframe(name, doBindEvents) {
return createElement("iframe",
{"name": name, "id": name},
document.body,
doBindEvents);
}
/**
* Creates a new iframe, binds load and error events, sets the src attribute and
* appends it to {@code document.body} .
* @param {string} url The src for the iframe.
* @return {Promise} The promise for success/error events.
*/
function requestViaIframe(url) {
return createRequestViaElement("iframe", {"src": url}, document.body);
}
/**
* Creates a new image, binds load and error events, sets the src attribute and
* appends it to {@code document.body} .
* @param {string} url The src for the image.
* @return {Promise} The promise for success/error events.
*/
function requestViaImage(url) {
return createRequestViaElement("img", {"src": url}, document.body);
}
/**
* Initiates a new XHR GET request to provided URL.
* @param {string} url The endpoint URL for the XHR.
* @return {Promise} The promise for success/error events.
*/
function requestViaXhr(url) {
return xhrRequest(url);
}
/**
* Initiates a new GET request to provided URL via the Fetch API.
* @param {string} url The endpoint URL for the Fetch.
* @return {Promise} The promise for success/error events.
*/
function requestViaFetch(url) {
return fetch(url);
}
/**
* Creates a new Worker, binds message and error events wrapping them into.
* {@code worker.eventPromise} and posts an empty string message to start
* the worker.
* @param {string} url The endpoint URL for the worker script.
* @return {Promise} The promise for success/error events.
*/
function requestViaWorker(url) {
var worker = new Worker(url);
bindEvents(worker, "message", "error");
worker.postMessage('');
return worker.eventPromise;
}
/**
* Sets the href attribute on a navigable DOM element and performs a navigation
* by clicking it. To avoid navigating away from the current execution
* context, a target attribute is set to point to a new helper iframe.
* @param {DOMElement} navigableElement The navigable DOMElement
* @param {string} url The href for the navigable element.
* @return {Promise} The promise for success/error events.
*/
function requestViaNavigable(navigableElement, url) {
var iframe = createHelperIframe(guid(), true);
setAttributes(navigableElement,
{"href": url,
"target": iframe.name});
navigableElement.click();
return iframe.eventPromise;
}
/**
* Creates a new anchor element, appends it to {@code document.body} and
* performs the navigation.
* @param {string} url The URL to navigate to.
* @return {Promise} The promise for success/error events.
*/
function requestViaAnchor(url) {
var a = createElement("a", {"innerHTML": "Link to resource"}, document.body);
return requestViaNavigable(a, url);
}
/**
* Creates a new area element, appends it to {@code document.body} and performs
* the navigation.
* @param {string} url The URL to navigate to.
* @return {Promise} The promise for success/error events.
*/
function requestViaArea(url) {
var area = createElement("area", {}, document.body);
return requestViaNavigable(area, url);
}
/**
* Creates a new script element, sets the src to url, and appends it to
* {@code document.body}.
* @param {string} url The src URL.
* @return {Promise} The promise for success/error events.
*/
function requestViaScript(url) {
return createRequestViaElement("script", {"src": url}, document.body);
}
/**
* Creates a new form element, sets attributes, appends it to
* {@code document.body} and submits the form.
* @param {string} url The URL to submit to.
* @return {Promise} The promise for success/error events.
*/
function requestViaForm(url) {
var iframe = createHelperIframe(guid());
var form = createElement("form",
{"action": url,
"method": "POST",
"target": iframe.name},
document.body);
bindEvents(iframe);
form.submit();
return iframe.eventPromise;
}
/**
* Creates a new link element for a stylesheet, binds load and error events,
* sets the href to url and appends it to {@code document.head}.
* @param {string} url The URL for a stylesheet.
* @return {Promise} The promise for success/error events.
*/
function requestViaLinkStylesheet(url) {
return createRequestViaElement("link",
{"rel": "stylesheet", "href": url},
document.head);
}
/**
* Creates a new link element for a prefetch, binds load and error events, sets
* the href to url and appends it to {@code document.head}.
* @param {string} url The URL of a resource to prefetch.
* @return {Promise} The promise for success/error events.
*/
function requestViaLinkPrefetch(url) {
// TODO(kristijanburnik): Check if prefetch should support load and error
// events. For now we assume it's not specified.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Link_prefetching_FAQ
return createRequestViaElement("link",
{"rel": "prefetch", "href": url},
document.head);
}
/**
* Creates a new media element with a child source element, binds loadeddata and
* error events, sets attributes and appends to document.body.
* @param {string} type The type of the media element (audio/video/picture).
* @param {object} media_attrs The attributes for the media element.
* @param {object} source_attrs The attributes for the child source element.
* @return {DOMElement} The newly created media element.
*/
function createMediaElement(type, media_attrs, source_attrs) {
var mediaElement = createElement(type, {});
var sourceElement = createElement("source", {}, mediaElement);
mediaElement.eventPromise = new Promise(function(resolve, reject) {
mediaElement.addEventListener("loadeddata", resolve);
// Notice that the source element will raise the error.
sourceElement.addEventListener("error", reject);
});
setAttributes(mediaElement, media_attrs);
setAttributes(sourceElement, source_attrs);
document.body.appendChild(mediaElement);
return mediaElement;
}
/**
* Creates a new video element, binds loadeddata and error events, sets
* attributes and source URL and appends to {@code document.body}.
* @param {string} url The URL of the video.
* @return {Promise} The promise for success/error events.
*/
function requestViaVideo(url) {
return createMediaElement("video",
{},
{type: "video/mp4", src: url}).eventPromise;
}
/**
* Creates a new audio element, binds loadeddata and error events, sets
* attributes and source URL and appends to {@code document.body}.
* @param {string} url The URL of the audio.
* @return {Promise} The promise for success/error events.
*/
function requestViaAudio(url) {
return createMediaElement("audio",
{},
{type: "audio/mpeg", src: url}).eventPromise;
}
/**
* Creates a new picture element, binds loadeddata and error events, sets
* attributes and source URL and appends to {@code document.body}. Also
* creates new image element appending it to the picture
* @param {string} url The URL of the image for the source and image elements.
* @return {Promise} The promise for success/error events.
*/
function requestViaPicture(url) {
var picture = createMediaElement("picture", {}, {"srcset": url,
"type": "image/png"});
return createRequestViaElement("img", {"src": url}, picture);
}
/**
* Creates a new object element, binds load and error events, sets the data to
* url, and appends it to {@code document.body}.
* @param {string} url The data URL.
* @return {Promise} The promise for success/error events.
*/
function requestViaObject(url) {
return createRequestViaElement("object", {"data": url}, document.body);
}
// SanityChecker does nothing in release mode. See sanity-checker.js for debug
// mode.
function SanityChecker() {}
SanityChecker.prototype.checkScenario = function() {};

View file

@ -0,0 +1,101 @@
import json, os, urllib, urlparse
def redirect(url, response):
response.add_required_headers = False
response.writer.write_status(301)
response.writer.write_header("access-control-allow-origin", "*")
response.writer.write_header("location", url)
response.writer.end_headers()
response.writer.write("")
def create_redirect_url(request, swap_scheme = False):
parsed = urlparse.urlsplit(request.url)
destination_netloc = parsed.netloc
scheme = parsed.scheme
if swap_scheme:
scheme = "http" if parsed.scheme == "https" else "https"
hostname = parsed.netloc.split(':')[0]
port = request.server.config["ports"][scheme][0]
destination_netloc = ":".join([hostname, str(port)])
# Remove "redirection" from query to avoid redirect loops.
parsed_query = dict(urlparse.parse_qsl(parsed.query))
assert "redirection" in parsed_query
del parsed_query["redirection"]
destination_url = urlparse.urlunsplit(urlparse.SplitResult(
scheme = scheme,
netloc = destination_netloc,
path = parsed.path,
query = urllib.urlencode(parsed_query),
fragment = None))
return destination_url
def main(request, response):
if "redirection" in request.GET:
redirection = request.GET["redirection"]
if redirection == "no-redirect":
pass
elif redirection == "keep-scheme-redirect":
redirect(create_redirect_url(request, swap_scheme=False), response)
return
elif redirection == "swap-scheme-redirect":
redirect(create_redirect_url(request, swap_scheme=True), response)
return
else:
raise ValueError ("Invalid redirect type: %s" % redirection)
content_type = "text/plain"
response_data = ""
if "action" in request.GET:
action = request.GET["action"]
if "content_type" in request.GET:
content_type = request.GET["content_type"]
key = request.GET["key"]
stash = request.server.stash
if action == "put":
value = request.GET["value"]
stash.take(key=key)
stash.put(key=key, value=value)
response_data = json.dumps({"status": "success", "result": key})
elif action == "purge":
value = stash.take(key=key)
if content_type == "image/png":
response_data = open(os.path.join(request.doc_root,
"images",
"smiley.png")).read()
elif content_type == "audio/mpeg":
response_data = open(os.path.join(request.doc_root,
"media",
"sound_5.oga")).read()
elif content_type == "video/mp4":
response_data = open(os.path.join(request.doc_root,
"media",
"movie_5.mp4")).read()
elif content_type == "application/javascript":
response_data = open(os.path.join(request.doc_root,
"mixed-content",
"generic",
"worker.js")).read()
else:
response_data = "/* purged */"
elif action == "take":
value = stash.take(key=key)
if value is None:
status = "allowed"
else:
status = "blocked"
response_data = json.dumps({"status": status, "result": value})
response.add_required_headers = False
response.writer.write_status(200)
response.writer.write_header("content-type", content_type)
response.writer.write_header("cache-control", "no-cache; must-revalidate")
response.writer.end_headers()
response.writer.write(response_data)

View file

@ -0,0 +1,156 @@
/**
* @fileoverview Test case for mixed-content in Web Platform Tests.
* @author burnik@google.com (Kristijan Burnik)
*/
/**
* MixedContentTestCase exercises all the tests for checking browser behavior
* when resources regarded as mixed-content are requested. A single run covers
* only a single scenario.
* @param {object} scenario A JSON describing the test arrangement and
* expectation(s). Refer to /mixed-content/spec.src.json for details.
* @param {string} description The test scenario verbose description.
* @param {SanityChecker} sanityChecker Instance of an object used to check the
* running scenario. Useful in debug mode. See ./sanity-checker.js.
* Run {@code ./tools/generate.py -h} for info on test generating modes.
* @return {object} Object wrapping the start method used to run the test.
*/
function MixedContentTestCase(scenario, description, sanityChecker) {
var insecureProtocol = "http";
var secureProtocol = "https";
var sameOriginHost = location.hostname;
var crossOriginHost = "{{domains[www1]}}";
// These values can evaluate to either empty strings or a ":port" string.
var insecurePort = getNormalizedPort(parseInt("{{ports[http][0]}}", 10));
var securePort = getNormalizedPort(parseInt("{{ports[https][0]}}", 10));
var resourcePath = "/mixed-content/generic/expect.py";
// Map all endpoints to scenario for use in the test.
var endpoint = {
"same-origin":
location.origin + resourcePath,
"same-host-https":
secureProtocol + "://" + sameOriginHost + securePort + resourcePath,
"same-host-http":
insecureProtocol + "://" + sameOriginHost + insecurePort + resourcePath,
"cross-origin-https":
secureProtocol + "://" + crossOriginHost + securePort + resourcePath,
"cross-origin-http":
insecureProtocol + "://" + crossOriginHost + insecurePort + resourcePath
};
// Mapping all the resource requesting methods to the scenario.
var resourceMap = {
"a-tag": requestViaAnchor,
"area-tag": requestViaArea,
"fetch-request": requestViaFetch,
"form-tag": requestViaForm,
"iframe-tag": requestViaIframe,
"img-tag": requestViaImage,
"script-tag": requestViaScript,
"worker-request": requestViaWorker,
"xhr-request": requestViaXhr,
"audio-tag": requestViaAudio,
"video-tag": requestViaVideo,
"picture-tag": requestViaPicture,
"object-tag": requestViaObject,
"link-css-tag": requestViaLinkStylesheet,
"link-prefetch-tag": requestViaLinkPrefetch
};
sanityChecker.checkScenario(scenario, resourceMap);
// Mapping all expected MIME types to the scenario.
var contentType = {
"a-tag": "text/html",
"area-tag": "text/html",
"fetch-request": "application/json",
"form-tag": "text/html",
"iframe-tag": "text/html",
"img-tag": "image/png",
"script-tag": "text/javascript",
"worker-request": "application/javascript",
"xhr-request": "application/json",
"audio-tag": "audio/mpeg",
"video-tag": "video/mp4",
"picture-tag": "image/png",
"object-tag": "text/html",
"link-css-tag": "text/css",
"link-prefetch-tag": "text/html"
};
var mixed_content_test = async_test(description);
function runTest() {
var testCompleted = false;
// Due to missing implementations, tests time out, so we fail them early.
// TODO(kristijanburnik): Once WPT rolled in:
// https://github.com/w3c/testharness.js/pull/127
// Refactor to make use of step_timeout.
setTimeout(function() {
mixed_content_test.step(function() {
assert_true(testCompleted, "Expected test to complete.");
mixed_content_test.done();
})
}, 1000);
var key = guid();
var value = guid();
var announceResourceRequestUrl = endpoint['same-origin'] +
"?action=put&key=" + key +
"&value=" + value;
var assertResourceRequestUrl = endpoint['same-origin'] +
"?action=take&key=" + key;
var resourceRequestUrl = endpoint[scenario.origin] + "?redirection=" +
scenario.redirection + "&action=purge&key=" +
key + "&content_type=" +
contentType[scenario.subresource];
xhrRequest(announceResourceRequestUrl)
.then(function(response) {
// Send out the real resource request.
// This should tear down the key if it's not blocked.
return resourceMap[scenario.subresource](resourceRequestUrl);
})
.then(function() {
mixed_content_test.step(function() {
assert_equals("allowed", scenario.expectation,
"The triggered event should match '" +
scenario.expectation + "'.");
}, "Check if success event was triggered.");
// Send request to check if the key has been torn down.
return xhrRequest(assertResourceRequestUrl);
}, function(error) {
mixed_content_test.step(function() {
assert_equals("blocked", scenario.expectation,
"The triggered event should match '" +
scenario.expectation + "'.");
// TODO(kristijanburnik): param "error" can be an event or error.
// Map assertion by resource.
// e.g.: assert_equals(e.type, "error");
}, "Check if error event was triggered.");
// When requestResource fails, we also check the key state.
return xhrRequest(assertResourceRequestUrl);
})
.then(function(response) {
// Now check if the value has been torn down. If it's still there,
// we have blocked the request to mixed-content.
mixed_content_test.step(function() {
assert_equals(response.status, scenario.expectation,
"The resource request should be '" + scenario.expectation +
"'.");
}, "Check if request was sent.");
mixed_content_test.done();
testCompleted = true;
});
} // runTest
return {start: runTest};
} // MixedContentTestCase

View file

@ -0,0 +1,38 @@
// The SanityChecker is used in debug mode to identify problems with the
// structure of the testsuite. In release mode it is mocked out to do nothing.
function SanityChecker() {}
SanityChecker.prototype.checkScenario = function(scenario, resourceInvoker) {
// Check if scenario is valid.
test(function() {
var expectedFields = SPEC_JSON["test_expansion_schema"];
for (var field in expectedFields) {
if (field == "expansion")
continue
assert_own_property(scenario, field,
"The scenario should contain field '" + field + "'.")
var expectedFieldList = expectedFields[field];
if (!expectedFieldList.hasOwnProperty('length')) {
var expectedFieldList = [];
for (var key in expectedFields[field]) {
expectedFieldList = expectedFieldList.concat(expectedFields[field][key])
}
}
assert_in_array(scenario[field], expectedFieldList,
"Scenario's " + field + " is one of: " +
expectedFieldList.join(", ")) + "."
}
// Check if the protocol is matched.
assert_equals(scenario["source_scheme"] + ":", location.protocol,
"Protocol of the test page should match the scenario.")
assert_own_property(resourceInvoker, scenario.subresource,
"Subresource should be supported");
}, "[MixedContentTestCase] The test scenario should be valid.");
}

View file

@ -0,0 +1 @@
<!-- DO NOT EDIT! Generated by %(generating_script_filename)s using %(html_template_filename)s. -->

View file

@ -0,0 +1 @@
var SPEC_JSON = %(spec_json)s;

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
%(generated_disclaimer)s
<html>
<head>
<title>Mixed-Content: %(spec_title)s</title>
<meta charset='utf-8'>
<meta name="description" content="%(spec_description)s">
<meta name="assert" content="%(test_description)s">%(meta_opt_in)s
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Common global functions for mixed-content tests. -->
<script src="/mixed-content/generic/common.js"></script>
<!-- The original specification JSON for validating the scenario. -->
<script src="/mixed-content/spec_json.js"></script>
<!-- Internal checking of the tests -->
<script src="/mixed-content/generic/sanity-checker.js"></script>
<!-- Simple wrapper API for all mixed-content test cases. -->
<script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
</head>
<body>
<h1>%(spec_title)s</h1>
<h2>%(spec_description)s</h2>
<pre>%(test_description)s</pre>
<p>See <a href="%(spec_specification_url)s" target="_blank">specification</a>
details for this test.</p>
<div id="log"></div>
<script>%(test_js)s</script>
</body>
</html>

View file

@ -0,0 +1,13 @@
MixedContentTestCase(
{
"opt_in_method": "%(opt_in_method)s",
"origin": "%(origin)s",
"source_scheme": "%(source_scheme)s",
"context_nesting": "%(context_nesting)s",
"redirection": "%(redirection)s",
"subresource": "%(subresource)s",
"expectation": "%(expectation)s"
},
document.querySelector("meta[name=assert]").content,
new SanityChecker()
).start();

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
%(generated_disclaimer)s
<html>
<head>
<title>Mixed-Content: %(spec_title)s</title>
<meta charset='utf-8'>
<meta name="description" content="%(spec_description)s">
<link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
<link rel="help" href="%(spec_specification_url)s">
<meta name="assert" content="%(test_description)s">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/mixed-content/generic/common.js"></script>
<script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
</head>
<body>
<script>%(test_js)s</script>
<div id="log"></div>
</body>
</html>

View file

@ -0,0 +1,7 @@
opt_in_method: %(opt_in_method)s
origin: %(origin)s
source_scheme: %(source_scheme)s
context_nesting: %(context_nesting)s
redirection: %(redirection)s
subresource: %(subresource)s
expectation: %(expectation)s

View file

@ -0,0 +1,35 @@
#!/usr/bin/env python
import os, json
from common_paths import *
import spec_validator
def rmtree(top):
top = os.path.abspath(top)
assert top != os.path.expanduser("~")
assert len(top) > len(os.path.expanduser("~"))
assert "web-platform-tests" in top
assert "mixed-content" in top
for root, dirs, files in os.walk(top, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
os.rmdir(top)
def main():
spec_json = load_spec_json();
spec_validator.assert_valid_spec_json(spec_json)
for spec in spec_json['specification']:
generated_dir = os.path.join(spec_directory, spec["name"])
if (os.path.isdir(generated_dir)):
rmtree(generated_dir)
if (os.path.isfile(generated_spec_json_filename)):
os.remove(generated_spec_json_filename)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,58 @@
import os, sys, json, re
script_directory = os.path.dirname(os.path.abspath(__file__))
generic_directory = os.path.abspath(os.path.join(script_directory, '..'))
template_directory = os.path.abspath(os.path.join(script_directory,
'..',
'template'))
spec_directory = os.path.abspath(os.path.join(script_directory, '..', '..'))
test_root_directory = os.path.abspath(os.path.join(script_directory,
'..', '..', '..'))
spec_filename = os.path.join(spec_directory, "spec.src.json")
generated_spec_json_filename = os.path.join(spec_directory, "spec_json.js")
selection_pattern = '%(opt_in_method)s/' + \
'%(origin)s/' + \
'%(subresource)s/' + \
'%(context_nesting)s/' + \
'%(redirection)s/'
test_file_path_pattern = '%(spec_name)s/' + selection_pattern + \
'%(name)s.%(source_scheme)s.html'
def get_template(basename):
with open(os.path.join(template_directory, basename)) as f:
return f.read()
def write_file(filename, contents):
with open(filename, "w") as f:
f.write(contents)
def read_nth_line(fp, line_number):
fp.seek(0)
for i, line in enumerate(fp):
if (i + 1) == line_number:
return line
def load_spec_json(path_to_spec = None):
if path_to_spec is None:
path_to_spec = spec_filename
re_error_location = re.compile('line ([0-9]+) column ([0-9]+)')
with open(path_to_spec) as f:
try:
return json.load(f)
except ValueError, ex:
print ex.message
match = re_error_location.search(ex.message)
if match:
line_number, column = int(match.group(1)), int(match.group(2))
print read_nth_line(f, line_number).rstrip()
print " " * (column - 1) + "^"
sys.exit(1)

View file

@ -0,0 +1,157 @@
#!/usr/bin/env python
import os, sys, json
from common_paths import *
import spec_validator
import argparse
def expand_pattern(expansion_pattern, test_expansion_schema):
expansion = {}
for artifact_key in expansion_pattern:
artifact_value = expansion_pattern[artifact_key]
if artifact_value == '*':
expansion[artifact_key] = test_expansion_schema[artifact_key]
elif isinstance(artifact_value, list):
expansion[artifact_key] = artifact_value
elif isinstance(artifact_value, dict):
# Flattened expansion.
expansion[artifact_key] = []
values_dict = expand_pattern(artifact_value,
test_expansion_schema[artifact_key])
for sub_key in values_dict.keys():
expansion[artifact_key] += values_dict[sub_key]
else:
expansion[artifact_key] = [artifact_value]
return expansion
def permute_expansion(expansion, artifact_order, selection = {}, artifact_index = 0):
assert isinstance(artifact_order, list), "artifact_order should be a list"
if artifact_index >= len(artifact_order):
yield selection
return
artifact_key = artifact_order[artifact_index]
for artifact_value in expansion[artifact_key]:
selection[artifact_key] = artifact_value
for next_selection in permute_expansion(expansion,
artifact_order,
selection,
artifact_index + 1):
yield next_selection
def generate_selection(selection, spec, test_html_template_basename):
selection['spec_name'] = spec['name']
selection['spec_title'] = spec['title']
selection['spec_description'] = spec['description']
selection['spec_specification_url'] = spec['specification_url']
test_filename = test_file_path_pattern % selection
test_headers_filename = test_filename + ".headers"
test_directory = os.path.dirname(test_filename)
full_path = os.path.join(spec_directory, test_directory)
test_html_template = get_template(test_html_template_basename)
test_js_template = get_template("test.js.template")
disclaimer_template = get_template('disclaimer.template')
test_description_template = get_template("test_description.template")
html_template_filename = os.path.join(template_directory,
test_html_template_basename)
generated_disclaimer = disclaimer_template \
% {'generating_script_filename': os.path.relpath(__file__,
test_root_directory),
'html_template_filename': os.path.relpath(html_template_filename,
test_root_directory)}
selection['generated_disclaimer'] = generated_disclaimer.rstrip()
test_description_template = \
test_description_template.rstrip().replace("\n", "\n" + " " * 33)
selection['test_description'] = test_description_template % selection
# Adjust the template for the test invoking JS. Indent it to look nice.
indent = "\n" + " " * 6;
test_js_template = indent + test_js_template.replace("\n", indent);
selection['test_js'] = test_js_template % selection
# Directory for the test files.
try:
os.makedirs(full_path)
except:
pass
# TODO(kristijanburnik): Implement the opt-in-method here.
opt_in_method = selection['opt_in_method']
selection['meta_opt_in'] = ''
if opt_in_method == 'meta-csp':
selection['meta_opt_in'] = '<meta http-equiv="Content-Security-Policy" ' + \
'content="block-all-mixed-content">'
elif opt_in_method == 'http-csp':
opt_in_headers = "Content-Security-Policy: block-all-mixed-content\n"
write_file(test_headers_filename, opt_in_headers)
elif opt_in_method == 'no-opt-in':
pass
else:
raise ValueError("Invalid opt_in_method %s" % opt_in_method)
# Write out the generated HTML file.
write_file(test_filename, test_html_template % selection)
def generate_test_source_files(spec_json, target):
test_expansion_schema = spec_json['test_expansion_schema']
specification = spec_json['specification']
spec_json_js_template = get_template('spec_json.js.template')
write_file(generated_spec_json_filename,
spec_json_js_template % {'spec_json': json.dumps(spec_json)})
# Choose a debug/release template depending on the target.
html_template = "test.%s.html.template" % target
artifact_order = test_expansion_schema.keys() + ['name']
# Create list of excluded tests.
exclusion_dict = {}
for excluded_pattern in spec_json['excluded_tests']:
excluded_expansion = \
expand_pattern(excluded_pattern,
test_expansion_schema)
for excluded_selection in permute_expansion(excluded_expansion, artifact_order):
excluded_selection_path = selection_pattern % excluded_selection
exclusion_dict[excluded_selection_path] = True
for spec in specification:
for expansion_pattern in spec['test_expansion']:
expansion = expand_pattern(expansion_pattern,
test_expansion_schema)
for selection in permute_expansion(expansion, artifact_order):
selection_path = selection_pattern % selection
if not selection_path in exclusion_dict:
generate_selection(selection,
spec,
html_template)
else:
print 'Excluding selection:', selection_path
def main(target, spec_filename):
spec_json = load_spec_json(spec_filename);
spec_validator.assert_valid_spec_json(spec_json)
generate_test_source_files(spec_json, target)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Test suite generator utility')
parser.add_argument('-t', '--target', type = str,
choices = ("release", "debug"), default = "release",
help = 'Sets the appropriate template for generating tests')
parser.add_argument('-s', '--spec', type = str, default = None,
help = 'Specify a file used for describing and generating the tests')
# TODO(kristijanburnik): Add option for the spec_json file.
args = parser.parse_args()
main(args.target, args.spec)

View file

@ -0,0 +1,3 @@
#!/bin/bash
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
python $DIR/clean.py && python $DIR/generate.py

View file

@ -0,0 +1,159 @@
#!/usr/bin/env python
import json, sys
from common_paths import *
def assert_non_empty_string(obj, field):
assert field in obj, 'Missing field "%s"' % field
assert isinstance(obj[field], basestring), \
'Field "%s" must be a string' % field
assert len(obj[field]) > 0, 'Field "%s" must not be empty' % field
def assert_non_empty_list(obj, field):
assert isinstance(obj[field], list), \
'%s must be a list' % field
assert len(obj[field]) > 0, \
'%s list must not be empty' % field
def assert_non_empty_dict(obj, field):
assert isinstance(obj[field], dict), \
'%s must be a dict' % field
assert len(obj[field]) > 0, \
'%s dict must not be empty' % field
def assert_contains(obj, field):
assert field in obj, 'Must contain field "%s"' % field
def assert_string_from(obj, field, items):
assert obj[field] in items, \
'Field "%s" must be from: %s' % (field, str(items))
def assert_string_or_list_items_from(obj, field, items):
if isinstance(obj[field], basestring):
assert_string_from(obj, field, items)
return
assert isinstance(obj[field], list), "%s must be a list!" % field
for allowed_value in obj[field]:
assert allowed_value != '*', "Wildcard is not supported for lists!"
assert allowed_value in items, \
'Field "%s" must be from: %s' % (field, str(items))
def assert_contains_only_fields(obj, expected_fields):
for expected_field in expected_fields:
assert_contains(obj, expected_field)
for actual_field in obj:
assert actual_field in expected_fields, \
'Unexpected field "%s".' % actual_field
def assert_value_unique_in(value, used_values):
assert value not in used_values, 'Duplicate value "%s"!' % str(value)
used_values[value] = True
def assert_valid_artifact(exp_pattern, artifact_key, schema):
if isinstance(schema, list):
assert_string_or_list_items_from(exp_pattern, artifact_key,
["*"] + schema)
return
for sub_artifact_key, sub_schema in schema.iteritems():
assert_valid_artifact(exp_pattern[artifact_key], sub_artifact_key,
sub_schema)
def validate(spec_json, details):
""" Validates the json specification for generating tests. """
details['object'] = spec_json
assert_contains_only_fields(spec_json, ["specification",
"test_expansion_schema",
"excluded_tests"])
assert_non_empty_list(spec_json, "specification")
assert_non_empty_dict(spec_json, "test_expansion_schema")
assert_non_empty_list(spec_json, "excluded_tests")
specification = spec_json['specification']
test_expansion_schema = spec_json['test_expansion_schema']
excluded_tests = spec_json['excluded_tests']
valid_test_expansion_fields = ['name'] + test_expansion_schema.keys()
# Validate each single spec.
for spec in specification:
details['object'] = spec
# Validate required fields for a single spec.
assert_contains_only_fields(spec, ['name',
'title',
'description',
'specification_url',
'test_expansion'])
assert_non_empty_string(spec, 'name')
assert_non_empty_string(spec, 'title')
assert_non_empty_string(spec, 'description')
assert_non_empty_string(spec, 'specification_url')
assert_non_empty_list(spec, 'test_expansion')
# Validate spec's test expansion.
used_spec_names = {}
for spec_exp in spec['test_expansion']:
details['object'] = spec_exp
assert_non_empty_string(spec_exp, 'name')
# The name is unique in same expansion group.
assert_value_unique_in((spec_exp['expansion'], spec_exp['name']),
used_spec_names)
assert_contains_only_fields(spec_exp, valid_test_expansion_fields)
for artifact in test_expansion_schema:
details['test_expansion_field'] = artifact
assert_valid_artifact(spec_exp, artifact,
test_expansion_schema[artifact])
del details['test_expansion_field']
# Validate the test_expansion schema members.
details['object'] = test_expansion_schema
assert_contains_only_fields(test_expansion_schema, ['expansion',
'source_scheme',
'opt_in_method',
'context_nesting',
'redirection',
'subresource',
'origin',
'expectation'])
# Validate excluded tests.
details['object'] = excluded_tests
for excluded_test_expansion in excluded_tests:
assert_contains_only_fields(excluded_test_expansion,
valid_test_expansion_fields)
del details['object']
def assert_valid_spec_json(spec_json):
error_details = {}
try:
validate(spec_json, error_details)
except AssertionError, err:
print 'ERROR:', err.message
print json.dumps(error_details, indent=4)
sys.exit(1)
def main():
spec_json = load_spec_json();
assert_valid_spec_json(spec_json)
print "Spec JSON is valid."
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
postMessage('done');