summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/common/security-features/tools/spec_validator.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xtesting/web-platform/tests/common/security-features/tools/spec_validator.py251
1 files changed, 251 insertions, 0 deletions
diff --git a/testing/web-platform/tests/common/security-features/tools/spec_validator.py b/testing/web-platform/tests/common/security-features/tools/spec_validator.py
new file mode 100755
index 0000000000..f8a1390ef0
--- /dev/null
+++ b/testing/web-platform/tests/common/security-features/tools/spec_validator.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python3
+
+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", "script-tag-dynamic-import", "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()