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 6441d0ad1dSMarkus Armbruster def visit_alternate_type(self, name, info, ifcond, features, 6541d0ad1dSMarkus Armbruster alternatives): 66ef9d9108SDaniel P. Berrange print('alternate %s' % name) 6741d0ad1dSMarkus 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): 9933aa3267SMarc-André Lureau if ifcond.is_present(): 1005fbc8126SMarkus Armbruster print('%sif %s' % (' ' * indent, ifcond.ifcond)) 101fbf09a2fSMarc-André Lureau 1022e2e0df2SPeter Krempa @classmethod 10384ab0086SMarkus Armbruster def _print_features(cls, features, indent=4): 1042e2e0df2SPeter Krempa if features: 1052e2e0df2SPeter Krempa for f in features: 10684ab0086SMarkus Armbruster print('%sfeature %s' % (' ' * indent, f.name)) 10784ab0086SMarkus Armbruster cls._print_if(f.ifcond, indent + 4) 1082e2e0df2SPeter Krempa 109181feaf3SMarkus Armbruster 110f01338ccSMarkus Armbrusterdef test_frontend(fname): 111f01338ccSMarkus Armbruster schema = QAPISchema(fname) 112156402e5SMarkus Armbruster schema.visit(QAPISchemaTestVisitor()) 113818c3318SMarkus Armbruster 114818c3318SMarkus Armbruster for doc in schema.docs: 115818c3318SMarkus Armbruster if doc.symbol: 116ef9d9108SDaniel P. Berrange print('doc symbol=%s' % doc.symbol) 117818c3318SMarkus Armbruster else: 118ef9d9108SDaniel P. Berrange print('doc freeform') 119ef9d9108SDaniel P. Berrange print(' body=\n%s' % doc.body.text) 1202f848044SDaniel P. Berrange for arg, section in doc.args.items(): 121ef9d9108SDaniel P. Berrange print(' arg=%s\n%s' % (arg, section.text)) 122a0418a4aSMarkus Armbruster for feat, section in doc.features.items(): 123a0418a4aSMarkus Armbruster print(' feature=%s\n%s' % (feat, section.text)) 124818c3318SMarkus Armbruster for section in doc.sections: 125*323c6689SJohn Snow print(' section=%s\n%s' % (section.kind, section.text)) 126f01338ccSMarkus Armbruster 127f01338ccSMarkus Armbruster 128f333681cSMarkus Armbrusterdef open_test_result(dir_name, file_name, update): 129f333681cSMarkus Armbruster mode = 'r+' if update else 'r' 130f333681cSMarkus Armbruster try: 1315c24c3e2SMarkus Armbruster return open(os.path.join(dir_name, file_name), mode, encoding='utf-8') 132f333681cSMarkus Armbruster except FileNotFoundError: 133f333681cSMarkus Armbruster if not update: 134f333681cSMarkus Armbruster raise 1355c24c3e2SMarkus Armbruster return open(os.path.join(dir_name, file_name), 'w+', encoding='utf-8') 136f333681cSMarkus Armbruster 137f333681cSMarkus Armbruster 138f01338ccSMarkus Armbrusterdef test_and_diff(test_name, dir_name, update): 139f01338ccSMarkus Armbruster sys.stdout = StringIO() 140f01338ccSMarkus Armbruster try: 141f01338ccSMarkus Armbruster test_frontend(os.path.join(dir_name, test_name + '.json')) 142f01338ccSMarkus Armbruster except QAPIError as err: 143f01338ccSMarkus Armbruster errstr = str(err) + '\n' 144f01338ccSMarkus Armbruster if dir_name: 145f01338ccSMarkus Armbruster errstr = errstr.replace(dir_name + '/', '') 146f01338ccSMarkus Armbruster actual_err = errstr.splitlines(True) 147f01338ccSMarkus Armbruster else: 148f01338ccSMarkus Armbruster actual_err = [] 149f01338ccSMarkus Armbruster finally: 150f01338ccSMarkus Armbruster actual_out = sys.stdout.getvalue().splitlines(True) 151f01338ccSMarkus Armbruster sys.stdout.close() 152f01338ccSMarkus Armbruster sys.stdout = sys.__stdout__ 153f01338ccSMarkus Armbruster 154f01338ccSMarkus Armbruster try: 155f333681cSMarkus Armbruster outfp = open_test_result(dir_name, test_name + '.out', update) 156f333681cSMarkus Armbruster errfp = open_test_result(dir_name, test_name + '.err', update) 157f01338ccSMarkus Armbruster expected_out = outfp.readlines() 158f01338ccSMarkus Armbruster expected_err = errfp.readlines() 159436911c2SMarkus Armbruster except OSError as err: 160f01338ccSMarkus Armbruster print("%s: can't open '%s': %s" 161f01338ccSMarkus Armbruster % (sys.argv[0], err.filename, err.strerror), 162f01338ccSMarkus Armbruster file=sys.stderr) 163f01338ccSMarkus Armbruster return 2 164f01338ccSMarkus Armbruster 165f01338ccSMarkus Armbruster if actual_out == expected_out and actual_err == expected_err: 166f01338ccSMarkus Armbruster return 0 167f01338ccSMarkus Armbruster 168f01338ccSMarkus Armbruster print("%s %s" % (test_name, 'UPDATE' if update else 'FAIL'), 169f01338ccSMarkus Armbruster file=sys.stderr) 170f01338ccSMarkus Armbruster out_diff = difflib.unified_diff(expected_out, actual_out, outfp.name) 171f01338ccSMarkus Armbruster err_diff = difflib.unified_diff(expected_err, actual_err, errfp.name) 172f01338ccSMarkus Armbruster sys.stdout.writelines(out_diff) 173f01338ccSMarkus Armbruster sys.stdout.writelines(err_diff) 174f01338ccSMarkus Armbruster 175f01338ccSMarkus Armbruster if not update: 176f01338ccSMarkus Armbruster return 1 177f01338ccSMarkus Armbruster 178f01338ccSMarkus Armbruster try: 179f01338ccSMarkus Armbruster outfp.truncate(0) 180f01338ccSMarkus Armbruster outfp.seek(0) 181f01338ccSMarkus Armbruster outfp.writelines(actual_out) 182f01338ccSMarkus Armbruster errfp.truncate(0) 183f01338ccSMarkus Armbruster errfp.seek(0) 184f01338ccSMarkus Armbruster errfp.writelines(actual_err) 185436911c2SMarkus Armbruster except OSError as err: 186f01338ccSMarkus Armbruster print("%s: can't write '%s': %s" 187f01338ccSMarkus Armbruster % (sys.argv[0], err.filename, err.strerror), 188f01338ccSMarkus Armbruster file=sys.stderr) 189f01338ccSMarkus Armbruster return 2 190f01338ccSMarkus Armbruster 191f01338ccSMarkus Armbruster return 0 192f01338ccSMarkus Armbruster 193f01338ccSMarkus Armbruster 194f01338ccSMarkus Armbrusterdef main(argv): 195f01338ccSMarkus Armbruster parser = argparse.ArgumentParser( 196f01338ccSMarkus Armbruster description='QAPI schema tester') 197f01338ccSMarkus Armbruster parser.add_argument('-d', '--dir', action='store', default='', 198f01338ccSMarkus Armbruster help="directory containing tests") 199f01338ccSMarkus Armbruster parser.add_argument('-u', '--update', action='store_true', 2007ce54db2SDaniel P. Berrangé default='QAPI_TEST_UPDATE' in os.environ, 201f01338ccSMarkus Armbruster help="update expected test results") 202f01338ccSMarkus Armbruster parser.add_argument('tests', nargs='*', metavar='TEST', action='store') 203f01338ccSMarkus Armbruster args = parser.parse_args() 204f01338ccSMarkus Armbruster 205f01338ccSMarkus Armbruster status = 0 206f01338ccSMarkus Armbruster for t in args.tests: 207f01338ccSMarkus Armbruster (dir_name, base_name) = os.path.split(t) 208f01338ccSMarkus Armbruster dir_name = dir_name or args.dir 209f01338ccSMarkus Armbruster test_name = os.path.splitext(base_name)[0] 210f01338ccSMarkus Armbruster status |= test_and_diff(test_name, dir_name, args.update) 211f01338ccSMarkus Armbruster 2125c24c3e2SMarkus Armbruster sys.exit(status) 213f01338ccSMarkus Armbruster 214f01338ccSMarkus Armbruster 215f01338ccSMarkus Armbrusterif __name__ == '__main__': 216f01338ccSMarkus Armbruster main(sys.argv) 2175c24c3e2SMarkus Armbruster sys.exit(0) 218