Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(327)

Side by Side Diff: sky/engine/core/inspector/generate-inspector-protocol-version

Issue 723773002: Bring back ScriptDebugServer and associated machinery (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2011 Google Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #
30 # Inspector protocol validator.
31 #
32 # Tests that subsequent protocol changes are not breaking backwards compatibilit y.
33 # Following violations are reported:
34 #
35 # - Domain has been removed
36 # - Command has been removed
37 # - Required command parameter was added or changed from optional
38 # - Required response parameter was removed or changed to optional
39 # - Event has been removed
40 # - Required event parameter was removed or changed to optional
41 # - Parameter type has changed.
42 #
43 # For the parameters with composite types the above checks are also applied
44 # recursively to every property of the type.
45 #
46 # Adding --show_changes to the command line prints out a list of valid public AP I changes.
47
48 import os.path
49 import re
50 import sys
51
52 def list_to_map(items, key):
53 result = {}
54 for item in items:
55 if not "hidden" in item:
56 result[item[key]] = item
57 return result
58
59 def named_list_to_map(container, name, key):
60 if name in container:
61 return list_to_map(container[name], key)
62 return {}
63
64 def removed(reverse):
65 if reverse:
66 return "added"
67 return "removed"
68
69 def required(reverse):
70 if reverse:
71 return "optional"
72 return "required"
73
74 def compare_schemas(schema_1, schema_2, reverse):
75 errors = []
76 types_1 = normalize_types_in_schema(schema_1)
77 types_2 = normalize_types_in_schema(schema_2)
78
79 domains_by_name_1 = list_to_map(schema_1, "domain")
80 domains_by_name_2 = list_to_map(schema_2, "domain")
81
82 for name in domains_by_name_1:
83 domain_1 = domains_by_name_1[name]
84 if not name in domains_by_name_2:
85 errors.append("%s: domain has been %s" % (name, removed(reverse)))
86 continue
87 compare_domains(domain_1, domains_by_name_2[name], types_1, types_2, err ors, reverse)
88 return errors
89
90 def compare_domains(domain_1, domain_2, types_map_1, types_map_2, errors, revers e):
91 domain_name = domain_1["domain"]
92 commands_1 = named_list_to_map(domain_1, "commands", "name")
93 commands_2 = named_list_to_map(domain_2, "commands", "name")
94 for name in commands_1:
95 command_1 = commands_1[name]
96 if not name in commands_2:
97 errors.append("%s.%s: command has been %s" % (domain_1["domain"], na me, removed(reverse)))
98 continue
99 compare_commands(domain_name, command_1, commands_2[name], types_map_1, types_map_2, errors, reverse)
100
101 events_1 = named_list_to_map(domain_1, "events", "name")
102 events_2 = named_list_to_map(domain_2, "events", "name")
103 for name in events_1:
104 event_1 = events_1[name]
105 if not name in events_2:
106 errors.append("%s.%s: event has been %s" % (domain_1["domain"], name , removed(reverse)))
107 continue
108 compare_events(domain_name, event_1, events_2[name], types_map_1, types_ map_2, errors, reverse)
109
110 def compare_commands(domain_name, command_1, command_2, types_map_1, types_map_2 , errors, reverse):
111 context = domain_name + "." + command_1["name"]
112
113 params_1 = named_list_to_map(command_1, "parameters", "name")
114 params_2 = named_list_to_map(command_2, "parameters", "name")
115 # Note the reversed order: we allow removing but forbid adding parameters.
116 compare_params_list(context, "parameter", params_2, params_1, types_map_2, t ypes_map_1, 0, errors, not reverse)
117
118 returns_1 = named_list_to_map(command_1, "returns", "name")
119 returns_2 = named_list_to_map(command_2, "returns", "name")
120 compare_params_list(context, "response parameter", returns_1, returns_2, typ es_map_1, types_map_2, 0, errors, reverse)
121
122 def compare_events(domain_name, event_1, event_2, types_map_1, types_map_2, erro rs, reverse):
123 context = domain_name + "." + event_1["name"]
124 params_1 = named_list_to_map(event_1, "parameters", "name")
125 params_2 = named_list_to_map(event_2, "parameters", "name")
126 compare_params_list(context, "parameter", params_1, params_2, types_map_1, t ypes_map_2, 0, errors, reverse)
127
128 def compare_params_list(context, kind, params_1, params_2, types_map_1, types_ma p_2, depth, errors, reverse):
129 for name in params_1:
130 param_1 = params_1[name]
131 if not name in params_2:
132 if not "optional" in param_1:
133 errors.append("%s.%s: required %s has been %s" % (context, name, kind, removed(reverse)))
134 continue
135
136 param_2 = params_2[name]
137 if param_2 and "optional" in param_2 and not "optional" in param_1:
138 errors.append("%s.%s: %s %s is now %s" % (context, name, required(re verse), kind, required(not reverse)))
139 continue
140 type_1 = extract_type(param_1, types_map_1, errors)
141 type_2 = extract_type(param_2, types_map_2, errors)
142 compare_types(context + "." + name, kind, type_1, type_2, types_map_1, t ypes_map_2, depth, errors, reverse)
143
144 def compare_types(context, kind, type_1, type_2, types_map_1, types_map_2, depth , errors, reverse):
145 if depth > 10:
146 return
147
148 base_type_1 = type_1["type"]
149 base_type_2 = type_2["type"]
150
151 if base_type_1 != base_type_2:
152 errors.append("%s: %s base type mismatch, '%s' vs '%s'" % (context, kind , base_type_1, base_type_2))
153 elif base_type_1 == "object":
154 params_1 = named_list_to_map(type_1, "properties", "name")
155 params_2 = named_list_to_map(type_2, "properties", "name")
156 # If both parameters have the same named type use it in the context.
157 if "id" in type_1 and "id" in type_2 and type_1["id"] == type_2["id"]:
158 type_name = type_1["id"]
159 else:
160 type_name = "<object>"
161 context += " %s->%s" % (kind, type_name)
162 compare_params_list(context, "property", params_1, params_2, types_map_1 , types_map_2, depth + 1, errors, reverse)
163 elif base_type_1 == "array":
164 item_type_1 = extract_type(type_1["items"], types_map_1, errors)
165 item_type_2 = extract_type(type_2["items"], types_map_2, errors)
166 compare_types(context, kind, item_type_1, item_type_2, types_map_1, type s_map_2, depth + 1, errors, reverse)
167
168 def extract_type(typed_object, types_map, errors):
169 if "type" in typed_object:
170 result = { "id": "<transient>", "type": typed_object["type"] }
171 if typed_object["type"] == "object":
172 result["properties"] = []
173 elif typed_object["type"] == "array":
174 result["items"] = typed_object["items"]
175 return result
176 elif "$ref" in typed_object:
177 ref = typed_object["$ref"]
178 if not ref in types_map:
179 errors.append("Can not resolve type: %s" % ref)
180 types_map[ref] = { "id": "<transient>", "type": "object" }
181 return types_map[ref]
182
183 def normalize_types_in_schema(schema):
184 types = {}
185 for domain in schema:
186 domain_name = domain["domain"]
187 normalize_types(domain, domain_name, types)
188 return types
189
190 def normalize_types(obj, domain_name, types):
191 if isinstance(obj, list):
192 for item in obj:
193 normalize_types(item, domain_name, types)
194 elif isinstance(obj, dict):
195 for key, value in obj.items():
196 if key == "$ref" and value.find(".") == -1:
197 obj[key] = "%s.%s" % (domain_name, value)
198 elif key == "id":
199 obj[key] = "%s.%s" % (domain_name, value)
200 types[obj[key]] = obj
201 else:
202 normalize_types(value, domain_name, types)
203
204 def load_json(filename):
205 input_file = open(filename, "r")
206 json_string = input_file.read()
207 json_string = re.sub(":\s*true", ": True", json_string)
208 json_string = re.sub(":\s*false", ": False", json_string)
209 return eval(json_string)
210
211 def self_test():
212 def create_test_schema_1():
213 return [
214 {
215 "domain": "Network",
216 "types": [
217 {
218 "id": "LoaderId",
219 "type": "string"
220 },
221 {
222 "id": "Headers",
223 "type": "object"
224 },
225 {
226 "id": "Request",
227 "type": "object",
228 "properties": [
229 { "name": "url", "type": "string" },
230 { "name": "method", "type": "string" },
231 { "name": "headers", "$ref": "Headers" },
232 { "name": "becameOptionalField", "type": "string" },
233 { "name": "removedField", "type": "string" },
234 ]
235 }
236 ],
237 "commands": [
238 {
239 "name": "removedCommand",
240 },
241 {
242 "name": "setExtraHTTPHeaders",
243 "parameters": [
244 { "name": "headers", "$ref": "Headers" },
245 { "name": "mismatched", "type": "string" },
246 { "name": "becameOptional", "$ref": "Headers" },
247 { "name": "removedRequired", "$ref": "Headers" },
248 { "name": "becameRequired", "$ref": "Headers", "optional ": True },
249 { "name": "removedOptional", "$ref": "Headers", "optiona l": True },
250 ],
251 "returns": [
252 { "name": "mimeType", "type": "string" },
253 { "name": "becameOptional", "type": "string" },
254 { "name": "removedRequired", "type": "string" },
255 { "name": "becameRequired", "type": "string", "optional" : True },
256 { "name": "removedOptional", "type": "string", "optional ": True },
257 ]
258 }
259 ],
260 "events": [
261 {
262 "name": "requestWillBeSent",
263 "parameters": [
264 { "name": "frameId", "type": "string", "hidden": True },
265 { "name": "request", "$ref": "Request" },
266 { "name": "becameOptional", "type": "string" },
267 { "name": "removedRequired", "type": "string" },
268 { "name": "becameRequired", "type": "string", "optional" : True },
269 { "name": "removedOptional", "type": "string", "optional ": True },
270 ]
271 },
272 {
273 "name": "removedEvent",
274 "parameters": [
275 { "name": "errorText", "type": "string" },
276 { "name": "canceled", "type": "boolean", "optional": Tru e }
277 ]
278 }
279 ]
280 },
281 {
282 "domain": "removedDomain"
283 }
284 ]
285
286 def create_test_schema_2():
287 return [
288 {
289 "domain": "Network",
290 "types": [
291 {
292 "id": "LoaderId",
293 "type": "string"
294 },
295 {
296 "id": "Request",
297 "type": "object",
298 "properties": [
299 { "name": "url", "type": "string" },
300 { "name": "method", "type": "string" },
301 { "name": "headers", "type": "object" },
302 { "name": "becameOptionalField", "type": "string", "opti onal": True },
303 ]
304 }
305 ],
306 "commands": [
307 {
308 "name": "addedCommand",
309 },
310 {
311 "name": "setExtraHTTPHeaders",
312 "parameters": [
313 { "name": "headers", "type": "object" },
314 { "name": "mismatched", "type": "object" },
315 { "name": "becameOptional", "type": "object" , "optional ": True },
316 { "name": "addedRequired", "type": "object" },
317 { "name": "becameRequired", "type": "object" },
318 { "name": "addedOptional", "type": "object", "optional": True },
319 ],
320 "returns": [
321 { "name": "mimeType", "type": "string" },
322 { "name": "becameOptional", "type": "string", "optional" : True },
323 { "name": "addedRequired", "type": "string"},
324 { "name": "becameRequired", "type": "string" },
325 { "name": "addedOptional", "type": "string", "optional": True },
326 ]
327 }
328 ],
329 "events": [
330 {
331 "name": "requestWillBeSent",
332 "parameters": [
333 { "name": "request", "$ref": "Request" },
334 { "name": "becameOptional", "type": "string", "optional" : True },
335 { "name": "addedRequired", "type": "string"},
336 { "name": "becameRequired", "type": "string" },
337 { "name": "addedOptional", "type": "string", "optional": True },
338 ]
339 },
340 {
341 "name": "addedEvent"
342 }
343 ]
344 },
345 {
346 "domain": "addedDomain"
347 }
348 ]
349
350 expected_errors = [
351 "removedDomain: domain has been removed",
352 "Network.removedCommand: command has been removed",
353 "Network.removedEvent: event has been removed",
354 "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, ' object' vs 'string'",
355 "Network.setExtraHTTPHeaders.addedRequired: required parameter has been added",
356 "Network.setExtraHTTPHeaders.becameRequired: optional parameter is now r equired",
357 "Network.setExtraHTTPHeaders.removedRequired: required response paramete r has been removed",
358 "Network.setExtraHTTPHeaders.becameOptional: required response parameter is now optional",
359 "Network.requestWillBeSent.removedRequired: required parameter has been removed",
360 "Network.requestWillBeSent.becameOptional: required parameter is now opt ional",
361 "Network.requestWillBeSent.request parameter->Network.Request.removedFie ld: required property has been removed",
362 "Network.requestWillBeSent.request parameter->Network.Request.becameOpti onalField: required property is now optional",
363 ]
364
365 expected_errors_reverse = [
366 "addedDomain: domain has been added",
367 "Network.addedEvent: event has been added",
368 "Network.addedCommand: command has been added",
369 "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, 's tring' vs 'object'",
370 "Network.setExtraHTTPHeaders.removedRequired: required parameter has been removed",
371 "Network.setExtraHTTPHeaders.becameOptional: required parameter is now op tional",
372 "Network.setExtraHTTPHeaders.addedRequired: required response parameter h as been added",
373 "Network.setExtraHTTPHeaders.becameRequired: optional response parameter is now required",
374 "Network.requestWillBeSent.becameRequired: optional parameter is now requ ired",
375 "Network.requestWillBeSent.addedRequired: required parameter has been add ed",
376 ]
377
378 def is_subset(subset, superset, message):
379 for i in range(len(subset)):
380 if subset[i] not in superset:
381 sys.stderr.write("%s error: %s\n" % (message, subset[i]))
382 return False
383 return True
384
385 def errors_match(expected, actual):
386 return (is_subset(actual, expected, "Unexpected") and
387 is_subset(expected, actual, "Missing"))
388
389 return (errors_match(expected_errors,
390 compare_schemas(create_test_schema_1(), create_test_sch ema_2(), False)) and
391 errors_match(expected_errors_reverse,
392 compare_schemas(create_test_schema_2(), create_test_sch ema_1(), True)))
393
394
395 def main():
396 if not self_test():
397 sys.stderr.write("Self-test failed")
398 return 1
399
400 if len(sys.argv) < 4 or sys.argv[1] != "-o":
401 sys.stderr.write("Usage: %s -o OUTPUT_FILE INPUT_FILE [--show-changes]\n " % sys.argv[0])
402 return 1
403
404 output_path = sys.argv[2]
405 output_file = open(output_path, "w")
406
407 input_path = sys.argv[3]
408 dir_name = os.path.dirname(input_path)
409 schema = load_json(input_path)
410
411 major = schema["version"]["major"]
412 minor = schema["version"]["minor"]
413 version = "%s.%s" % (major, minor)
414 if len(dir_name) == 0:
415 dir_name = "."
416 baseline_path = os.path.normpath(dir_name + "/Inspector-" + version + ".json ")
417 baseline_schema = load_json(baseline_path)
418
419 errors = compare_schemas(baseline_schema["domains"], schema["domains"], Fals e)
420 if len(errors) > 0:
421 sys.stderr.write(" Compatibility with %s: FAILED\n" % version)
422 for error in errors:
423 sys.stderr.write( " %s\n" % error)
424 return 1
425
426 if len(sys.argv) > 4 and sys.argv[4] == "--show-changes":
427 changes = compare_schemas(
428 load_json(input_path)["domains"], load_json(baseline_path)["domains" ], True)
429 if len(changes) > 0:
430 print " Public changes since %s:" % version
431 for change in changes:
432 print " %s" % change
433
434 output_file.write("""
435 #ifndef InspectorProtocolVersion_h
436 #define InspectorProtocolVersion_h
437
438 #include "wtf/Vector.h"
439 #include "wtf/text/WTFString.h"
440
441 namespace blink {
442
443 String inspectorProtocolVersion() { return "%s"; }
444
445 int inspectorProtocolVersionMajor() { return %s; }
446
447 int inspectorProtocolVersionMinor() { return %s; }
448
449 bool supportsInspectorProtocolVersion(const String& version)
450 {
451 Vector<String> tokens;
452 version.split(".", tokens);
453 if (tokens.size() != 2)
454 return false;
455
456 bool ok = true;
457 int major = tokens[0].toInt(&ok);
458 if (!ok || major != %s)
459 return false;
460
461 int minor = tokens[1].toInt(&ok);
462 if (!ok || minor > %s)
463 return false;
464
465 return true;
466 }
467
468 }
469
470 #endif // !defined(InspectorProtocolVersion_h)
471 """ % (version, major, minor, major, minor))
472
473 output_file.close()
474
475 if __name__ == '__main__':
476 sys.exit(main())
OLDNEW
« no previous file with comments | « sky/engine/core/inspector/ScriptDebugListener.h ('k') | sky/engine/core/inspector/protocol.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698