xref: /qemu/tests/qapi-schema/test-qapi.py (revision 94d689d0c6f23dc3129e8432c496ccb866788dbf)
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