blob: 15f0e1e73272b2ed8f7ba0a27707c550fd625a0e [file] [log] [blame]
#!/usr/bin/env python
from __future__ import print_function
import json, sys
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_value_from(obj, field, items):
assert obj[field] in items, \
'Field "%s" must be from: %s' % (field, str(items))
def assert_atom_or_list_items_from(obj, field, items):
if isinstance(obj[field], basestring) or isinstance(
obj[field], int) or obj[field] is None:
assert_value_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 leaf_values(schema):
if isinstance(schema, list):
return schema
ret = []
for _, sub_schema in schema.iteritems():
ret += leaf_values(sub_schema)
return ret
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_atom_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, [
"selection_pattern", "test_file_path_pattern",
"test_description_template", "test_page_title_template",
"specification", "delivery_key", "subresource_schema",
"source_context_schema", "source_context_list_schema",
"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 = test_expansion_schema.keys()
# Should be consistent with `sourceContextMap` in
# `/common/security-features/resources/common.sub.js`.
valid_source_context_names = [
"top", "iframe", "iframe-blank", "srcdoc", "worker-classic",
"worker-module", "worker-classic-data", "worker-module-data",
"sharedworker-classic", "sharedworker-module",
"sharedworker-classic-data", "sharedworker-module-data"
]
valid_subresource_names = [
"a-tag", "area-tag", "audio-tag", "form-tag", "iframe-tag", "img-tag",
"link-css-tag", "link-prefetch-tag", "object-tag", "picture-tag",
"script-tag", "video-tag"
] + ["beacon", "fetch", "xhr", "websocket"] + [
"worker-classic", "worker-module", "worker-import",
"worker-import-data", "sharedworker-classic", "sharedworker-module",
"sharedworker-import", "sharedworker-import-data",
"serviceworker-classic", "serviceworker-module",
"serviceworker-import", "serviceworker-import-data"
] + [
"worklet-animation", "worklet-audio", "worklet-layout",
"worklet-paint", "worklet-animation-import", "worklet-audio-import",
"worklet-layout-import", "worklet-paint-import",
"worklet-animation-import-data", "worklet-audio-import-data",
"worklet-layout-import-data", "worklet-paint-import-data"
]
# Validate each single spec.
for spec in specification:
details['object'] = spec
# Validate required fields for a single spec.
assert_contains_only_fields(spec, [
'title', 'description', 'specification_url', 'test_expansion'
])
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')
for spec_exp in spec['test_expansion']:
details['object'] = spec_exp
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 source_context_schema.
details['object'] = spec_json['source_context_schema']
assert_contains_only_fields(
spec_json['source_context_schema'],
['supported_delivery_type', 'supported_subresource'])
assert_contains_only_fields(
spec_json['source_context_schema']['supported_delivery_type'],
valid_source_context_names)
for source_context in spec_json['source_context_schema'][
'supported_delivery_type']:
assert_valid_artifact(
spec_json['source_context_schema']['supported_delivery_type'],
source_context, test_expansion_schema['delivery_type'])
assert_contains_only_fields(
spec_json['source_context_schema']['supported_subresource'],
valid_source_context_names)
for source_context in spec_json['source_context_schema'][
'supported_subresource']:
assert_valid_artifact(
spec_json['source_context_schema']['supported_subresource'],
source_context, leaf_values(test_expansion_schema['subresource']))
# Validate subresource_schema.
details['object'] = spec_json['subresource_schema']
assert_contains_only_fields(spec_json['subresource_schema'],
['supported_delivery_type'])
assert_contains_only_fields(
spec_json['subresource_schema']['supported_delivery_type'],
leaf_values(test_expansion_schema['subresource']))
for subresource in spec_json['subresource_schema'][
'supported_delivery_type']:
assert_valid_artifact(
spec_json['subresource_schema']['supported_delivery_type'],
subresource, test_expansion_schema['delivery_type'])
# Validate the test_expansion schema members.
details['object'] = test_expansion_schema
assert_contains_only_fields(test_expansion_schema, [
'expansion', 'source_scheme', 'source_context_list', 'delivery_type',
'delivery_value', 'redirection', 'subresource', 'origin', 'expectation'
])
assert_atom_or_list_items_from(test_expansion_schema, 'expansion',
['default', 'override'])
assert_atom_or_list_items_from(test_expansion_schema, 'source_scheme',
['http', 'https'])
assert_atom_or_list_items_from(
test_expansion_schema, 'source_context_list',
spec_json['source_context_list_schema'].keys())
# Should be consistent with `preprocess_redirection` in
# `/common/security-features/subresource/subresource.py`.
assert_atom_or_list_items_from(test_expansion_schema, 'redirection', [
'no-redirect', 'keep-origin', 'swap-origin', 'keep-scheme',
'swap-scheme', 'downgrade'
])
for subresource in leaf_values(test_expansion_schema['subresource']):
assert subresource in valid_subresource_names, "Invalid subresource %s" % subresource
# Should be consistent with getSubresourceOrigin() in
# `/common/security-features/resources/common.sub.js`.
assert_atom_or_list_items_from(test_expansion_schema, 'origin', [
'same-http', 'same-https', 'same-ws', 'same-wss', 'cross-http',
'cross-https', 'cross-ws', 'cross-wss', 'same-http-downgrade',
'cross-http-downgrade', 'same-ws-downgrade', 'cross-ws-downgrade'
])
# 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)
details['object'] = excluded_test_expansion
for artifact in test_expansion_schema:
details['test_expansion_field'] = artifact
assert_valid_artifact(excluded_test_expansion, artifact,
test_expansion_schema[artifact])
del details['test_expansion_field']
del details['object']
def assert_valid_spec_json(spec_json):
error_details = {}
try:
validate(spec_json, error_details)
except AssertionError as 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()