1c88ee46cSPhilippe Mathieu-Daudé#!/usr/bin/env python3 298626572SMarkus Armbruster# 398626572SMarkus Armbruster# QAPI parser test harness 498626572SMarkus Armbruster# 598626572SMarkus Armbruster# Copyright (c) 2013 Red Hat Inc. 698626572SMarkus Armbruster# 798626572SMarkus Armbruster# Authors: 898626572SMarkus Armbruster# Markus Armbruster <armbru@redhat.com> 998626572SMarkus Armbruster# 1098626572SMarkus Armbruster# This work is licensed under the terms of the GNU GPL, version 2 or later. 1198626572SMarkus Armbruster# See the COPYING file in the top-level directory. 1298626572SMarkus Armbruster# 1398626572SMarkus Armbruster 14e6c42b96SMarkus Armbruster 15f01338ccSMarkus Armbrusterimport argparse 16f01338ccSMarkus Armbrusterimport difflib 17f01338ccSMarkus Armbrusterimport os 1898626572SMarkus Armbrusterimport sys 19ed39c03eSMarkus Armbrusterfrom io import StringIO 20e6c42b96SMarkus Armbruster 21e6c42b96SMarkus Armbrusterfrom qapi.error import QAPIError 22e6c42b96SMarkus Armbrusterfrom qapi.schema import QAPISchema, QAPISchemaVisitor 23e6c42b96SMarkus Armbruster 2498626572SMarkus Armbruster 25156402e5SMarkus Armbrusterclass QAPISchemaTestVisitor(QAPISchemaVisitor): 26cf40a0a5SMarkus Armbruster 27cf40a0a5SMarkus Armbruster def visit_module(self, name): 28cf40a0a5SMarkus Armbruster print('module %s' % name) 29cf40a0a5SMarkus Armbruster 30cf40a0a5SMarkus Armbruster def visit_include(self, name, info): 31cf40a0a5SMarkus Armbruster print('include %s' % name) 32cf40a0a5SMarkus Armbruster 33013b4efcSMarkus Armbruster def visit_enum_type(self, name, info, ifcond, features, members, prefix): 341e381b65SMarc-André Lureau print('enum %s' % name) 35156402e5SMarkus Armbruster if prefix: 36ef9d9108SDaniel P. Berrange print(' prefix %s' % prefix) 371e381b65SMarc-André Lureau for m in members: 381e381b65SMarc-André Lureau print(' member %s' % m.name) 396cc32b0eSMarc-André Lureau self._print_if(m.ifcond, indent=8) 40b6c18755SMarkus Armbruster self._print_features(m.features, indent=8) 41fbf09a2fSMarc-André Lureau self._print_if(ifcond) 42013b4efcSMarkus Armbruster self._print_features(features) 43156402e5SMarkus Armbruster 44ca0ac758SMarkus Armbruster def visit_array_type(self, name, info, ifcond, element_type): 45ca0ac758SMarkus Armbruster if not info: 46ca0ac758SMarkus Armbruster return # suppress built-in arrays 47ca0ac758SMarkus Armbruster print('array %s %s' % (name, element_type.name)) 48ca0ac758SMarkus Armbruster self._print_if(ifcond) 49ca0ac758SMarkus Armbruster 507b3bc9e2SMarkus Armbruster def visit_object_type(self, name, info, ifcond, features, 51d1da8af8SMarkus Armbruster base, members, branches): 52ef9d9108SDaniel P. Berrange print('object %s' % name) 53156402e5SMarkus Armbruster if base: 54ef9d9108SDaniel P. Berrange print(' base %s' % base.name) 55156402e5SMarkus Armbruster for m in members: 56b736e25aSMarkus Armbruster print(' member %s: %s optional=%s' 57b736e25aSMarkus Armbruster % (m.name, m.type.name, m.optional)) 58ccadd6bcSMarc-André Lureau self._print_if(m.ifcond, 8) 5984ab0086SMarkus Armbruster self._print_features(m.features, indent=8) 60d1da8af8SMarkus Armbruster self._print_variants(branches) 61fbf09a2fSMarc-André Lureau self._print_if(ifcond) 622e2e0df2SPeter Krempa self._print_features(features) 63156402e5SMarkus Armbruster 64*41d0ad1dSMarkus Armbruster def visit_alternate_type(self, name, info, ifcond, features, 65*41d0ad1dSMarkus Armbruster alternatives): 66ef9d9108SDaniel P. Berrange print('alternate %s' % name) 67*41d0ad1dSMarkus Armbruster self._print_variants(alternatives) 68fbf09a2fSMarc-André Lureau self._print_if(ifcond) 69013b4efcSMarkus Armbruster self._print_features(features) 70156402e5SMarkus Armbruster 717b3bc9e2SMarkus Armbruster def visit_command(self, name, info, ifcond, features, 727b3bc9e2SMarkus Armbruster arg_type, ret_type, gen, success_response, boxed, 7304f22362SKevin Wolf allow_oob, allow_preconfig, coroutine): 74b736e25aSMarkus Armbruster print('command %s %s -> %s' 75b736e25aSMarkus Armbruster % (name, arg_type and arg_type.name, 76b736e25aSMarkus Armbruster ret_type and ret_type.name)) 7704f22362SKevin Wolf print(' gen=%s success_response=%s boxed=%s oob=%s preconfig=%s%s' 7804f22362SKevin Wolf % (gen, success_response, boxed, allow_oob, allow_preconfig, 7904f22362SKevin Wolf " coroutine=True" if coroutine else "")) 80fbf09a2fSMarc-André Lureau self._print_if(ifcond) 812e2e0df2SPeter Krempa self._print_features(features) 82156402e5SMarkus Armbruster 83013b4efcSMarkus Armbruster def visit_event(self, name, info, ifcond, features, arg_type, boxed): 84ef9d9108SDaniel P. Berrange print('event %s %s' % (name, arg_type and arg_type.name)) 85ef9d9108SDaniel P. Berrange print(' boxed=%s' % boxed) 86fbf09a2fSMarc-André Lureau self._print_if(ifcond) 87013b4efcSMarkus Armbruster self._print_features(features) 88156402e5SMarkus Armbruster 89156402e5SMarkus Armbruster @staticmethod 90156402e5SMarkus Armbruster def _print_variants(variants): 91156402e5SMarkus Armbruster if variants: 92ef9d9108SDaniel P. Berrange print(' tag %s' % variants.tag_member.name) 93156402e5SMarkus Armbruster for v in variants.variants: 94ef9d9108SDaniel P. Berrange print(' case %s: %s' % (v.name, v.type.name)) 95a2724280SMarc-André Lureau QAPISchemaTestVisitor._print_if(v.ifcond, indent=8) 96156402e5SMarkus Armbruster 97fbf09a2fSMarc-André Lureau @staticmethod 98fbf09a2fSMarc-André Lureau def _print_if(ifcond, indent=4): 999c629fa8SMarkus Armbruster # TODO Drop this hack after replacing OrderedDict by plain 1009c629fa8SMarkus Armbruster # dict (requires Python 3.7) 1019c629fa8SMarkus Armbruster def _massage(subcond): 1029c629fa8SMarkus Armbruster if isinstance(subcond, str): 1039c629fa8SMarkus Armbruster return subcond 1049c629fa8SMarkus Armbruster if isinstance(subcond, list): 1059c629fa8SMarkus Armbruster return [_massage(val) for val in subcond] 1069c629fa8SMarkus Armbruster return {key: _massage(val) for key, val in subcond.items()} 1079c629fa8SMarkus Armbruster 10833aa3267SMarc-André Lureau if ifcond.is_present(): 1099c629fa8SMarkus Armbruster print('%sif %s' % (' ' * indent, _massage(ifcond.ifcond))) 110fbf09a2fSMarc-André Lureau 1112e2e0df2SPeter Krempa @classmethod 11284ab0086SMarkus Armbruster def _print_features(cls, features, indent=4): 1132e2e0df2SPeter Krempa if features: 1142e2e0df2SPeter Krempa for f in features: 11584ab0086SMarkus Armbruster print('%sfeature %s' % (' ' * indent, f.name)) 11684ab0086SMarkus Armbruster cls._print_if(f.ifcond, indent + 4) 1172e2e0df2SPeter Krempa 118181feaf3SMarkus Armbruster 119f01338ccSMarkus Armbrusterdef test_frontend(fname): 120f01338ccSMarkus Armbruster schema = QAPISchema(fname) 121156402e5SMarkus Armbruster schema.visit(QAPISchemaTestVisitor()) 122818c3318SMarkus Armbruster 123818c3318SMarkus Armbruster for doc in schema.docs: 124818c3318SMarkus Armbruster if doc.symbol: 125ef9d9108SDaniel P. Berrange print('doc symbol=%s' % doc.symbol) 126818c3318SMarkus Armbruster else: 127ef9d9108SDaniel P. Berrange print('doc freeform') 128ef9d9108SDaniel P. Berrange print(' body=\n%s' % doc.body.text) 1292f848044SDaniel P. Berrange for arg, section in doc.args.items(): 130ef9d9108SDaniel P. Berrange print(' arg=%s\n%s' % (arg, section.text)) 131a0418a4aSMarkus Armbruster for feat, section in doc.features.items(): 132a0418a4aSMarkus Armbruster print(' feature=%s\n%s' % (feat, section.text)) 133818c3318SMarkus Armbruster for section in doc.sections: 13431c54b92SMarkus Armbruster print(' section=%s\n%s' % (section.tag, section.text)) 135f01338ccSMarkus Armbruster 136f01338ccSMarkus Armbruster 137f333681cSMarkus Armbrusterdef open_test_result(dir_name, file_name, update): 138f333681cSMarkus Armbruster mode = 'r+' if update else 'r' 139f333681cSMarkus Armbruster try: 1405c24c3e2SMarkus Armbruster return open(os.path.join(dir_name, file_name), mode, encoding='utf-8') 141f333681cSMarkus Armbruster except FileNotFoundError: 142f333681cSMarkus Armbruster if not update: 143f333681cSMarkus Armbruster raise 1445c24c3e2SMarkus Armbruster return open(os.path.join(dir_name, file_name), 'w+', encoding='utf-8') 145f333681cSMarkus Armbruster 146f333681cSMarkus Armbruster 147f01338ccSMarkus Armbrusterdef test_and_diff(test_name, dir_name, update): 148f01338ccSMarkus Armbruster sys.stdout = StringIO() 149f01338ccSMarkus Armbruster try: 150f01338ccSMarkus Armbruster test_frontend(os.path.join(dir_name, test_name + '.json')) 151f01338ccSMarkus Armbruster except QAPIError as err: 152f01338ccSMarkus Armbruster errstr = str(err) + '\n' 153f01338ccSMarkus Armbruster if dir_name: 154f01338ccSMarkus Armbruster errstr = errstr.replace(dir_name + '/', '') 155f01338ccSMarkus Armbruster actual_err = errstr.splitlines(True) 156f01338ccSMarkus Armbruster else: 157f01338ccSMarkus Armbruster actual_err = [] 158f01338ccSMarkus Armbruster finally: 159f01338ccSMarkus Armbruster actual_out = sys.stdout.getvalue().splitlines(True) 160f01338ccSMarkus Armbruster sys.stdout.close() 161f01338ccSMarkus Armbruster sys.stdout = sys.__stdout__ 162f01338ccSMarkus Armbruster 163f01338ccSMarkus Armbruster try: 164f333681cSMarkus Armbruster outfp = open_test_result(dir_name, test_name + '.out', update) 165f333681cSMarkus Armbruster errfp = open_test_result(dir_name, test_name + '.err', update) 166f01338ccSMarkus Armbruster expected_out = outfp.readlines() 167f01338ccSMarkus Armbruster expected_err = errfp.readlines() 168436911c2SMarkus Armbruster except OSError as err: 169f01338ccSMarkus Armbruster print("%s: can't open '%s': %s" 170f01338ccSMarkus Armbruster % (sys.argv[0], err.filename, err.strerror), 171f01338ccSMarkus Armbruster file=sys.stderr) 172f01338ccSMarkus Armbruster return 2 173f01338ccSMarkus Armbruster 174f01338ccSMarkus Armbruster if actual_out == expected_out and actual_err == expected_err: 175f01338ccSMarkus Armbruster return 0 176f01338ccSMarkus Armbruster 177f01338ccSMarkus Armbruster print("%s %s" % (test_name, 'UPDATE' if update else 'FAIL'), 178f01338ccSMarkus Armbruster file=sys.stderr) 179f01338ccSMarkus Armbruster out_diff = difflib.unified_diff(expected_out, actual_out, outfp.name) 180f01338ccSMarkus Armbruster err_diff = difflib.unified_diff(expected_err, actual_err, errfp.name) 181f01338ccSMarkus Armbruster sys.stdout.writelines(out_diff) 182f01338ccSMarkus Armbruster sys.stdout.writelines(err_diff) 183f01338ccSMarkus Armbruster 184f01338ccSMarkus Armbruster if not update: 185f01338ccSMarkus Armbruster return 1 186f01338ccSMarkus Armbruster 187f01338ccSMarkus Armbruster try: 188f01338ccSMarkus Armbruster outfp.truncate(0) 189f01338ccSMarkus Armbruster outfp.seek(0) 190f01338ccSMarkus Armbruster outfp.writelines(actual_out) 191f01338ccSMarkus Armbruster errfp.truncate(0) 192f01338ccSMarkus Armbruster errfp.seek(0) 193f01338ccSMarkus Armbruster errfp.writelines(actual_err) 194436911c2SMarkus Armbruster except OSError as err: 195f01338ccSMarkus Armbruster print("%s: can't write '%s': %s" 196f01338ccSMarkus Armbruster % (sys.argv[0], err.filename, err.strerror), 197f01338ccSMarkus Armbruster file=sys.stderr) 198f01338ccSMarkus Armbruster return 2 199f01338ccSMarkus Armbruster 200f01338ccSMarkus Armbruster return 0 201f01338ccSMarkus Armbruster 202f01338ccSMarkus Armbruster 203f01338ccSMarkus Armbrusterdef main(argv): 204f01338ccSMarkus Armbruster parser = argparse.ArgumentParser( 205f01338ccSMarkus Armbruster description='QAPI schema tester') 206f01338ccSMarkus Armbruster parser.add_argument('-d', '--dir', action='store', default='', 207f01338ccSMarkus Armbruster help="directory containing tests") 208f01338ccSMarkus Armbruster parser.add_argument('-u', '--update', action='store_true', 2097ce54db2SDaniel P. Berrangé default='QAPI_TEST_UPDATE' in os.environ, 210f01338ccSMarkus Armbruster help="update expected test results") 211f01338ccSMarkus Armbruster parser.add_argument('tests', nargs='*', metavar='TEST', action='store') 212f01338ccSMarkus Armbruster args = parser.parse_args() 213f01338ccSMarkus Armbruster 214f01338ccSMarkus Armbruster status = 0 215f01338ccSMarkus Armbruster for t in args.tests: 216f01338ccSMarkus Armbruster (dir_name, base_name) = os.path.split(t) 217f01338ccSMarkus Armbruster dir_name = dir_name or args.dir 218f01338ccSMarkus Armbruster test_name = os.path.splitext(base_name)[0] 219f01338ccSMarkus Armbruster status |= test_and_diff(test_name, dir_name, args.update) 220f01338ccSMarkus Armbruster 2215c24c3e2SMarkus Armbruster sys.exit(status) 222f01338ccSMarkus Armbruster 223f01338ccSMarkus Armbruster 224f01338ccSMarkus Armbrusterif __name__ == '__main__': 225f01338ccSMarkus Armbruster main(sys.argv) 2265c24c3e2SMarkus Armbruster sys.exit(0) 227