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

Side by Side Diff: third_party/WebKit/Source/platform/inspector_protocol/generate-inspector-protocol-version

Issue 2282283002: [DevTools] Prepare inspector_protocol build to move. (Closed)
Patch Set: NOTREACHED Created 4 years, 3 months 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 collections
49 import copy
50 import os.path
51 import optparse
52 import re
53 import sys
54
55 try:
56 import json
57 except ImportError:
58 import simplejson as json
59
60 def list_to_map(items, key):
61 result = {}
62 for item in items:
63 if not "experimental" in item and not "hidden" in item:
64 result[item[key]] = item
65 return result
66
67
68 def named_list_to_map(container, name, key):
69 if name in container:
70 return list_to_map(container[name], key)
71 return {}
72
73
74 def removed(reverse):
75 if reverse:
76 return "added"
77 return "removed"
78
79
80 def required(reverse):
81 if reverse:
82 return "optional"
83 return "required"
84
85
86 def compare_schemas(d_1, d_2, reverse):
87 errors = []
88 domains_1 = copy.deepcopy(d_1)
89 domains_2 = copy.deepcopy(d_2)
90 types_1 = normalize_types_in_schema(domains_1)
91 types_2 = normalize_types_in_schema(domains_2)
92
93 domains_by_name_1 = list_to_map(domains_1, "domain")
94 domains_by_name_2 = list_to_map(domains_2, "domain")
95
96 for name in domains_by_name_1:
97 domain_1 = domains_by_name_1[name]
98 if not name in domains_by_name_2:
99 errors.append("%s: domain has been %s" % (name, removed(reverse)))
100 continue
101 compare_domains(domain_1, domains_by_name_2[name], types_1, types_2, err ors, reverse)
102 return errors
103
104
105 def compare_domains(domain_1, domain_2, types_map_1, types_map_2, errors, revers e):
106 domain_name = domain_1["domain"]
107 commands_1 = named_list_to_map(domain_1, "commands", "name")
108 commands_2 = named_list_to_map(domain_2, "commands", "name")
109 for name in commands_1:
110 command_1 = commands_1[name]
111 if not name in commands_2:
112 errors.append("%s.%s: command has been %s" % (domain_1["domain"], na me, removed(reverse)))
113 continue
114 compare_commands(domain_name, command_1, commands_2[name], types_map_1, types_map_2, errors, reverse)
115
116 events_1 = named_list_to_map(domain_1, "events", "name")
117 events_2 = named_list_to_map(domain_2, "events", "name")
118 for name in events_1:
119 event_1 = events_1[name]
120 if not name in events_2:
121 errors.append("%s.%s: event has been %s" % (domain_1["domain"], name , removed(reverse)))
122 continue
123 compare_events(domain_name, event_1, events_2[name], types_map_1, types_ map_2, errors, reverse)
124
125
126 def compare_commands(domain_name, command_1, command_2, types_map_1, types_map_2 , errors, reverse):
127 context = domain_name + "." + command_1["name"]
128
129 params_1 = named_list_to_map(command_1, "parameters", "name")
130 params_2 = named_list_to_map(command_2, "parameters", "name")
131 # Note the reversed order: we allow removing but forbid adding parameters.
132 compare_params_list(context, "parameter", params_2, params_1, types_map_2, t ypes_map_1, 0, errors, not reverse)
133
134 returns_1 = named_list_to_map(command_1, "returns", "name")
135 returns_2 = named_list_to_map(command_2, "returns", "name")
136 compare_params_list(context, "response parameter", returns_1, returns_2, typ es_map_1, types_map_2, 0, errors, reverse)
137
138
139 def compare_events(domain_name, event_1, event_2, types_map_1, types_map_2, erro rs, reverse):
140 context = domain_name + "." + event_1["name"]
141 params_1 = named_list_to_map(event_1, "parameters", "name")
142 params_2 = named_list_to_map(event_2, "parameters", "name")
143 compare_params_list(context, "parameter", params_1, params_2, types_map_1, t ypes_map_2, 0, errors, reverse)
144
145
146 def compare_params_list(context, kind, params_1, params_2, types_map_1, types_ma p_2, depth, errors, reverse):
147 for name in params_1:
148 param_1 = params_1[name]
149 if not name in params_2:
150 if not "optional" in param_1:
151 errors.append("%s.%s: required %s has been %s" % (context, name, kind, removed(reverse)))
152 continue
153
154 param_2 = params_2[name]
155 if param_2 and "optional" in param_2 and not "optional" in param_1:
156 errors.append("%s.%s: %s %s is now %s" % (context, name, required(re verse), kind, required(not reverse)))
157 continue
158 type_1 = extract_type(param_1, types_map_1, errors)
159 type_2 = extract_type(param_2, types_map_2, errors)
160 compare_types(context + "." + name, kind, type_1, type_2, types_map_1, t ypes_map_2, depth, errors, reverse)
161
162
163 def compare_types(context, kind, type_1, type_2, types_map_1, types_map_2, depth , errors, reverse):
164 if depth > 10:
165 return
166
167 base_type_1 = type_1["type"]
168 base_type_2 = type_2["type"]
169
170 if base_type_1 != base_type_2:
171 errors.append("%s: %s base type mismatch, '%s' vs '%s'" % (context, kind , base_type_1, base_type_2))
172 elif base_type_1 == "object":
173 params_1 = named_list_to_map(type_1, "properties", "name")
174 params_2 = named_list_to_map(type_2, "properties", "name")
175 # If both parameters have the same named type use it in the context.
176 if "id" in type_1 and "id" in type_2 and type_1["id"] == type_2["id"]:
177 type_name = type_1["id"]
178 else:
179 type_name = "<object>"
180 context += " %s->%s" % (kind, type_name)
181 compare_params_list(context, "property", params_1, params_2, types_map_1 , types_map_2, depth + 1, errors, reverse)
182 elif base_type_1 == "array":
183 item_type_1 = extract_type(type_1["items"], types_map_1, errors)
184 item_type_2 = extract_type(type_2["items"], types_map_2, errors)
185 compare_types(context, kind, item_type_1, item_type_2, types_map_1, type s_map_2, depth + 1, errors, reverse)
186
187
188 def extract_type(typed_object, types_map, errors):
189 if "type" in typed_object:
190 result = { "id": "<transient>", "type": typed_object["type"] }
191 if typed_object["type"] == "object":
192 result["properties"] = []
193 elif typed_object["type"] == "array":
194 result["items"] = typed_object["items"]
195 return result
196 elif "$ref" in typed_object:
197 ref = typed_object["$ref"]
198 if not ref in types_map:
199 errors.append("Can not resolve type: %s" % ref)
200 types_map[ref] = { "id": "<transient>", "type": "object" }
201 return types_map[ref]
202
203
204 def normalize_types_in_schema(domains):
205 types = {}
206 for domain in domains:
207 domain_name = domain["domain"]
208 normalize_types(domain, domain_name, types)
209 return types
210
211
212 def normalize_types(obj, domain_name, types):
213 if isinstance(obj, list):
214 for item in obj:
215 normalize_types(item, domain_name, types)
216 elif isinstance(obj, dict):
217 for key, value in obj.items():
218 if key == "$ref" and value.find(".") == -1:
219 obj[key] = "%s.%s" % (domain_name, value)
220 elif key == "id":
221 obj[key] = "%s.%s" % (domain_name, value)
222 types[obj[key]] = obj
223 else:
224 normalize_types(value, domain_name, types)
225
226
227 def load_schema(file, domains):
228 if not os.path.isfile(file):
229 return
230 input_file = open(file, "r")
231 json_string = input_file.read()
232 parsed_json = json.loads(json_string)
233 domains += parsed_json["domains"]
234 return parsed_json["version"]
235
236
237 def self_test():
238 def create_test_schema_1():
239 return [
240 {
241 "domain": "Network",
242 "types": [
243 {
244 "id": "LoaderId",
245 "type": "string"
246 },
247 {
248 "id": "Headers",
249 "type": "object"
250 },
251 {
252 "id": "Request",
253 "type": "object",
254 "properties": [
255 { "name": "url", "type": "string" },
256 { "name": "method", "type": "string" },
257 { "name": "headers", "$ref": "Headers" },
258 { "name": "becameOptionalField", "type": "string" },
259 { "name": "removedField", "type": "string" },
260 ]
261 }
262 ],
263 "commands": [
264 {
265 "name": "removedCommand",
266 },
267 {
268 "name": "setExtraHTTPHeaders",
269 "parameters": [
270 { "name": "headers", "$ref": "Headers" },
271 { "name": "mismatched", "type": "string" },
272 { "name": "becameOptional", "$ref": "Headers" },
273 { "name": "removedRequired", "$ref": "Headers" },
274 { "name": "becameRequired", "$ref": "Headers", "optional ": True },
275 { "name": "removedOptional", "$ref": "Headers", "optiona l": True },
276 ],
277 "returns": [
278 { "name": "mimeType", "type": "string" },
279 { "name": "becameOptional", "type": "string" },
280 { "name": "removedRequired", "type": "string" },
281 { "name": "becameRequired", "type": "string", "optional" : True },
282 { "name": "removedOptional", "type": "string", "optional ": True },
283 ]
284 }
285 ],
286 "events": [
287 {
288 "name": "requestWillBeSent",
289 "parameters": [
290 { "name": "frameId", "type": "string", "experimental": T rue },
291 { "name": "request", "$ref": "Request" },
292 { "name": "becameOptional", "type": "string" },
293 { "name": "removedRequired", "type": "string" },
294 { "name": "becameRequired", "type": "string", "optional" : True },
295 { "name": "removedOptional", "type": "string", "optional ": True },
296 ]
297 },
298 {
299 "name": "removedEvent",
300 "parameters": [
301 { "name": "errorText", "type": "string" },
302 { "name": "canceled", "type": "boolean", "optional": Tru e }
303 ]
304 }
305 ]
306 },
307 {
308 "domain": "removedDomain"
309 }
310 ]
311
312 def create_test_schema_2():
313 return [
314 {
315 "domain": "Network",
316 "types": [
317 {
318 "id": "LoaderId",
319 "type": "string"
320 },
321 {
322 "id": "Request",
323 "type": "object",
324 "properties": [
325 { "name": "url", "type": "string" },
326 { "name": "method", "type": "string" },
327 { "name": "headers", "type": "object" },
328 { "name": "becameOptionalField", "type": "string", "opti onal": True },
329 ]
330 }
331 ],
332 "commands": [
333 {
334 "name": "addedCommand",
335 },
336 {
337 "name": "setExtraHTTPHeaders",
338 "parameters": [
339 { "name": "headers", "type": "object" },
340 { "name": "mismatched", "type": "object" },
341 { "name": "becameOptional", "type": "object" , "optional ": True },
342 { "name": "addedRequired", "type": "object" },
343 { "name": "becameRequired", "type": "object" },
344 { "name": "addedOptional", "type": "object", "optional": True },
345 ],
346 "returns": [
347 { "name": "mimeType", "type": "string" },
348 { "name": "becameOptional", "type": "string", "optional" : True },
349 { "name": "addedRequired", "type": "string"},
350 { "name": "becameRequired", "type": "string" },
351 { "name": "addedOptional", "type": "string", "optional": True },
352 ]
353 }
354 ],
355 "events": [
356 {
357 "name": "requestWillBeSent",
358 "parameters": [
359 { "name": "request", "$ref": "Request" },
360 { "name": "becameOptional", "type": "string", "optional" : True },
361 { "name": "addedRequired", "type": "string"},
362 { "name": "becameRequired", "type": "string" },
363 { "name": "addedOptional", "type": "string", "optional": True },
364 ]
365 },
366 {
367 "name": "addedEvent"
368 }
369 ]
370 },
371 {
372 "domain": "addedDomain"
373 }
374 ]
375
376 expected_errors = [
377 "removedDomain: domain has been removed",
378 "Network.removedCommand: command has been removed",
379 "Network.removedEvent: event has been removed",
380 "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, ' object' vs 'string'",
381 "Network.setExtraHTTPHeaders.addedRequired: required parameter has been added",
382 "Network.setExtraHTTPHeaders.becameRequired: optional parameter is now r equired",
383 "Network.setExtraHTTPHeaders.removedRequired: required response paramete r has been removed",
384 "Network.setExtraHTTPHeaders.becameOptional: required response parameter is now optional",
385 "Network.requestWillBeSent.removedRequired: required parameter has been removed",
386 "Network.requestWillBeSent.becameOptional: required parameter is now opt ional",
387 "Network.requestWillBeSent.request parameter->Network.Request.removedFie ld: required property has been removed",
388 "Network.requestWillBeSent.request parameter->Network.Request.becameOpti onalField: required property is now optional",
389 ]
390
391 expected_errors_reverse = [
392 "addedDomain: domain has been added",
393 "Network.addedEvent: event has been added",
394 "Network.addedCommand: command has been added",
395 "Network.setExtraHTTPHeaders.mismatched: parameter base type mismatch, 's tring' vs 'object'",
396 "Network.setExtraHTTPHeaders.removedRequired: required parameter has been removed",
397 "Network.setExtraHTTPHeaders.becameOptional: required parameter is now op tional",
398 "Network.setExtraHTTPHeaders.addedRequired: required response parameter h as been added",
399 "Network.setExtraHTTPHeaders.becameRequired: optional response parameter is now required",
400 "Network.requestWillBeSent.becameRequired: optional parameter is now requ ired",
401 "Network.requestWillBeSent.addedRequired: required parameter has been add ed",
402 ]
403
404 def is_subset(subset, superset, message):
405 for i in range(len(subset)):
406 if subset[i] not in superset:
407 sys.stderr.write("%s error: %s\n" % (message, subset[i]))
408 return False
409 return True
410
411 def errors_match(expected, actual):
412 return (is_subset(actual, expected, "Unexpected") and
413 is_subset(expected, actual, "Missing"))
414
415 return (errors_match(expected_errors,
416 compare_schemas(create_test_schema_1(), create_test_sch ema_2(), False)) and
417 errors_match(expected_errors_reverse,
418 compare_schemas(create_test_schema_2(), create_test_sch ema_1(), True)))
419
420
421
422 def load_domains_and_baselines(file, domains, baseline_domains):
423 version = load_schema(os.path.normpath(file), domains)
424 suffix = "-%s.%s.json" % (version["major"], version["minor"])
425 baseline_file = file.replace(".json", suffix)
426 load_schema(os.path.normpath(baseline_file), baseline_domains)
427 return version
428
429
430 def main():
431 if not self_test():
432 sys.stderr.write("Self-test failed")
433 return 1
434
435 cmdline_parser = optparse.OptionParser()
436 cmdline_parser.add_option("--show_changes")
437 cmdline_parser.add_option("--o")
438 arg_options, arg_values = cmdline_parser.parse_args()
439
440 if len(arg_values) < 1 or not arg_options.o:
441 sys.stderr.write("Usage: %s --o OUTPUT_FILE [--show_changes] PROTOCOL_FO LDER1 ?PROTOCOL_FOLDER2 \n" % sys.argv[0])
442 return 1
443
444 output_path = arg_options.o
445 output_file = open(output_path, "w")
446
447 domains = []
448 baseline_domains = []
449 version = load_domains_and_baselines(arg_values[0], domains, baseline_domain s)
450 if len(arg_values) > 1:
451 load_domains_and_baselines(arg_values[1], domains, baseline_domains)
452
453 expected_errors = [
454 "Debugger.globalObjectCleared: event has been removed",
455 "Runtime.executionContextCreated.context parameter->Runtime.ExecutionCon textDescription.frameId: required property has been removed",
456 "Debugger.canSetScriptSource: command has been removed",
457 "Console.messageRepeatCountUpdated: event has been removed",
458 "Console.messagesCleared: event has been removed"
459 ]
460
461 errors = compare_schemas(baseline_domains, domains, False)
462 unexpected_errors = []
463 for i in range(len(errors)):
464 if errors[i] not in expected_errors:
465 unexpected_errors.append(errors[i])
466 if len(unexpected_errors) > 0:
467 sys.stderr.write(" Compatibility checks FAILED\n")
468 for error in unexpected_errors:
469 sys.stderr.write( " %s\n" % error)
470 return 1
471
472 if arg_options.show_changes:
473 changes = compare_schemas(domains, baseline_domains, True)
474 if len(changes) > 0:
475 print " Public changes since %s:" % version
476 for change in changes:
477 print " %s" % change
478
479 json.dump({"version": version, "domains": domains}, output_file, indent=4, s ort_keys=False, separators=(',', ': '))
480 output_file.close()
481
482 if __name__ == '__main__':
483 sys.exit(main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698