| Index: sky/engine/core/inspector/generate-inspector-protocol-version
|
| diff --git a/sky/engine/core/inspector/generate-inspector-protocol-version b/sky/engine/core/inspector/generate-inspector-protocol-version
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..d6ee227267c3e3730ff34c0c12e6b524a0d441f2
|
| --- /dev/null
|
| +++ b/sky/engine/core/inspector/generate-inspector-protocol-version
|
| @@ -0,0 +1,476 @@
|
| +#!/usr/bin/env python
|
| +# Copyright (c) 2011 Google Inc. All rights reserved.
|
| +#
|
| +# Redistribution and use in source and binary forms, with or without
|
| +# modification, are permitted provided that the following conditions are
|
| +# met:
|
| +#
|
| +# * Redistributions of source code must retain the above copyright
|
| +# notice, this list of conditions and the following disclaimer.
|
| +# * Redistributions in binary form must reproduce the above
|
| +# copyright notice, this list of conditions and the following disclaimer
|
| +# in the documentation and/or other materials provided with the
|
| +# distribution.
|
| +# * Neither the name of Google Inc. nor the names of its
|
| +# contributors may be used to endorse or promote products derived from
|
| +# this software without specific prior written permission.
|
| +#
|
| +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| +#
|
| +# Inspector protocol validator.
|
| +#
|
| +# Tests that subsequent protocol changes are not breaking backwards compatibility.
|
| +# Following violations are reported:
|
| +#
|
| +# - Domain has been removed
|
| +# - Command has been removed
|
| +# - Required command parameter was added or changed from optional
|
| +# - Required response parameter was removed or changed to optional
|
| +# - Event has been removed
|
| +# - Required event parameter was removed or changed to optional
|
| +# - Parameter type has changed.
|
| +#
|
| +# For the parameters with composite types the above checks are also applied
|
| +# recursively to every property of the type.
|
| +#
|
| +# Adding --show_changes to the command line prints out a list of valid public API changes.
|
| +
|
| +import os.path
|
| +import re
|
| +import sys
|
| +
|
| +def list_to_map(items, key):
|
| + result = {}
|
| + for item in items:
|
| + if not "hidden" in item:
|
| + result[item[key]] = item
|
| + return result
|
| +
|
| +def named_list_to_map(container, name, key):
|
| + if name in container:
|
| + return list_to_map(container[name], key)
|
| + return {}
|
| +
|
| +def removed(reverse):
|
| + if reverse:
|
| + return "added"
|
| + return "removed"
|
| +
|
| +def required(reverse):
|
| + if reverse:
|
| + return "optional"
|
| + return "required"
|
| +
|
| +def compare_schemas(schema_1, schema_2, reverse):
|
| + errors = []
|
| + types_1 = normalize_types_in_schema(schema_1)
|
| + types_2 = normalize_types_in_schema(schema_2)
|
| +
|
| + domains_by_name_1 = list_to_map(schema_1, "domain")
|
| + domains_by_name_2 = list_to_map(schema_2, "domain")
|
| +
|
| + for name in domains_by_name_1:
|
| + domain_1 = domains_by_name_1[name]
|
| + if not name in domains_by_name_2:
|
| + errors.append("%s: domain has been %s" % (name, removed(reverse)))
|
| + continue
|
| + compare_domains(domain_1, domains_by_name_2[name], types_1, types_2, errors, reverse)
|
| + return errors
|
| +
|
| +def compare_domains(domain_1, domain_2, types_map_1, types_map_2, errors, reverse):
|
| + domain_name = domain_1["domain"]
|
| + commands_1 = named_list_to_map(domain_1, "commands", "name")
|
| + commands_2 = named_list_to_map(domain_2, "commands", "name")
|
| + for name in commands_1:
|
| + command_1 = commands_1[name]
|
| + if not name in commands_2:
|
| + errors.append("%s.%s: command has been %s" % (domain_1["domain"], name, removed(reverse)))
|
| + continue
|
| + compare_commands(domain_name, command_1, commands_2[name], types_map_1, types_map_2, errors, reverse)
|
| +
|
| + events_1 = named_list_to_map(domain_1, "events", "name")
|
| + events_2 = named_list_to_map(domain_2, "events", "name")
|
| + for name in events_1:
|
| + event_1 = events_1[name]
|
| + if not name in events_2:
|
| + errors.append("%s.%s: event has been %s" % (domain_1["domain"], name, removed(reverse)))
|
| + continue
|
| + compare_events(domain_name, event_1, events_2[name], types_map_1, types_map_2, errors, reverse)
|
| +
|
| +def compare_commands(domain_name, command_1, command_2, types_map_1, types_map_2, errors, reverse):
|
| + context = domain_name + "." + command_1["name"]
|
| +
|
| + params_1 = named_list_to_map(command_1, "parameters", "name")
|
| + params_2 = named_list_to_map(command_2, "parameters", "name")
|
| + # Note the reversed order: we allow removing but forbid adding parameters.
|
| + compare_params_list(context, "parameter", params_2, params_1, types_map_2, types_map_1, 0, errors, not reverse)
|
| +
|
| + returns_1 = named_list_to_map(command_1, "returns", "name")
|
| + returns_2 = named_list_to_map(command_2, "returns", "name")
|
| + compare_params_list(context, "response parameter", returns_1, returns_2, types_map_1, types_map_2, 0, errors, reverse)
|
| +
|
| +def compare_events(domain_name, event_1, event_2, types_map_1, types_map_2, errors, reverse):
|
| + context = domain_name + "." + event_1["name"]
|
| + params_1 = named_list_to_map(event_1, "parameters", "name")
|
| + params_2 = named_list_to_map(event_2, "parameters", "name")
|
| + compare_params_list(context, "parameter", params_1, params_2, types_map_1, types_map_2, 0, errors, reverse)
|
| +
|
| +def compare_params_list(context, kind, params_1, params_2, types_map_1, types_map_2, depth, errors, reverse):
|
| + for name in params_1:
|
| + param_1 = params_1[name]
|
| + if not name in params_2:
|
| + if not "optional" in param_1:
|
| + errors.append("%s.%s: required %s has been %s" % (context, name, kind, removed(reverse)))
|
| + continue
|
| +
|
| + param_2 = params_2[name]
|
| + if param_2 and "optional" in param_2 and not "optional" in param_1:
|
| + errors.append("%s.%s: %s %s is now %s" % (context, name, required(reverse), kind, required(not reverse)))
|
| + continue
|
| + type_1 = extract_type(param_1, types_map_1, errors)
|
| + type_2 = extract_type(param_2, types_map_2, errors)
|
| + compare_types(context + "." + name, kind, type_1, type_2, types_map_1, types_map_2, depth, errors, reverse)
|
| +
|
| +def compare_types(context, kind, type_1, type_2, types_map_1, types_map_2, depth, errors, reverse):
|
| + if depth > 10:
|
| + return
|
| +
|
| + base_type_1 = type_1["type"]
|
| + base_type_2 = type_2["type"]
|
| +
|
| + if base_type_1 != base_type_2:
|
| + errors.append("%s: %s base type mismatch, '%s' vs '%s'" % (context, kind, base_type_1, base_type_2))
|
| + elif base_type_1 == "object":
|
| + params_1 = named_list_to_map(type_1, "properties", "name")
|
| + params_2 = named_list_to_map(type_2, "properties", "name")
|
| + # If both parameters have the same named type use it in the context.
|
| + if "id" in type_1 and "id" in type_2 and type_1["id"] == type_2["id"]:
|
| + type_name = type_1["id"]
|
| + else:
|
| + type_name = "<object>"
|
| + context += " %s->%s" % (kind, type_name)
|
| + compare_params_list(context, "property", params_1, params_2, types_map_1, types_map_2, depth + 1, errors, reverse)
|
| + elif base_type_1 == "array":
|
| + item_type_1 = extract_type(type_1["items"], types_map_1, errors)
|
| + item_type_2 = extract_type(type_2["items"], types_map_2, errors)
|
| + compare_types(context, kind, item_type_1, item_type_2, types_map_1, types_map_2, depth + 1, errors, reverse)
|
| +
|
| +def extract_type(typed_object, types_map, errors):
|
| + if "type" in typed_object:
|
| + result = { "id": "<transient>", "type": typed_object["type"] }
|
| + if typed_object["type"] == "object":
|
| + result["properties"] = []
|
| + elif typed_object["type"] == "array":
|
| + result["items"] = typed_object["items"]
|
| + return result
|
| + elif "$ref" in typed_object:
|
| + ref = typed_object["$ref"]
|
| + if not ref in types_map:
|
| + errors.append("Can not resolve type: %s" % ref)
|
| + types_map[ref] = { "id": "<transient>", "type": "object" }
|
| + return types_map[ref]
|
| +
|
| +def normalize_types_in_schema(schema):
|
| + types = {}
|
| + for domain in schema:
|
| + domain_name = domain["domain"]
|
| + normalize_types(domain, domain_name, types)
|
| + return types
|
| +
|
| +def normalize_types(obj, domain_name, types):
|
| + if isinstance(obj, list):
|
| + for item in obj:
|
| + normalize_types(item, domain_name, types)
|
| + elif isinstance(obj, dict):
|
| + for key, value in obj.items():
|
| + if key == "$ref" and value.find(".") == -1:
|
| + obj[key] = "%s.%s" % (domain_name, value)
|
| + elif key == "id":
|
| + obj[key] = "%s.%s" % (domain_name, value)
|
| + types[obj[key]] = obj
|
| + else:
|
| + normalize_types(value, domain_name, types)
|
| +
|
| +def load_json(filename):
|
| + input_file = open(filename, "r")
|
| + json_string = input_file.read()
|
| + json_string = re.sub(":\s*true", ": True", json_string)
|
| + json_string = re.sub(":\s*false", ": False", json_string)
|
| + return eval(json_string)
|
| +
|
| +def self_test():
|
| + def create_test_schema_1():
|
| + return [
|
| + {
|
| + "domain": "Network",
|
| + "types": [
|
| + {
|
| + "id": "LoaderId",
|
| + "type": "string"
|
| + },
|
| + {
|
| + "id": "Headers",
|
| + "type": "object"
|
| + },
|
| + {
|
| + "id": "Request",
|
| + "type": "object",
|
| + "properties": [
|
| + { "name": "url", "type": "string" },
|
| + { "name": "method", "type": "string" },
|
| + { "name": "headers", "$ref": "Headers" },
|
| + { "name": "becameOptionalField", "type": "string" },
|
| + { "name": "removedField", "type": "string" },
|
| + ]
|
| + }
|
| + ],
|
| + "commands": [
|
| + {
|
| + "name": "removedCommand",
|
| + },
|
| + {
|
| + "name": "setExtraHTTPHeaders",
|
| + "parameters": [
|
| + { "name": "headers", "$ref": "Headers" },
|
| + { "name": "mismatched", "type": "string" },
|
| + { "name": "becameOptional", "$ref": "Headers" },
|
| + { "name": "removedRequired", "$ref": "Headers" },
|
| + { "name": "becameRequired", "$ref": "Headers", "optional": True },
|
| + { "name": "removedOptional", "$ref": "Headers", "optional": True },
|
| + ],
|
| + "returns": [
|
| + { "name": "mimeType", "type": "string" },
|
| + { "name": "becameOptional", "type": "string" },
|
| + { "name": "removedRequired", "type": "string" },
|
| + { "name": "becameRequired", "type": "string", "optional": True },
|
| + { "name": "removedOptional", "type": "string", "optional": True },
|
| + ]
|
| + }
|
| + ],
|
| + "events": [
|
| + {
|
| + "name": "requestWillBeSent",
|
| + "parameters": [
|
| + { "name": "frameId", "type": "string", "hidden": True },
|
| + { "name": "request", "$ref": "Request" },
|
| + { "name": "becameOptional", "type": "string" },
|
| + { "name": "removedRequired", "type": "string" },
|
| + { "name": "becameRequired", "type": "string", "optional": True },
|
| + { "name": "removedOptional", "type": "string", "optional": True },
|
| + ]
|
| + },
|
| + {
|
| + "name": "removedEvent",
|
| + "parameters": [
|
| + { "name": "errorText", "type": "string" },
|
| + { "name": "canceled", "type": "boolean", "optional": True }
|
| + ]
|
| + }
|
| + ]
|
| + },
|
| + {
|
| + "domain": "removedDomain"
|
| + }
|
| + ]
|
| +
|
| + def create_test_schema_2():
|
| + return [
|
| + {
|
| + "domain": "Network",
|
| + "types": [
|
| + {
|
| + "id": "LoaderId",
|
| + "type": "string"
|
| + },
|
| + {
|
| + "id": "Request",
|
| + "type": "object",
|
| + "properties": [
|
| + { "name": "url", "type": "string" },
|
| + { "name": "method", "type": "string" },
|
| + { "name": "headers", "type": "object" },
|
| + { "name": "becameOptionalField", "type": "string", "optional": True },
|
| + ]
|
| + }
|
| + ],
|
| + "commands": [
|
| + {
|
| + "name": "addedCommand",
|
| + },
|
| + {
|
| + "name": "setExtraHTTPHeaders",
|
| + "parameters": [
|
| + { "name": "headers", "type": "object" },
|
| + { "name": "mismatched", "type": "object" },
|
| + { "name": "becameOptional", "type": "object" , "optional": True },
|
| + { "name": "addedRequired", "type": "object" },
|
| + { "name": "becameRequired", "type": "object" },
|
| + { "name": "addedOptional", "type": "object", "optional": True },
|
| + ],
|
| + "returns": [
|
| + { "name": "mimeType", "type": "string" },
|
| + { "name": "becameOptional", "type": "string", "optional": True },
|
| + { "name": "addedRequired", "type": "string"},
|
| + { "name": "becameRequired", "type": "string" },
|
| + { "name": "addedOptional", "type": "string", "optional": True },
|
| + ]
|
| + }
|
| + ],
|
| + "events": [
|
| + {
|
| + "name": "requestWillBeSent",
|
| + "parameters": [
|
| + { "name": "request", "$ref": "Request" },
|
| + { "name": "becameOptional", "type": "string", "optional": True },
|
| + { "name": "addedRequired", "type": "string"},
|
| + { "name": "becameRequired", "type": "string" },
|
| + { "name": "addedOptional", "type": "string", "optional": True },
|
| + ]
|
| + },
|
| + {
|
| + "name": "addedEvent"
|
| + }
|
| + ]
|
| + },
|
| + {
|
| + "domain": "addedDomain"
|
| + }
|
| + ]
|
| +
|
| + expected_errors = [
|
| + "removedDomain: domain has been removed",
|
| + "Network.removedCommand: command has been removed",
|
| + "Network.removedEvent: event has been removed",
|
| + "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, 'object' vs 'string'",
|
| + "Network.setExtraHTTPHeaders.addedRequired: required parameter has been added",
|
| + "Network.setExtraHTTPHeaders.becameRequired: optional parameter is now required",
|
| + "Network.setExtraHTTPHeaders.removedRequired: required response parameter has been removed",
|
| + "Network.setExtraHTTPHeaders.becameOptional: required response parameter is now optional",
|
| + "Network.requestWillBeSent.removedRequired: required parameter has been removed",
|
| + "Network.requestWillBeSent.becameOptional: required parameter is now optional",
|
| + "Network.requestWillBeSent.request parameter->Network.Request.removedField: required property has been removed",
|
| + "Network.requestWillBeSent.request parameter->Network.Request.becameOptionalField: required property is now optional",
|
| + ]
|
| +
|
| + expected_errors_reverse = [
|
| + "addedDomain: domain has been added",
|
| + "Network.addedEvent: event has been added",
|
| + "Network.addedCommand: command has been added",
|
| + "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, 'string' vs 'object'",
|
| + "Network.setExtraHTTPHeaders.removedRequired: required parameter has been removed",
|
| + "Network.setExtraHTTPHeaders.becameOptional: required parameter is now optional",
|
| + "Network.setExtraHTTPHeaders.addedRequired: required response parameter has been added",
|
| + "Network.setExtraHTTPHeaders.becameRequired: optional response parameter is now required",
|
| + "Network.requestWillBeSent.becameRequired: optional parameter is now required",
|
| + "Network.requestWillBeSent.addedRequired: required parameter has been added",
|
| + ]
|
| +
|
| + def is_subset(subset, superset, message):
|
| + for i in range(len(subset)):
|
| + if subset[i] not in superset:
|
| + sys.stderr.write("%s error: %s\n" % (message, subset[i]))
|
| + return False
|
| + return True
|
| +
|
| + def errors_match(expected, actual):
|
| + return (is_subset(actual, expected, "Unexpected") and
|
| + is_subset(expected, actual, "Missing"))
|
| +
|
| + return (errors_match(expected_errors,
|
| + compare_schemas(create_test_schema_1(), create_test_schema_2(), False)) and
|
| + errors_match(expected_errors_reverse,
|
| + compare_schemas(create_test_schema_2(), create_test_schema_1(), True)))
|
| +
|
| +
|
| +def main():
|
| + if not self_test():
|
| + sys.stderr.write("Self-test failed")
|
| + return 1
|
| +
|
| + if len(sys.argv) < 4 or sys.argv[1] != "-o":
|
| + sys.stderr.write("Usage: %s -o OUTPUT_FILE INPUT_FILE [--show-changes]\n" % sys.argv[0])
|
| + return 1
|
| +
|
| + output_path = sys.argv[2]
|
| + output_file = open(output_path, "w")
|
| +
|
| + input_path = sys.argv[3]
|
| + dir_name = os.path.dirname(input_path)
|
| + schema = load_json(input_path)
|
| +
|
| + major = schema["version"]["major"]
|
| + minor = schema["version"]["minor"]
|
| + version = "%s.%s" % (major, minor)
|
| + if len(dir_name) == 0:
|
| + dir_name = "."
|
| + baseline_path = os.path.normpath(dir_name + "/Inspector-" + version + ".json")
|
| + baseline_schema = load_json(baseline_path)
|
| +
|
| + errors = compare_schemas(baseline_schema["domains"], schema["domains"], False)
|
| + if len(errors) > 0:
|
| + sys.stderr.write(" Compatibility with %s: FAILED\n" % version)
|
| + for error in errors:
|
| + sys.stderr.write( " %s\n" % error)
|
| + return 1
|
| +
|
| + if len(sys.argv) > 4 and sys.argv[4] == "--show-changes":
|
| + changes = compare_schemas(
|
| + load_json(input_path)["domains"], load_json(baseline_path)["domains"], True)
|
| + if len(changes) > 0:
|
| + print " Public changes since %s:" % version
|
| + for change in changes:
|
| + print " %s" % change
|
| +
|
| + output_file.write("""
|
| +#ifndef InspectorProtocolVersion_h
|
| +#define InspectorProtocolVersion_h
|
| +
|
| +#include "wtf/Vector.h"
|
| +#include "wtf/text/WTFString.h"
|
| +
|
| +namespace blink {
|
| +
|
| +String inspectorProtocolVersion() { return "%s"; }
|
| +
|
| +int inspectorProtocolVersionMajor() { return %s; }
|
| +
|
| +int inspectorProtocolVersionMinor() { return %s; }
|
| +
|
| +bool supportsInspectorProtocolVersion(const String& version)
|
| +{
|
| + Vector<String> tokens;
|
| + version.split(".", tokens);
|
| + if (tokens.size() != 2)
|
| + return false;
|
| +
|
| + bool ok = true;
|
| + int major = tokens[0].toInt(&ok);
|
| + if (!ok || major != %s)
|
| + return false;
|
| +
|
| + int minor = tokens[1].toInt(&ok);
|
| + if (!ok || minor > %s)
|
| + return false;
|
| +
|
| + return true;
|
| +}
|
| +
|
| +}
|
| +
|
| +#endif // !defined(InspectorProtocolVersion_h)
|
| +""" % (version, major, minor, major, minor))
|
| +
|
| + output_file.close()
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
|
|