blob: 7e3f9f4aa1feee3a2b438f94a82ed4271be921b1 [file] [log] [blame]
Philippe Mathieu-Daudéc88ee462020-01-30 17:32:24 +01001#!/usr/bin/env python3
Markus Armbruster98626572013-07-27 17:41:53 +02002#
3# QAPI parser test harness
4#
5# Copyright (c) 2013 Red Hat Inc.
6#
7# Authors:
8# Markus Armbruster <armbru@redhat.com>
9#
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.
12#
13
Markus Armbrustere6c42b92019-10-18 09:43:44 +020014
Markus Armbrusterf01338c2019-10-18 09:43:42 +020015import argparse
16import difflib
17import os
Markus Armbruster98626572013-07-27 17:41:53 +020018import sys
Markus Armbrustered39c032020-03-04 16:59:30 +010019from io import StringIO
Markus Armbrustere6c42b92019-10-18 09:43:44 +020020
21from qapi.error import QAPIError
22from qapi.schema import QAPISchema, QAPISchemaVisitor
23
Markus Armbruster98626572013-07-27 17:41:53 +020024
Markus Armbruster156402e2015-09-16 13:06:08 +020025class QAPISchemaTestVisitor(QAPISchemaVisitor):
Markus Armbrustercf40a0a2018-02-11 10:35:55 +010026
27 def visit_module(self, name):
28 print('module %s' % name)
29
30 def visit_include(self, name, info):
31 print('include %s' % name)
32
Markus Armbruster013b4ef2020-03-17 12:54:37 +010033 def visit_enum_type(self, name, info, ifcond, features, members, prefix):
Marc-André Lureau1e381b62018-12-13 16:37:05 +040034 print('enum %s' % name)
Markus Armbruster156402e2015-09-16 13:06:08 +020035 if prefix:
Daniel P. Berrangeef9d9102018-01-16 13:42:04 +000036 print(' prefix %s' % prefix)
Marc-André Lureau1e381b62018-12-13 16:37:05 +040037 for m in members:
38 print(' member %s' % m.name)
Marc-André Lureau6cc32b02018-12-13 16:37:11 +040039 self._print_if(m.ifcond, indent=8)
Markus Armbrusterb6c18752021-10-25 06:24:02 +020040 self._print_features(m.features, indent=8)
Marc-André Lureaufbf09a22018-07-03 17:56:38 +020041 self._print_if(ifcond)
Markus Armbruster013b4ef2020-03-17 12:54:37 +010042 self._print_features(features)
Markus Armbruster156402e2015-09-16 13:06:08 +020043
Markus Armbrusterca0ac752019-03-01 16:40:45 +010044 def visit_array_type(self, name, info, ifcond, element_type):
45 if not info:
46 return # suppress built-in arrays
47 print('array %s %s' % (name, element_type.name))
48 self._print_if(ifcond)
49
Markus Armbruster7b3bc9e2020-03-17 12:54:38 +010050 def visit_object_type(self, name, info, ifcond, features,
Markus Armbrusterd1da8af2024-03-15 16:28:22 +010051 base, members, branches):
Daniel P. Berrangeef9d9102018-01-16 13:42:04 +000052 print('object %s' % name)
Markus Armbruster156402e2015-09-16 13:06:08 +020053 if base:
Daniel P. Berrangeef9d9102018-01-16 13:42:04 +000054 print(' base %s' % base.name)
Markus Armbruster156402e2015-09-16 13:06:08 +020055 for m in members:
Markus Armbrusterb736e252018-06-21 10:35:51 +020056 print(' member %s: %s optional=%s'
57 % (m.name, m.type.name, m.optional))
Marc-André Lureauccadd6b2018-12-13 16:37:15 +040058 self._print_if(m.ifcond, 8)
Markus Armbruster84ab0082020-03-17 12:54:45 +010059 self._print_features(m.features, indent=8)
Markus Armbrusterd1da8af2024-03-15 16:28:22 +010060 self._print_variants(branches)
Marc-André Lureaufbf09a22018-07-03 17:56:38 +020061 self._print_if(ifcond)
Peter Krempa2e2e0df22019-10-18 10:14:52 +020062 self._print_features(features)
Markus Armbruster156402e2015-09-16 13:06:08 +020063
Markus Armbruster41d0ad12024-03-16 07:43:36 +010064 def visit_alternate_type(self, name, info, ifcond, features,
65 alternatives):
Daniel P. Berrangeef9d9102018-01-16 13:42:04 +000066 print('alternate %s' % name)
Markus Armbruster41d0ad12024-03-16 07:43:36 +010067 self._print_variants(alternatives)
Marc-André Lureaufbf09a22018-07-03 17:56:38 +020068 self._print_if(ifcond)
Markus Armbruster013b4ef2020-03-17 12:54:37 +010069 self._print_features(features)
Markus Armbruster156402e2015-09-16 13:06:08 +020070
Markus Armbruster7b3bc9e2020-03-17 12:54:38 +010071 def visit_command(self, name, info, ifcond, features,
72 arg_type, ret_type, gen, success_response, boxed,
Kevin Wolf04f22362020-10-05 17:58:49 +020073 allow_oob, allow_preconfig, coroutine):
Markus Armbrusterb736e252018-06-21 10:35:51 +020074 print('command %s %s -> %s'
75 % (name, arg_type and arg_type.name,
76 ret_type and ret_type.name))
Kevin Wolf04f22362020-10-05 17:58:49 +020077 print(' gen=%s success_response=%s boxed=%s oob=%s preconfig=%s%s'
78 % (gen, success_response, boxed, allow_oob, allow_preconfig,
79 " coroutine=True" if coroutine else ""))
Marc-André Lureaufbf09a22018-07-03 17:56:38 +020080 self._print_if(ifcond)
Peter Krempa2e2e0df22019-10-18 10:14:52 +020081 self._print_features(features)
Markus Armbruster156402e2015-09-16 13:06:08 +020082
Markus Armbruster013b4ef2020-03-17 12:54:37 +010083 def visit_event(self, name, info, ifcond, features, arg_type, boxed):
Daniel P. Berrangeef9d9102018-01-16 13:42:04 +000084 print('event %s %s' % (name, arg_type and arg_type.name))
Markus Armbruster758f2722019-10-18 10:14:50 +020085 print(' boxed=%s' % boxed)
Marc-André Lureaufbf09a22018-07-03 17:56:38 +020086 self._print_if(ifcond)
Markus Armbruster013b4ef2020-03-17 12:54:37 +010087 self._print_features(features)
Markus Armbruster156402e2015-09-16 13:06:08 +020088
89 @staticmethod
90 def _print_variants(variants):
91 if variants:
Daniel P. Berrangeef9d9102018-01-16 13:42:04 +000092 print(' tag %s' % variants.tag_member.name)
Markus Armbruster156402e2015-09-16 13:06:08 +020093 for v in variants.variants:
Daniel P. Berrangeef9d9102018-01-16 13:42:04 +000094 print(' case %s: %s' % (v.name, v.type.name))
Marc-André Lureaua2724282018-12-13 16:37:17 +040095 QAPISchemaTestVisitor._print_if(v.ifcond, indent=8)
Markus Armbruster156402e2015-09-16 13:06:08 +020096
Marc-André Lureaufbf09a22018-07-03 17:56:38 +020097 @staticmethod
98 def _print_if(ifcond, indent=4):
Markus Armbruster9c629fa2021-08-31 14:38:07 +020099 # TODO Drop this hack after replacing OrderedDict by plain
100 # dict (requires Python 3.7)
101 def _massage(subcond):
102 if isinstance(subcond, str):
103 return subcond
104 if isinstance(subcond, list):
105 return [_massage(val) for val in subcond]
106 return {key: _massage(val) for key, val in subcond.items()}
107
Marc-André Lureau33aa3262021-08-04 12:30:58 +0400108 if ifcond.is_present():
Markus Armbruster9c629fa2021-08-31 14:38:07 +0200109 print('%sif %s' % (' ' * indent, _massage(ifcond.ifcond)))
Marc-André Lureaufbf09a22018-07-03 17:56:38 +0200110
Peter Krempa2e2e0df22019-10-18 10:14:52 +0200111 @classmethod
Markus Armbruster84ab0082020-03-17 12:54:45 +0100112 def _print_features(cls, features, indent=4):
Peter Krempa2e2e0df22019-10-18 10:14:52 +0200113 if features:
114 for f in features:
Markus Armbruster84ab0082020-03-17 12:54:45 +0100115 print('%sfeature %s' % (' ' * indent, f.name))
116 cls._print_if(f.ifcond, indent + 4)
Peter Krempa2e2e0df22019-10-18 10:14:52 +0200117
Markus Armbruster181feaf2018-02-11 10:35:51 +0100118
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200119def test_frontend(fname):
120 schema = QAPISchema(fname)
121 schema.visit(QAPISchemaTestVisitor())
Markus Armbruster181feaf2018-02-11 10:35:51 +0100122
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200123 for doc in schema.docs:
124 if doc.symbol:
125 print('doc symbol=%s' % doc.symbol)
126 else:
127 print('doc freeform')
128 print(' body=\n%s' % doc.body.text)
129 for arg, section in doc.args.items():
130 print(' arg=%s\n%s' % (arg, section.text))
Markus Armbrustera0418a42019-10-24 13:02:22 +0200131 for feat, section in doc.features.items():
132 print(' feature=%s\n%s' % (feat, section.text))
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200133 for section in doc.sections:
Markus Armbruster31c54b92024-02-16 15:58:32 +0100134 print(' section=%s\n%s' % (section.tag, section.text))
Markus Armbruster818c3312017-03-20 14:11:53 +0100135
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200136
Markus Armbrusterf3336812021-09-22 14:56:19 +0200137def open_test_result(dir_name, file_name, update):
138 mode = 'r+' if update else 'r'
139 try:
Markus Armbruster5c24c3e2023-10-25 11:29:25 +0200140 return open(os.path.join(dir_name, file_name), mode, encoding='utf-8')
Markus Armbrusterf3336812021-09-22 14:56:19 +0200141 except FileNotFoundError:
142 if not update:
143 raise
Markus Armbruster5c24c3e2023-10-25 11:29:25 +0200144 return open(os.path.join(dir_name, file_name), 'w+', encoding='utf-8')
Markus Armbrusterf3336812021-09-22 14:56:19 +0200145
146
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200147def test_and_diff(test_name, dir_name, update):
148 sys.stdout = StringIO()
149 try:
150 test_frontend(os.path.join(dir_name, test_name + '.json'))
151 except QAPIError as err:
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200152 errstr = str(err) + '\n'
153 if dir_name:
154 errstr = errstr.replace(dir_name + '/', '')
155 actual_err = errstr.splitlines(True)
Markus Armbruster818c3312017-03-20 14:11:53 +0100156 else:
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200157 actual_err = []
158 finally:
159 actual_out = sys.stdout.getvalue().splitlines(True)
160 sys.stdout.close()
161 sys.stdout = sys.__stdout__
162
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200163 try:
Markus Armbrusterf3336812021-09-22 14:56:19 +0200164 outfp = open_test_result(dir_name, test_name + '.out', update)
165 errfp = open_test_result(dir_name, test_name + '.err', update)
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200166 expected_out = outfp.readlines()
167 expected_err = errfp.readlines()
Markus Armbruster436911c2021-09-22 14:56:18 +0200168 except OSError as err:
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200169 print("%s: can't open '%s': %s"
170 % (sys.argv[0], err.filename, err.strerror),
171 file=sys.stderr)
172 return 2
173
174 if actual_out == expected_out and actual_err == expected_err:
175 return 0
176
177 print("%s %s" % (test_name, 'UPDATE' if update else 'FAIL'),
178 file=sys.stderr)
179 out_diff = difflib.unified_diff(expected_out, actual_out, outfp.name)
180 err_diff = difflib.unified_diff(expected_err, actual_err, errfp.name)
181 sys.stdout.writelines(out_diff)
182 sys.stdout.writelines(err_diff)
183
184 if not update:
185 return 1
186
187 try:
188 outfp.truncate(0)
189 outfp.seek(0)
190 outfp.writelines(actual_out)
191 errfp.truncate(0)
192 errfp.seek(0)
193 errfp.writelines(actual_err)
Markus Armbruster436911c2021-09-22 14:56:18 +0200194 except OSError as err:
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200195 print("%s: can't write '%s': %s"
196 % (sys.argv[0], err.filename, err.strerror),
197 file=sys.stderr)
198 return 2
199
200 return 0
201
202
203def main(argv):
204 parser = argparse.ArgumentParser(
205 description='QAPI schema tester')
206 parser.add_argument('-d', '--dir', action='store', default='',
207 help="directory containing tests")
208 parser.add_argument('-u', '--update', action='store_true',
Daniel P. Berrangé7ce54db2023-04-20 11:26:17 +0100209 default='QAPI_TEST_UPDATE' in os.environ,
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200210 help="update expected test results")
211 parser.add_argument('tests', nargs='*', metavar='TEST', action='store')
212 args = parser.parse_args()
213
214 status = 0
215 for t in args.tests:
216 (dir_name, base_name) = os.path.split(t)
217 dir_name = dir_name or args.dir
218 test_name = os.path.splitext(base_name)[0]
219 status |= test_and_diff(test_name, dir_name, args.update)
220
Markus Armbruster5c24c3e2023-10-25 11:29:25 +0200221 sys.exit(status)
Markus Armbrusterf01338c2019-10-18 09:43:42 +0200222
223
224if __name__ == '__main__':
225 main(sys.argv)
Markus Armbruster5c24c3e2023-10-25 11:29:25 +0200226 sys.exit(0)