Update web-platform-tests to revision d3cf77a7b8c20c678b725238eaa8a72eca3787ae

This commit is contained in:
WPT Sync Bot 2019-04-25 22:18:37 -04:00
parent 880f3b8b7a
commit efca990ffe
541 changed files with 8000 additions and 2276 deletions

View file

@ -2,3 +2,12 @@ This directory contains the common infrastructure for the following tests.
- referrer-policy/
- mixed-content/
- upgrade-insecure-requests/
Subdirectories:
- `subresource`:
Serves subresources, with support for redirects, stash, etc.
The subresource paths are managed by `subresourceMap` and
fetched in `requestVia*()` functions in `resources/common.js`.
- `scope`:
Serves nested contexts, such as iframe documents or workers.
Used from `invokeFrom*()` functions in `resources/common.js`.

View file

@ -5,6 +5,133 @@
* method's JSDoc.
*/
// ===============================================================
// Types
// ===============================================================
// Objects of the following types are used to represent what kind of
// subresource requests should be sent with what kind of policies,
// from what kind of possibly nested source contexts.
// The objects are represented as JSON objects (not JavaScript/Python classes
// in a strict sense) to be passed between JavaScript/Python code.
// Note: So far this document covers:
// - resources/common.js : client-side test infra code
// - scope/ - server-side scripts that serves nested source contexts
// but doesn't cover:
// - tools/ - generator scripts that generates top-level HTML documents.
// There are some policies only handled by generators (e.g. mixed-content
// opt-ins) and not yet covered by the docs here.
/**
@typedef PolicyDelivery
@type {object}
Referrer policy etc. can be applied/delivered in several ways.
A PolicyDelivery object specifies what policy is delivered and how.
@property {string} deliveryType
Specifies how the policy is delivered.
The valid deliveryType are:
"attr"
[A] DOM attributes e.g. referrerPolicy.
"rel-noref"
[A] <link rel="noreferrer"> (referrer-policy only).
"http-rp"
[B] HTTP response headers.
"meta"
[B] <meta> elements.
@property {string} key
@property {string} value
Specifies what policy to be delivered. The valid keys are:
"referrerPolicy"
Referrer Policy
https://w3c.github.io/webappsec-referrer-policy/
Valid values are those listed in
https://w3c.github.io/webappsec-referrer-policy/#referrer-policy
(except that "" is represented as null/None)
A PolicyDelivery can be specified in several ways:
- (for [A]) Associated with an individual subresource request and
specified in `Subresource.policies`,
e.g. referrerPolicy attributes of DOM elements.
This is handled in invokeRequest().
- (for [B]) Associated with an nested environmental settings object and
specified in `SourceContext.policies`,
e.g. HTTP referrer-policy response headers of HTML/worker scripts.
This is handled in server-side under /common/security-features/scope/.
- (for [B]) Associated with the top-level HTML document.
This is handled by the generators.d
*/
/**
@typedef Subresource
@type {object}
A Subresource represents how a subresource request is sent.
@property{SubresourceType} subresourceType
How the subresource request is sent,
e.g. "img-tag" for sending a request via <img src>.
See the keys of `subresourceMap` for valid values.
@property{string} url
subresource's URL.
Typically this is constructed by getRequestURLs() below.
@property{PolicyDelivery} policyDeliveries
Policies delivered specific to the subresource request.
*/
/**
@typedef SourceContext
@type {object}
Requests can be possibly sent from various kinds of source contexts, i.e.
fetch client's environment settings objects:
top-level windows, iframes, or workers.
A SourceContext object specifies one environment settings object, and
an Array<SourceContext> specifies a possibly nested context,
from the outer-most to inner-most environment settings objects.
For example:
[{sourceContextType: "srcdoc"}, {sourceContextType: "classic-worker"}]
means that a subresource request is to be sent from
a classic dedicated worker created from <iframe srcdoc>
inside the top-level HTML document.
Note: the top-level document is not included in the array and
is assumed implicitly.
SourceContext (or Array<SourceContext>) is set based on
the fetch client's settings object that is used for the subresource request,
NOT on module map settings object, and
NOT on the inner-most settings object that appears in the test.
For example, Array<SourceContext> is `[]` (indicating the top Window)
for `worker.js`
- When it is the root worker script: `new Worker('worker.js')`, or
- When it is imported from the root worker script:
`new Worker('top.js', {type: 'module'})`
where `top.js` has `import 'worker.js'`.
because the request for `worker.js` uses the Window as its fetch client's
settings object, while a WorkerGlobalScope is created though.
@property {string} sourceContextType
Kind of the source context to be used.
Valid values are the keys of `sourceContextMap` below.
@property {Array<PolicyDelivery>} policyDeliveries
A list of PolicyDelivery applied to the source context.
*/
// ===============================================================
// General utility functions
// ===============================================================
function timeoutPromise(t, ms) {
return new Promise(resolve => { t.step_timeout(resolve, ms); });
}
@ -70,8 +197,16 @@ function xhrRequest(url, responseType) {
*/
function setAttributes(el, attrs) {
attrs = attrs || {}
for (var attr in attrs)
el.setAttribute(attr, attrs[attr]);
for (var attr in attrs) {
if (attr !== 'src')
el.setAttribute(attr, attrs[attr]);
}
// Workaround for Chromium: set <img>'s src attribute after all other
// attributes to ensure the policy is applied.
for (var attr in attrs) {
if (attr === 'src')
el.setAttribute(attr, attrs[attr]);
}
}
/**
@ -202,52 +337,79 @@ function wrapResult(server_data) {
}
}
// `requestVia*()` functions return promises that are resolved on successful
// requests with objects with the following keys:
// - `headers`: HTTP request headers sent to server.
// - `referrer`: Referrer.
// - `location`: The URL of the subresource.
//
// Category 1:
// `headers`: set.
// `referrer`: set via `document.referrer`.
// `location`: set via `document.location`.
// See `template/document.html.template`.
// Category 2:
// `headers`: set.
// `referrer`: set to `headers.referer` by `wrapResult()`.
// `location`: not set.
// Category 3:
// All the keys listed above are NOT set.
//
// -------------------------------- -------- --------------------------
// Function name Category Used in
// -------- ------- ---------
// referrer mixed- upgrade-
// policy content insecure-
// policy content request
// -------------------------------- -------- -------- ------- ---------
// requestViaAnchor 1 Y Y -
// requestViaArea 1 Y Y -
// requestViaAudio 3 - Y -
// requestViaDedicatedWorker 2 Y Y Y
// requestViaFetch 2 Y Y -
// requestViaForm 3 - Y -
// requestViaIframe 1 Y Y -
// requestViaImage 3 - Y -
// requestViaImageForReferrerPolicy 2 Y - -
// requestViaLinkPrefetch 3 - Y -
// requestViaLinkStylesheet 3 - Y -
// requestViaObject 3 - Y -
// requestViaPicture 3 - Y -
// requestViaScript 2 Y Y -
// requestViaSendBeacon 3 - Y -
// requestViaSharedWorker 2 Y - -
// requestViaVideo 3 - Y -
// requestViaWebSocket 3 - Y -
// requestViaWorklet 3 - Y Y
// requestViaXhr 2 Y Y -
// -------------------------------- -------- -------- ------- ---------
// ===============================================================
// Subresources
// ===============================================================
/**
@typedef RequestResult
@type {object}
Represents the result of sending an request.
All properties are optional. See the comments for
requestVia*() and invokeRequest() below to see which properties are set.
@property {Array<Object<string, string>>} headers
HTTP request headers sent to server.
@property {string} referrer - Referrer.
@property {string} location - The URL of the subresource.
@property {string} sourceContextUrl
the URL of the global object where the actual request is sent.
*/
/**
requestVia*(url, additionalAttributes) functions send a subresource
request from the current environment settings object.
@param {string} url
The URL of the subresource.
@param {Object<string, string>} additionalAttributes
Additional attributes set to DOM elements
(element-initiated requests only).
@returns {Promise} that are resolved with a RequestResult object
on successful requests.
- Category 1:
`headers`: set.
`referrer`: set via `document.referrer`.
`location`: set via `document.location`.
See `template/document.html.template`.
- Category 2:
`headers`: set.
`referrer`: set to `headers.referer` by `wrapResult()`.
`location`: not set.
- Category 3:
All the keys listed above are NOT set.
`sourceContextUrl` is not set here.
-------------------------------- -------- --------------------------
Function name Category Used in
-------- ------- ---------
referrer mixed- upgrade-
policy content insecure-
policy content request
-------------------------------- -------- -------- ------- ---------
requestViaAnchor 1 Y Y -
requestViaArea 1 Y Y -
requestViaAudio 3 - Y -
requestViaDedicatedWorker 2 Y Y Y
requestViaFetch 2 Y Y -
requestViaForm 3 - Y -
requestViaIframe 1 Y Y -
requestViaImage 2 Y Y -
requestViaLinkPrefetch 3 - Y -
requestViaLinkStylesheet 3 - Y -
requestViaObject 3 - Y -
requestViaPicture 3 - Y -
requestViaScript 2 Y Y -
requestViaSendBeacon 3 - Y -
requestViaSharedWorker 2 Y - -
requestViaVideo 3 - Y -
requestViaWebSocket 3 - Y -
requestViaWorklet 3 - Y Y
requestViaXhr 2 Y Y -
-------------------------------- -------- -------- ------- ---------
*/
/**
* Creates a new iframe, binds load and error events, sets the src attribute and
@ -274,40 +436,23 @@ function requestViaIframe(url, additionalAttributes) {
* @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);
function requestViaImage(url, additionalAttributes) {
const img = createElement(
"img",
// crossOrigin attribute is added to read the pixel data of the response.
Object.assign({"src": url, "crossOrigin": "Anonymous"}, additionalAttributes),
document.body, true);
return img.eventPromise.then(() => wrapResult(decodeImageData(img)));
}
// Helpers for requestViaImageForReferrerPolicy().
function loadImageInWindow(src, attributes, w) {
return new Promise((resolve, reject) => {
var image = new w.Image();
image.crossOrigin = "Anonymous";
image.onload = function() {
resolve(image);
};
// Helper for requestViaImage().
function decodeImageData(img) {
var canvas = document.createElement("canvas");
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0);
var imgData = context.getImageData(0, 0, img.clientWidth, img.clientHeight);
const rgba = imgData.data;
// Extend element with attributes. (E.g. "referrerPolicy" or "rel")
if (attributes) {
for (var attr in attributes) {
image[attr] = attributes[attr];
}
}
image.src = src;
w.document.body.appendChild(image)
});
}
function extractImageData(img) {
var canvas = document.createElement("canvas");
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0);
var imgData = context.getImageData(0, 0, img.clientWidth, img.clientHeight);
return imgData.data;
}
function decodeImageData(rgba) {
let decodedBytes = new Uint8ClampedArray(rgba.length);
let decodedLength = 0;
@ -349,62 +494,6 @@ function decodeImageData(rgba) {
return JSON.parse(string_data);
}
// A variant of requestViaImage for referrer policy tests.
// This tests many patterns of <iframe>s to test referrer policy inheritance.
// TODO(https://crbug.com/906850): Merge this into requestViaImage().
// <iframe>-related code should be moved outside requestViaImage*().
function requestViaImageForReferrerPolicy(url, attributes, referrerPolicy) {
// For images, we'll test:
// - images in a `srcdoc` frame to ensure that it uses the referrer
// policy of its parent,
// - images in a top-level document,
// - and images in a `srcdoc` frame with its own referrer policy to
// override its parent.
var iframeWithoutOwnPolicy = document.createElement('iframe');
var noSrcDocPolicy = new Promise((resolve, reject) => {
iframeWithoutOwnPolicy.srcdoc = "Hello, world.";
iframeWithoutOwnPolicy.onload = resolve;
document.body.appendChild(iframeWithoutOwnPolicy);
})
.then(() => {
var nextUrl = url + "&cache_destroyer2=" + (new Date()).getTime();
return loadImageInWindow(nextUrl, attributes,
iframeWithoutOwnPolicy.contentWindow);
})
.then(function (img) {
return decodeImageData(extractImageData(img));
});
// Give a srcdoc iframe a referrer policy different from the top-level page's policy.
var iframePolicy = (referrerPolicy === "no-referrer") ? "unsafe-url" : "no-referrer";
var iframeWithOwnPolicy = document.createElement('iframe');
var srcDocPolicy = new Promise((resolve, reject) => {
iframeWithOwnPolicy.srcdoc = "<meta name='referrer' content='" + iframePolicy + "'>Hello world.";
iframeWithOwnPolicy.onload = resolve;
document.body.appendChild(iframeWithOwnPolicy);
})
.then(() => {
var nextUrl = url + "&cache_destroyer3=" + (new Date()).getTime();
return loadImageInWindow(nextUrl, null,
iframeWithOwnPolicy.contentWindow);
})
.then(function (img) {
return decodeImageData(extractImageData(img));
});
var pagePolicy = loadImageInWindow(url, attributes, window)
.then(function (img) {
return decodeImageData(extractImageData(img));
});
return Promise.all([noSrcDocPolicy, srcDocPolicy, pagePolicy]).then(values => {
assert_equals(values[0].headers.referer, values[2].headers.referer, "Referrer inside 'srcdoc' without its own policy should be the same as embedder's referrer.");
assert_equals((iframePolicy === "no-referrer" ? undefined : document.location.href), values[1].headers.referer, "Referrer inside 'srcdoc' should use the iframe's policy if it has one");
return wrapResult(values[2]);
});
}
/**
* Initiates a new XHR GET request to provided URL.
* @param {string} url The endpoint URL for the XHR.
@ -745,6 +834,14 @@ function requestViaWebSocket(url) {
});
}
/**
@typedef SubresourceType
@type {string}
Represents how a subresource is sent.
The keys of `subresourceMap` below are the valid values.
*/
// Subresource paths and invokers.
const subresourceMap = {
"a-tag": {
@ -778,7 +875,6 @@ const subresourceMap = {
"img-tag": {
path: "/common/security-features/subresource/image.py",
invoker: requestViaImage,
invokerForReferrerPolicy: requestViaImageForReferrerPolicy,
},
"link-css-tag": {
path: "/common/security-features/subresource/empty.py",
@ -854,6 +950,35 @@ for (const workletType of ['animation', 'audio', 'layout', 'paint']) {
};
}
/**
@typedef RedirectionType
@type {string}
Represents what redirects should occur to the subresource request
after initial request.
See preprocess_redirection() in
/common/security-features/subresource/subresource.py for valid values.
*/
/**
Construct subresource (and related) URLs.
@param {SubresourceType} subresourceType
@param {OriginType} originType
@param {RedirectionType} redirectionType
@returns {object} with following properties:
{string} testUrl
The subresource request URL.
{string} announceUrl
{string} assertUrl
The URLs to be used for detecting whether `testUrl` is actually sent
to the server.
1. Fetch `announceUrl` first,
2. then possibly fetch `testUrl`, and
3. finally fetch `assertUrl`.
The fetch result of `assertUrl` should indicate whether
`testUrl` is actually sent to the server or not.
*/
function getRequestURLs(subresourceType, originType, redirectionType) {
const key = guid();
const value = guid();
@ -875,6 +1000,122 @@ function getRequestURLs(subresourceType, originType, redirectionType) {
};
}
// ===============================================================
// Source Context
// ===============================================================
// Requests can be sent from several source contexts,
// such as the main documents, iframes, workers, or so,
// possibly nested, and possibly with <meta>/http headers added.
// invokeRequest() and invokeFrom*() functions handles
// SourceContext-related setup in client-side.
/**
invokeRequest() invokes a subresource request
(specified as `subresource`)
from a (possibly nested) environment settings object
(specified as `sourceContextList`).
For nested contexts, invokeRequest() calls an invokeFrom*() function
that creates a nested environment settings object using
/common/security-features/scope/, which calls invokeRequest()
again inside the nested environment settings object.
This cycle continues until all specified
nested environment settings object are created, and
finally invokeRequest() calls a requestVia*() function to start the
subresource request from the inner-most environment settings object.
@param {Subresource} subresource
@param {Array<SourceContext>} sourceContextList
@returns {Promise} A promise that is resolved with an RequestResult object.
`sourceContextUrl` is always set. For whether other properties are set,
see the comments for requestVia*() above.
*/
function invokeRequest(subresource, sourceContextList) {
if (sourceContextList.length === 0) {
// No further nested global objects. Send the subresource request here.
const additionalAttributes = {};
/** @type {PolicyDelivery} policyDelivery */
for (const policyDelivery of (subresource.policyDeliveries || [])) {
// Depending on the delivery method, extend the subresource element with
// these attributes.
if (policyDelivery.deliveryType === "attr") {
additionalAttributes[policyDelivery.key] = policyDelivery.value;
} else if (policyDelivery.deliveryType === "rel-noref") {
additionalAttributes["rel"] = "noreferrer";
}
}
return subresourceMap[subresource.subresourceType].invoker(
subresource.url,
additionalAttributes)
.then(result => Object.assign(
{sourceContextUrl: location.toString()},
result));
}
// Defines invokers for each valid SourceContext.sourceContextType.
const sourceContextMap = {
"srcdoc": { // <iframe srcdoc></iframe>
invoker: invokeFromIframe,
},
"iframe": { // <iframe src="same-origin-URL"></iframe>
invoker: invokeFromIframe,
},
};
return sourceContextMap[sourceContextList[0].sourceContextType].invoker(
subresource, sourceContextList);
}
/**
invokeFrom*() functions are helper functions with the same parameters
and return values as invokeRequest(), that are tied to specific types
of top-most environment settings objects.
For example, invokeFromIframe() is the helper function for the cases where
sourceContextList[0] is an iframe.
*/
function invokeFromIframe(subresource, sourceContextList) {
const currentSourceContext = sourceContextList.shift();
const frameUrl =
"/common/security-features/scope/document.py?policyDeliveries=" +
encodeURIComponent(JSON.stringify(
currentSourceContext.policyDeliveries || []));
let promise;
if (currentSourceContext.sourceContextType === 'srcdoc') {
promise = fetch(frameUrl)
.then(r => r.text())
.then(srcdoc => {
return createElement("iframe", {srcdoc: srcdoc}, document.body, true);
});
} else if (currentSourceContext.sourceContextType === 'iframe') {
promise = Promise.resolve(
createElement("iframe", {src: frameUrl}, document.body, true));
}
return promise
.then(iframe => {
return iframe.eventPromise
.then(() => {
const promise = bindEvents2(
window, "message", iframe, "error", window, "error");
iframe.contentWindow.postMessage(
{subresource: subresource,
sourceContextList: sourceContextList},
"*");
return promise;
})
.then(event => {
if (event.data.error)
return Promise.reject(event.data.error);
return event.data;
});
});
}
// SanityChecker does nothing in release mode. See sanity-checker.js for debug
// mode.
function SanityChecker() {}

View file

@ -0,0 +1,35 @@
import os, sys, json
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import util
def main(request, response):
policyDeliveries = json.loads(request.GET.first("policyDeliveries", "[]"))
maybe_additional_headers = {}
meta = ''
error = ''
for delivery in policyDeliveries:
if delivery['deliveryType'] == 'meta':
if delivery['key'] == 'referrerPolicy':
meta += '<meta name="referrer" content="%s">' % delivery['value']
else:
error = 'invalid delivery key'
elif delivery['deliveryType'] == 'http-rp':
if delivery['key'] == 'referrerPolicy':
maybe_additional_headers['Referrer-Policy'] = delivery['value']
else:
error = 'invalid delivery key'
else:
error = 'invalid deliveryType'
handler = lambda: util.get_template("document.html.template") % ({
"meta": meta,
"error": error
})
util.respond(
request,
response,
payload_generator=handler,
content_type="text/html",
maybe_additional_headers=maybe_additional_headers)

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
%(meta)s
<script src="/common/security-features/resources/common.js"></script>
<script>
// Receive a message from the parent and start the test.
function onMessageFromParent(event) {
// Because this window might receive messages from child iframe during
// tests, we first remove the listener here before staring the test.
window.removeEventListener('message', onMessageFromParent);
const configurationError = "%(error)s";
if (configurationError.length > 0) {
parent.postMessage({error: configurationError}, "*");
return;
}
invokeRequest(event.data.subresource,
event.data.sourceContextList)
.then(result => parent.postMessage(result, "*"))
.catch(e => {
const message = (e.error && e.error.stack) || e.message || "Error";
parent.postMessage({error: message}, "*");
});
}
window.addEventListener('message', onMessageFromParent);
</script>
</head>
</html>

View file

@ -0,0 +1,42 @@
import os
def get_template(template_basename):
script_directory = os.path.dirname(os.path.abspath(__file__))
template_directory = os.path.abspath(
os.path.join(script_directory, "template"))
template_filename = os.path.join(template_directory, template_basename)
with open(template_filename, "r") as f:
return f.read()
def __noop(request, response):
return ""
def respond(request,
response,
status_code=200,
content_type="text/html",
payload_generator=__noop,
cache_control="no-cache; must-revalidate",
access_control_allow_origin="*",
maybe_additional_headers=None):
response.add_required_headers = False
response.writer.write_status(status_code)
if access_control_allow_origin != None:
response.writer.write_header("access-control-allow-origin",
access_control_allow_origin)
response.writer.write_header("content-type", content_type)
response.writer.write_header("cache-control", cache_control)
additional_headers = maybe_additional_headers or {}
for header, value in additional_headers.items():
response.writer.write_header(header, value)
response.writer.end_headers()
payload = payload_generator()
response.writer.write(payload)

View file

@ -0,0 +1,189 @@
from __future__ import print_function
import copy
import os, sys, json
import spec_validator
import argparse
import util
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(config, selection, spec, test_html_template_basename):
# TODO: Refactor out this referrer-policy-specific part.
if 'referrer_policy' in spec:
# Oddball: it can be None, so in JS it's null.
selection['referrer_policy'] = spec['referrer_policy']
test_parameters = json.dumps(selection, indent=2, separators=(',', ':'))
# Adjust the template for the test invoking JS. Indent it to look nice.
indent = "\n" + " " * 8
test_parameters = test_parameters.replace("\n", indent)
selection['test_js'] = '''
%s(
%s,
document.querySelector("meta[name=assert]").content,
new SanityChecker()
).start();
''' % (config.test_case_name, test_parameters)
selection['spec_name'] = spec['name']
selection['test_page_title'] = config.test_page_title_template % spec['title']
selection['spec_description'] = spec['description']
selection['spec_specification_url'] = spec['specification_url']
selection['helper_js'] = config.helper_js
selection['sanity_checker_js'] = config.sanity_checker_js
selection['spec_json_js'] = config.spec_json_js
test_filename = os.path.join(config.spec_directory, config.test_file_path_pattern % selection)
test_headers_filename = test_filename + ".headers"
test_directory = os.path.dirname(test_filename)
test_html_template = util.get_template(test_html_template_basename)
disclaimer_template = util.get_template('disclaimer.template')
html_template_filename = os.path.join(util.template_directory,
test_html_template_basename)
generated_disclaimer = disclaimer_template \
% {'generating_script_filename': os.path.relpath(__file__,
util.test_root_directory),
'html_template_filename': os.path.relpath(html_template_filename,
util.test_root_directory)}
# Adjust the template for the test invoking JS. Indent it to look nice.
selection['generated_disclaimer'] = generated_disclaimer.rstrip()
selection['test_description'] = config.test_description_template % selection
selection['test_description'] = \
selection['test_description'].rstrip().replace("\n", "\n" + " " * 33)
# Directory for the test files.
try:
os.makedirs(test_directory)
except:
pass
delivery = config.handleDelivery(selection, spec)
if len(delivery['headers']) > 0:
with open(test_headers_filename, "w") as f:
for header in delivery['headers']:
f.write(header)
f.write('\n')
selection['meta_delivery_method'] = delivery['meta']
# Obey the lint and pretty format.
if len(selection['meta_delivery_method']) > 0:
selection['meta_delivery_method'] = "\n " + \
selection['meta_delivery_method']
# Write out the generated HTML file.
util.write_file(test_filename, test_html_template % selection)
def generate_test_source_files(config, spec_json, target):
test_expansion_schema = spec_json['test_expansion_schema']
specification = spec_json['specification']
spec_json_js_template = util.get_template('spec_json.js.template')
generated_spec_json_filename = os.path.join(config.spec_directory, "spec_json.js")
util.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']
artifact_order.remove('expansion')
# 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 = config.selection_pattern % excluded_selection
exclusion_dict[excluded_selection_path] = True
for spec in specification:
# Used to make entries with expansion="override" override preceding
# entries with the same |selection_path|.
output_dict = {}
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 = config.selection_pattern % selection
if not selection_path in exclusion_dict:
if selection_path in output_dict:
if expansion_pattern['expansion'] != 'override':
print("Error: %s's expansion is default but overrides %s" % (selection['name'], output_dict[selection_path]['name']))
sys.exit(1)
output_dict[selection_path] = copy.deepcopy(selection)
else:
print('Excluding selection:', selection_path)
for selection_path in output_dict:
selection = output_dict[selection_path]
generate_selection(config,
selection,
spec,
html_template)
def main(config):
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()
if args.spec:
config.spec_directory = args.spec
spec_filename = os.path.join(config.spec_directory, "spec.src.json")
spec_json = util.load_spec_json(spec_filename)
spec_validator.assert_valid_spec_json(spec_json)
generate_test_source_files(config, spec_json, args.target)

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,24 @@
<!DOCTYPE html>
%(generated_disclaimer)s
<html>
<head>
<title>%(test_page_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">%(meta_delivery_method)s
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/security-features/resources/common.js"></script>
<!-- The original specification JSON for validating the scenario. -->
<script src="%(spec_json_js)s"></script>
<!-- Internal checking of the tests -->
<script src="%(sanity_checker_js)s"></script>
<script src="%(helper_js)s"></script>
</head>
<body>
<script>%(test_js)s</script>
<div id="log"></div>
</body>
</html>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
%(generated_disclaimer)s
<html>
<head>
<title>%(test_page_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">%(meta_delivery_method)s
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/security-features/resources/common.js"></script>
<script src="%(helper_js)s"></script>
</head>
<body>
<script>%(test_js)s</script>
<div id="log"></div>
</body>
</html>

View file

@ -0,0 +1,41 @@
from __future__ import print_function
import os, sys, json, re
script_directory = os.path.dirname(os.path.abspath(__file__))
template_directory = os.path.abspath(os.path.join(script_directory,
'template'))
test_root_directory = os.path.abspath(os.path.join(script_directory,
'..', '..', '..'))
def get_template(basename):
with open(os.path.join(template_directory, basename), "r") 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):
re_error_location = re.compile('line ([0-9]+) column ([0-9]+)')
with open(path_to_spec, "r") as f:
try:
return json.load(f)
except ValueError as 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)