Merge remote-tracking branch 'remotes/dgilbert-gitlab/tags/pull-migration-20210726a...
[qemu/armbru.git] / tests / qapi-schema / test-qapi.py
blobf1c4deb9a51baeefb89f4195f9a1314cdc76a1fd
1 #!/usr/bin/env python3
3 # QAPI parser test harness
5 # Copyright (c) 2013 Red Hat Inc.
7 # Authors:
8 # Markus Armbruster <armbru@redhat.com>
10 # This work is licensed under the terms of the GNU GPL, version 2 or later.
11 # See the COPYING file in the top-level directory.
15 import argparse
16 import difflib
17 import os
18 import sys
19 from io import StringIO
21 from qapi.error import QAPIError
22 from qapi.schema import QAPISchema, QAPISchemaVisitor
25 class QAPISchemaTestVisitor(QAPISchemaVisitor):
27 def visit_module(self, name):
28 print('module %s' % name)
30 def visit_include(self, name, info):
31 print('include %s' % name)
33 def visit_enum_type(self, name, info, ifcond, features, members, prefix):
34 print('enum %s' % name)
35 if prefix:
36 print(' prefix %s' % prefix)
37 for m in members:
38 print(' member %s' % m.name)
39 self._print_if(m.ifcond, indent=8)
40 self._print_if(ifcond)
41 self._print_features(features)
43 def visit_array_type(self, name, info, ifcond, element_type):
44 if not info:
45 return # suppress built-in arrays
46 print('array %s %s' % (name, element_type.name))
47 self._print_if(ifcond)
49 def visit_object_type(self, name, info, ifcond, features,
50 base, members, variants):
51 print('object %s' % name)
52 if base:
53 print(' base %s' % base.name)
54 for m in members:
55 print(' member %s: %s optional=%s'
56 % (m.name, m.type.name, m.optional))
57 self._print_if(m.ifcond, 8)
58 self._print_features(m.features, indent=8)
59 self._print_variants(variants)
60 self._print_if(ifcond)
61 self._print_features(features)
63 def visit_alternate_type(self, name, info, ifcond, features, variants):
64 print('alternate %s' % name)
65 self._print_variants(variants)
66 self._print_if(ifcond)
67 self._print_features(features)
69 def visit_command(self, name, info, ifcond, features,
70 arg_type, ret_type, gen, success_response, boxed,
71 allow_oob, allow_preconfig, coroutine):
72 print('command %s %s -> %s'
73 % (name, arg_type and arg_type.name,
74 ret_type and ret_type.name))
75 print(' gen=%s success_response=%s boxed=%s oob=%s preconfig=%s%s'
76 % (gen, success_response, boxed, allow_oob, allow_preconfig,
77 " coroutine=True" if coroutine else ""))
78 self._print_if(ifcond)
79 self._print_features(features)
81 def visit_event(self, name, info, ifcond, features, arg_type, boxed):
82 print('event %s %s' % (name, arg_type and arg_type.name))
83 print(' boxed=%s' % boxed)
84 self._print_if(ifcond)
85 self._print_features(features)
87 @staticmethod
88 def _print_variants(variants):
89 if variants:
90 print(' tag %s' % variants.tag_member.name)
91 for v in variants.variants:
92 print(' case %s: %s' % (v.name, v.type.name))
93 QAPISchemaTestVisitor._print_if(v.ifcond, indent=8)
95 @staticmethod
96 def _print_if(ifcond, indent=4):
97 if ifcond:
98 print('%sif %s' % (' ' * indent, ifcond))
100 @classmethod
101 def _print_features(cls, features, indent=4):
102 if features:
103 for f in features:
104 print('%sfeature %s' % (' ' * indent, f.name))
105 cls._print_if(f.ifcond, indent + 4)
108 def test_frontend(fname):
109 schema = QAPISchema(fname)
110 schema.visit(QAPISchemaTestVisitor())
112 for doc in schema.docs:
113 if doc.symbol:
114 print('doc symbol=%s' % doc.symbol)
115 else:
116 print('doc freeform')
117 print(' body=\n%s' % doc.body.text)
118 for arg, section in doc.args.items():
119 print(' arg=%s\n%s' % (arg, section.text))
120 for feat, section in doc.features.items():
121 print(' feature=%s\n%s' % (feat, section.text))
122 for section in doc.sections:
123 print(' section=%s\n%s' % (section.name, section.text))
126 def test_and_diff(test_name, dir_name, update):
127 sys.stdout = StringIO()
128 try:
129 test_frontend(os.path.join(dir_name, test_name + '.json'))
130 except QAPIError as err:
131 errstr = str(err) + '\n'
132 if dir_name:
133 errstr = errstr.replace(dir_name + '/', '')
134 actual_err = errstr.splitlines(True)
135 else:
136 actual_err = []
137 finally:
138 actual_out = sys.stdout.getvalue().splitlines(True)
139 sys.stdout.close()
140 sys.stdout = sys.__stdout__
142 mode = 'r+' if update else 'r'
143 try:
144 outfp = open(os.path.join(dir_name, test_name + '.out'), mode)
145 errfp = open(os.path.join(dir_name, test_name + '.err'), mode)
146 expected_out = outfp.readlines()
147 expected_err = errfp.readlines()
148 except IOError as err:
149 print("%s: can't open '%s': %s"
150 % (sys.argv[0], err.filename, err.strerror),
151 file=sys.stderr)
152 return 2
154 if actual_out == expected_out and actual_err == expected_err:
155 return 0
157 print("%s %s" % (test_name, 'UPDATE' if update else 'FAIL'),
158 file=sys.stderr)
159 out_diff = difflib.unified_diff(expected_out, actual_out, outfp.name)
160 err_diff = difflib.unified_diff(expected_err, actual_err, errfp.name)
161 sys.stdout.writelines(out_diff)
162 sys.stdout.writelines(err_diff)
164 if not update:
165 return 1
167 try:
168 outfp.truncate(0)
169 outfp.seek(0)
170 outfp.writelines(actual_out)
171 errfp.truncate(0)
172 errfp.seek(0)
173 errfp.writelines(actual_err)
174 except IOError as err:
175 print("%s: can't write '%s': %s"
176 % (sys.argv[0], err.filename, err.strerror),
177 file=sys.stderr)
178 return 2
180 return 0
183 def main(argv):
184 parser = argparse.ArgumentParser(
185 description='QAPI schema tester')
186 parser.add_argument('-d', '--dir', action='store', default='',
187 help="directory containing tests")
188 parser.add_argument('-u', '--update', action='store_true',
189 help="update expected test results")
190 parser.add_argument('tests', nargs='*', metavar='TEST', action='store')
191 args = parser.parse_args()
193 status = 0
194 for t in args.tests:
195 (dir_name, base_name) = os.path.split(t)
196 dir_name = dir_name or args.dir
197 test_name = os.path.splitext(base_name)[0]
198 status |= test_and_diff(test_name, dir_name, args.update)
200 exit(status)
203 if __name__ == '__main__':
204 main(sys.argv)
205 exit(0)