aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2019-10-23 13:39:08 +0100
committerPeter Maydell <peter.maydell@linaro.org>2019-10-23 13:39:08 +0100
commit69717d0f890e14cbdd668297751f9446d2e2a8fd (patch)
tree899680cc9b8e66c9cfc9fd09fd0a301f20415ba7
parentec97eb6133e204c8c0ee492cfc9c7551b6297aca (diff)
parent5f76a7aac156ca75680dad5df4a385fd0b58f6b1 (diff)
Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2019-10-22-v3' into staging
QAPI patches for 2019-10-22 # gpg: Signature made Tue 22 Oct 2019 15:56:36 BST # gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653 # gpg: issuer "armbru@redhat.com" # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full] # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full] # Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653 * remotes/armbru/tags/pull-qapi-2019-10-22-v3: qapi: Allow introspecting fix for savevm's cooperation with blockdev tests/qapi-schema: Cover feature documentation comments tests: qapi: Test 'features' of commands qapi: Add feature flags to commands tests/qapi-schema: Tidy up test output indentation qapi: Clear scripts/qapi/doc.py executable bits again qapi: Split up scripts/qapi/common.py qapi: Move gen_enum(), gen_enum_lookup() back to qapi/types.py qapi: Speed up frontend tests qapi: Eliminate accidental global frontend state qapi: Store pragma state in QAPISourceInfo, not global state qapi: Don't suppress doc generation without pragma doc-required Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--Makefile13
-rw-r--r--docs/devel/qapi-code-gen.txt10
-rw-r--r--qapi/introspect.json6
-rw-r--r--qapi/misc.json9
-rwxr-xr-xscripts/qapi-gen.py10
-rw-r--r--scripts/qapi/commands.py4
-rw-r--r--scripts/qapi/common.py2377
-rw-r--r--[-rwxr-xr-x]scripts/qapi/doc.py14
-rw-r--r--scripts/qapi/error.py43
-rw-r--r--scripts/qapi/events.py3
-rw-r--r--scripts/qapi/expr.py383
-rw-r--r--scripts/qapi/gen.py291
-rw-r--r--scripts/qapi/introspect.py12
-rw-r--r--scripts/qapi/parser.py570
-rw-r--r--scripts/qapi/schema.py1057
-rw-r--r--scripts/qapi/source.py67
-rw-r--r--scripts/qapi/types.py61
-rw-r--r--scripts/qapi/visit.py2
-rw-r--r--tests/Makefile.include30
-rw-r--r--tests/qapi-schema/allow-preconfig-test.err4
-rw-r--r--tests/qapi-schema/allow-preconfig-test.exit1
-rw-r--r--tests/qapi-schema/alternate-any.err4
-rw-r--r--tests/qapi-schema/alternate-any.exit1
-rw-r--r--tests/qapi-schema/alternate-array.err4
-rw-r--r--tests/qapi-schema/alternate-array.exit1
-rw-r--r--tests/qapi-schema/alternate-base.err4
-rw-r--r--tests/qapi-schema/alternate-base.exit1
-rw-r--r--tests/qapi-schema/alternate-branch-if-invalid.err4
-rw-r--r--tests/qapi-schema/alternate-branch-if-invalid.exit1
-rw-r--r--tests/qapi-schema/alternate-clash.err4
-rw-r--r--tests/qapi-schema/alternate-clash.exit1
-rw-r--r--tests/qapi-schema/alternate-conflict-bool-string.err4
-rw-r--r--tests/qapi-schema/alternate-conflict-bool-string.exit1
-rw-r--r--tests/qapi-schema/alternate-conflict-dict.err4
-rw-r--r--tests/qapi-schema/alternate-conflict-dict.exit1
-rw-r--r--tests/qapi-schema/alternate-conflict-enum-bool.err4
-rw-r--r--tests/qapi-schema/alternate-conflict-enum-bool.exit1
-rw-r--r--tests/qapi-schema/alternate-conflict-enum-int.err4
-rw-r--r--tests/qapi-schema/alternate-conflict-enum-int.exit1
-rw-r--r--tests/qapi-schema/alternate-conflict-num-string.err4
-rw-r--r--tests/qapi-schema/alternate-conflict-num-string.exit1
-rw-r--r--tests/qapi-schema/alternate-conflict-string.err4
-rw-r--r--tests/qapi-schema/alternate-conflict-string.exit1
-rw-r--r--tests/qapi-schema/alternate-empty.err4
-rw-r--r--tests/qapi-schema/alternate-empty.exit1
-rw-r--r--tests/qapi-schema/alternate-invalid-dict.err4
-rw-r--r--tests/qapi-schema/alternate-invalid-dict.exit1
-rw-r--r--tests/qapi-schema/alternate-nested.err4
-rw-r--r--tests/qapi-schema/alternate-nested.exit1
-rw-r--r--tests/qapi-schema/alternate-unknown.err4
-rw-r--r--tests/qapi-schema/alternate-unknown.exit1
-rw-r--r--tests/qapi-schema/args-alternate.err4
-rw-r--r--tests/qapi-schema/args-alternate.exit1
-rw-r--r--tests/qapi-schema/args-any.err4
-rw-r--r--tests/qapi-schema/args-any.exit1
-rw-r--r--tests/qapi-schema/args-array-empty.err4
-rw-r--r--tests/qapi-schema/args-array-empty.exit1
-rw-r--r--tests/qapi-schema/args-array-unknown.err4
-rw-r--r--tests/qapi-schema/args-array-unknown.exit1
-rw-r--r--tests/qapi-schema/args-bad-boxed.err4
-rw-r--r--tests/qapi-schema/args-bad-boxed.exit1
-rw-r--r--tests/qapi-schema/args-boxed-anon.err4
-rw-r--r--tests/qapi-schema/args-boxed-anon.exit1
-rw-r--r--tests/qapi-schema/args-boxed-string.err4
-rw-r--r--tests/qapi-schema/args-boxed-string.exit1
-rw-r--r--tests/qapi-schema/args-int.err4
-rw-r--r--tests/qapi-schema/args-int.exit1
-rw-r--r--tests/qapi-schema/args-invalid.err4
-rw-r--r--tests/qapi-schema/args-invalid.exit1
-rw-r--r--tests/qapi-schema/args-member-array-bad.err4
-rw-r--r--tests/qapi-schema/args-member-array-bad.exit1
-rw-r--r--tests/qapi-schema/args-member-case.err4
-rw-r--r--tests/qapi-schema/args-member-case.exit1
-rw-r--r--tests/qapi-schema/args-member-unknown.err4
-rw-r--r--tests/qapi-schema/args-member-unknown.exit1
-rw-r--r--tests/qapi-schema/args-name-clash.err4
-rw-r--r--tests/qapi-schema/args-name-clash.exit1
-rw-r--r--tests/qapi-schema/args-union.err4
-rw-r--r--tests/qapi-schema/args-union.exit1
-rw-r--r--tests/qapi-schema/args-unknown.err4
-rw-r--r--tests/qapi-schema/args-unknown.exit1
-rw-r--r--tests/qapi-schema/bad-base.err4
-rw-r--r--tests/qapi-schema/bad-base.exit1
-rw-r--r--tests/qapi-schema/bad-data.err4
-rw-r--r--tests/qapi-schema/bad-data.exit1
-rw-r--r--tests/qapi-schema/bad-ident.err4
-rw-r--r--tests/qapi-schema/bad-ident.exit1
-rw-r--r--tests/qapi-schema/bad-if-empty-list.err4
-rw-r--r--tests/qapi-schema/bad-if-empty-list.exit1
-rw-r--r--tests/qapi-schema/bad-if-empty.err4
-rw-r--r--tests/qapi-schema/bad-if-empty.exit1
-rw-r--r--tests/qapi-schema/bad-if-list.err4
-rw-r--r--tests/qapi-schema/bad-if-list.exit1
-rw-r--r--tests/qapi-schema/bad-if.err4
-rw-r--r--tests/qapi-schema/bad-if.exit1
-rw-r--r--tests/qapi-schema/bad-type-bool.err2
-rw-r--r--tests/qapi-schema/bad-type-bool.exit1
-rw-r--r--tests/qapi-schema/bad-type-dict.err2
-rw-r--r--tests/qapi-schema/bad-type-dict.exit1
-rw-r--r--tests/qapi-schema/bad-type-int.err2
-rw-r--r--tests/qapi-schema/bad-type-int.exit1
-rw-r--r--tests/qapi-schema/base-cycle-direct.err4
-rw-r--r--tests/qapi-schema/base-cycle-direct.exit1
-rw-r--r--tests/qapi-schema/base-cycle-indirect.err4
-rw-r--r--tests/qapi-schema/base-cycle-indirect.exit1
-rw-r--r--tests/qapi-schema/command-int.err4
-rw-r--r--tests/qapi-schema/command-int.exit1
-rw-r--r--tests/qapi-schema/comments.exit1
-rw-r--r--tests/qapi-schema/doc-bad-alternate-member.err2
-rw-r--r--tests/qapi-schema/doc-bad-alternate-member.exit1
-rw-r--r--tests/qapi-schema/doc-bad-command-arg.err2
-rw-r--r--tests/qapi-schema/doc-bad-command-arg.exit1
-rw-r--r--tests/qapi-schema/doc-bad-section.exit1
-rw-r--r--tests/qapi-schema/doc-bad-symbol.err4
-rw-r--r--tests/qapi-schema/doc-bad-symbol.exit1
-rw-r--r--tests/qapi-schema/doc-bad-union-member.err2
-rw-r--r--tests/qapi-schema/doc-bad-union-member.exit1
-rw-r--r--tests/qapi-schema/doc-before-include.err2
-rw-r--r--tests/qapi-schema/doc-before-include.exit1
-rw-r--r--tests/qapi-schema/doc-before-pragma.err2
-rw-r--r--tests/qapi-schema/doc-before-pragma.exit1
-rw-r--r--tests/qapi-schema/doc-duplicated-arg.err2
-rw-r--r--tests/qapi-schema/doc-duplicated-arg.exit1
-rw-r--r--tests/qapi-schema/doc-duplicated-return.err2
-rw-r--r--tests/qapi-schema/doc-duplicated-return.exit1
-rw-r--r--tests/qapi-schema/doc-duplicated-since.err2
-rw-r--r--tests/qapi-schema/doc-duplicated-since.exit1
-rw-r--r--tests/qapi-schema/doc-empty-arg.err2
-rw-r--r--tests/qapi-schema/doc-empty-arg.exit1
-rw-r--r--tests/qapi-schema/doc-empty-section.err2
-rw-r--r--tests/qapi-schema/doc-empty-section.exit1
-rw-r--r--tests/qapi-schema/doc-empty-symbol.err2
-rw-r--r--tests/qapi-schema/doc-empty-symbol.exit1
-rw-r--r--tests/qapi-schema/doc-good.exit1
-rw-r--r--tests/qapi-schema/doc-good.json17
-rw-r--r--tests/qapi-schema/doc-good.out9
-rw-r--r--tests/qapi-schema/doc-good.texi22
-rw-r--r--tests/qapi-schema/doc-interleaved-section.err2
-rw-r--r--tests/qapi-schema/doc-interleaved-section.exit1
-rw-r--r--tests/qapi-schema/doc-invalid-end.err2
-rw-r--r--tests/qapi-schema/doc-invalid-end.exit1
-rw-r--r--tests/qapi-schema/doc-invalid-end2.err2
-rw-r--r--tests/qapi-schema/doc-invalid-end2.exit1
-rw-r--r--tests/qapi-schema/doc-invalid-return.err2
-rw-r--r--tests/qapi-schema/doc-invalid-return.exit1
-rw-r--r--tests/qapi-schema/doc-invalid-section.err2
-rw-r--r--tests/qapi-schema/doc-invalid-section.exit1
-rw-r--r--tests/qapi-schema/doc-invalid-start.err2
-rw-r--r--tests/qapi-schema/doc-invalid-start.exit1
-rw-r--r--tests/qapi-schema/doc-missing-colon.err2
-rw-r--r--tests/qapi-schema/doc-missing-colon.exit1
-rw-r--r--tests/qapi-schema/doc-missing-expr.err2
-rw-r--r--tests/qapi-schema/doc-missing-expr.exit1
-rw-r--r--tests/qapi-schema/doc-missing-space.err2
-rw-r--r--tests/qapi-schema/doc-missing-space.exit1
-rw-r--r--tests/qapi-schema/doc-missing.err4
-rw-r--r--tests/qapi-schema/doc-missing.exit1
-rw-r--r--tests/qapi-schema/doc-no-symbol.err2
-rw-r--r--tests/qapi-schema/doc-no-symbol.exit1
-rw-r--r--tests/qapi-schema/double-type.err4
-rw-r--r--tests/qapi-schema/double-type.exit1
-rw-r--r--tests/qapi-schema/duplicate-key.err2
-rw-r--r--tests/qapi-schema/duplicate-key.exit1
-rw-r--r--tests/qapi-schema/empty.exit1
-rw-r--r--tests/qapi-schema/enum-bad-member.err4
-rw-r--r--tests/qapi-schema/enum-bad-member.exit1
-rw-r--r--tests/qapi-schema/enum-bad-name.err4
-rw-r--r--tests/qapi-schema/enum-bad-name.exit1
-rw-r--r--tests/qapi-schema/enum-bad-prefix.err4
-rw-r--r--tests/qapi-schema/enum-bad-prefix.exit1
-rw-r--r--tests/qapi-schema/enum-clash-member.err4
-rw-r--r--tests/qapi-schema/enum-clash-member.exit1
-rw-r--r--tests/qapi-schema/enum-dict-member-unknown.err4
-rw-r--r--tests/qapi-schema/enum-dict-member-unknown.exit1
-rw-r--r--tests/qapi-schema/enum-if-invalid.err4
-rw-r--r--tests/qapi-schema/enum-if-invalid.exit1
-rw-r--r--tests/qapi-schema/enum-int-member.err2
-rw-r--r--tests/qapi-schema/enum-int-member.exit1
-rw-r--r--tests/qapi-schema/enum-member-case.err4
-rw-r--r--tests/qapi-schema/enum-member-case.exit1
-rw-r--r--tests/qapi-schema/enum-missing-data.err4
-rw-r--r--tests/qapi-schema/enum-missing-data.exit1
-rw-r--r--tests/qapi-schema/enum-wrong-data.err4
-rw-r--r--tests/qapi-schema/enum-wrong-data.exit1
-rw-r--r--tests/qapi-schema/escape-outside-string.err2
-rw-r--r--tests/qapi-schema/event-boxed-empty.err4
-rw-r--r--tests/qapi-schema/event-boxed-empty.exit1
-rw-r--r--tests/qapi-schema/event-case.exit1
-rw-r--r--tests/qapi-schema/event-case.out2
-rw-r--r--tests/qapi-schema/event-member-invalid-dict.err4
-rw-r--r--tests/qapi-schema/event-member-invalid-dict.exit1
-rw-r--r--tests/qapi-schema/event-nest-struct.err4
-rw-r--r--tests/qapi-schema/event-nest-struct.exit1
-rw-r--r--tests/qapi-schema/features-bad-type.err4
-rw-r--r--tests/qapi-schema/features-bad-type.exit1
-rw-r--r--tests/qapi-schema/features-duplicate-name.err4
-rw-r--r--tests/qapi-schema/features-duplicate-name.exit1
-rw-r--r--tests/qapi-schema/features-if-invalid.err4
-rw-r--r--tests/qapi-schema/features-if-invalid.exit1
-rw-r--r--tests/qapi-schema/features-missing-name.err4
-rw-r--r--tests/qapi-schema/features-missing-name.exit1
-rw-r--r--tests/qapi-schema/features-name-bad-type.err4
-rw-r--r--tests/qapi-schema/features-name-bad-type.exit1
-rw-r--r--tests/qapi-schema/features-no-list.err4
-rw-r--r--tests/qapi-schema/features-no-list.exit1
-rw-r--r--tests/qapi-schema/features-unknown-key.err4
-rw-r--r--tests/qapi-schema/features-unknown-key.exit1
-rw-r--r--tests/qapi-schema/flat-union-array-branch.err4
-rw-r--r--tests/qapi-schema/flat-union-array-branch.exit1
-rw-r--r--tests/qapi-schema/flat-union-bad-base.err4
-rw-r--r--tests/qapi-schema/flat-union-bad-base.exit1
-rw-r--r--tests/qapi-schema/flat-union-bad-discriminator.err4
-rw-r--r--tests/qapi-schema/flat-union-bad-discriminator.exit1
-rw-r--r--tests/qapi-schema/flat-union-base-any.err4
-rw-r--r--tests/qapi-schema/flat-union-base-any.exit1
-rw-r--r--tests/qapi-schema/flat-union-base-union.err4
-rw-r--r--tests/qapi-schema/flat-union-base-union.exit1
-rw-r--r--tests/qapi-schema/flat-union-clash-member.err4
-rw-r--r--tests/qapi-schema/flat-union-clash-member.exit1
-rw-r--r--tests/qapi-schema/flat-union-discriminator-bad-name.err4
-rw-r--r--tests/qapi-schema/flat-union-discriminator-bad-name.exit1
-rw-r--r--tests/qapi-schema/flat-union-empty.err4
-rw-r--r--tests/qapi-schema/flat-union-empty.exit1
-rw-r--r--tests/qapi-schema/flat-union-inline-invalid-dict.err4
-rw-r--r--tests/qapi-schema/flat-union-inline-invalid-dict.exit1
-rw-r--r--tests/qapi-schema/flat-union-inline.err4
-rw-r--r--tests/qapi-schema/flat-union-inline.exit1
-rw-r--r--tests/qapi-schema/flat-union-int-branch.err4
-rw-r--r--tests/qapi-schema/flat-union-int-branch.exit1
-rw-r--r--tests/qapi-schema/flat-union-invalid-branch-key.err4
-rw-r--r--tests/qapi-schema/flat-union-invalid-branch-key.exit1
-rw-r--r--tests/qapi-schema/flat-union-invalid-discriminator.err4
-rw-r--r--tests/qapi-schema/flat-union-invalid-discriminator.exit1
-rw-r--r--tests/qapi-schema/flat-union-invalid-if-discriminator.err4
-rw-r--r--tests/qapi-schema/flat-union-invalid-if-discriminator.exit1
-rw-r--r--tests/qapi-schema/flat-union-no-base.err4
-rw-r--r--tests/qapi-schema/flat-union-no-base.exit1
-rw-r--r--tests/qapi-schema/flat-union-optional-discriminator.err4
-rw-r--r--tests/qapi-schema/flat-union-optional-discriminator.exit1
-rw-r--r--tests/qapi-schema/flat-union-string-discriminator.err4
-rw-r--r--tests/qapi-schema/flat-union-string-discriminator.exit1
-rw-r--r--tests/qapi-schema/funny-char.err2
-rw-r--r--tests/qapi-schema/funny-char.exit1
-rw-r--r--tests/qapi-schema/funny-word.err2
-rw-r--r--tests/qapi-schema/funny-word.exit1
-rw-r--r--tests/qapi-schema/ident-with-escape.err2
-rw-r--r--tests/qapi-schema/ident-with-escape.exit1
-rw-r--r--tests/qapi-schema/include-before-err.err2
-rw-r--r--tests/qapi-schema/include-before-err.exit1
-rw-r--r--tests/qapi-schema/include-cycle.err6
-rw-r--r--tests/qapi-schema/include-cycle.exit1
-rw-r--r--tests/qapi-schema/include-extra-junk.err2
-rw-r--r--tests/qapi-schema/include-extra-junk.exit1
-rw-r--r--tests/qapi-schema/include-nested-err.err4
-rw-r--r--tests/qapi-schema/include-nested-err.exit1
-rw-r--r--tests/qapi-schema/include-no-file.err2
-rw-r--r--tests/qapi-schema/include-no-file.exit1
-rw-r--r--tests/qapi-schema/include-non-file.err2
-rw-r--r--tests/qapi-schema/include-non-file.exit1
-rw-r--r--tests/qapi-schema/include-repetition.exit1
-rw-r--r--tests/qapi-schema/include-self-cycle.err2
-rw-r--r--tests/qapi-schema/include-self-cycle.exit1
-rw-r--r--tests/qapi-schema/include-simple.exit1
-rw-r--r--tests/qapi-schema/indented-expr.exit1
-rw-r--r--tests/qapi-schema/indented-expr.out4
-rw-r--r--tests/qapi-schema/leading-comma-list.err2
-rw-r--r--tests/qapi-schema/leading-comma-list.exit1
-rw-r--r--tests/qapi-schema/leading-comma-object.err2
-rw-r--r--tests/qapi-schema/leading-comma-object.exit1
-rw-r--r--tests/qapi-schema/missing-colon.err2
-rw-r--r--tests/qapi-schema/missing-colon.exit1
-rw-r--r--tests/qapi-schema/missing-comma-list.err2
-rw-r--r--tests/qapi-schema/missing-comma-list.exit1
-rw-r--r--tests/qapi-schema/missing-comma-object.err2
-rw-r--r--tests/qapi-schema/missing-comma-object.exit1
-rw-r--r--tests/qapi-schema/missing-type.err2
-rw-r--r--tests/qapi-schema/missing-type.exit1
-rw-r--r--tests/qapi-schema/nested-struct-data-invalid-dict.err4
-rw-r--r--tests/qapi-schema/nested-struct-data-invalid-dict.exit1
-rw-r--r--tests/qapi-schema/nested-struct-data.err4
-rw-r--r--tests/qapi-schema/nested-struct-data.exit1
-rw-r--r--tests/qapi-schema/non-objects.err2
-rw-r--r--tests/qapi-schema/non-objects.exit1
-rw-r--r--tests/qapi-schema/oob-test.err4
-rw-r--r--tests/qapi-schema/oob-test.exit1
-rw-r--r--tests/qapi-schema/pragma-doc-required-crap.err2
-rw-r--r--tests/qapi-schema/pragma-doc-required-crap.exit1
-rw-r--r--tests/qapi-schema/pragma-extra-junk.err2
-rw-r--r--tests/qapi-schema/pragma-extra-junk.exit1
-rw-r--r--tests/qapi-schema/pragma-name-case-whitelist-crap.err2
-rw-r--r--tests/qapi-schema/pragma-name-case-whitelist-crap.exit1
-rw-r--r--tests/qapi-schema/pragma-non-dict.err2
-rw-r--r--tests/qapi-schema/pragma-non-dict.exit1
-rw-r--r--tests/qapi-schema/pragma-returns-whitelist-crap.err2
-rw-r--r--tests/qapi-schema/pragma-returns-whitelist-crap.exit1
-rw-r--r--tests/qapi-schema/pragma-unknown.err2
-rw-r--r--tests/qapi-schema/pragma-unknown.exit1
-rw-r--r--tests/qapi-schema/qapi-schema-test.exit1
-rw-r--r--tests/qapi-schema/qapi-schema-test.json18
-rw-r--r--tests/qapi-schema/qapi-schema-test.out75
-rw-r--r--tests/qapi-schema/quoted-structural-chars.err2
-rw-r--r--tests/qapi-schema/quoted-structural-chars.exit1
-rw-r--r--tests/qapi-schema/redefined-builtin.err4
-rw-r--r--tests/qapi-schema/redefined-builtin.exit1
-rw-r--r--tests/qapi-schema/redefined-command.err8
-rw-r--r--tests/qapi-schema/redefined-command.exit1
-rw-r--r--tests/qapi-schema/redefined-event.err8
-rw-r--r--tests/qapi-schema/redefined-event.exit1
-rw-r--r--tests/qapi-schema/redefined-type.err8
-rw-r--r--tests/qapi-schema/redefined-type.exit1
-rw-r--r--tests/qapi-schema/reserved-command-q.err4
-rw-r--r--tests/qapi-schema/reserved-command-q.exit1
-rw-r--r--tests/qapi-schema/reserved-enum-q.err4
-rw-r--r--tests/qapi-schema/reserved-enum-q.exit1
-rw-r--r--tests/qapi-schema/reserved-member-has.err4
-rw-r--r--tests/qapi-schema/reserved-member-has.exit1
-rw-r--r--tests/qapi-schema/reserved-member-q.err4
-rw-r--r--tests/qapi-schema/reserved-member-q.exit1
-rw-r--r--tests/qapi-schema/reserved-member-u.err4
-rw-r--r--tests/qapi-schema/reserved-member-u.exit1
-rw-r--r--tests/qapi-schema/reserved-member-underscore.err4
-rw-r--r--tests/qapi-schema/reserved-member-underscore.exit1
-rw-r--r--tests/qapi-schema/reserved-type-kind.err4
-rw-r--r--tests/qapi-schema/reserved-type-kind.exit1
-rw-r--r--tests/qapi-schema/reserved-type-list.err4
-rw-r--r--tests/qapi-schema/reserved-type-list.exit1
-rw-r--r--tests/qapi-schema/returns-alternate.err4
-rw-r--r--tests/qapi-schema/returns-alternate.exit1
-rw-r--r--tests/qapi-schema/returns-array-bad.err4
-rw-r--r--tests/qapi-schema/returns-array-bad.exit1
-rw-r--r--tests/qapi-schema/returns-dict.err4
-rw-r--r--tests/qapi-schema/returns-dict.exit1
-rw-r--r--tests/qapi-schema/returns-unknown.err4
-rw-r--r--tests/qapi-schema/returns-unknown.exit1
-rw-r--r--tests/qapi-schema/returns-whitelist.err4
-rw-r--r--tests/qapi-schema/returns-whitelist.exit1
-rw-r--r--tests/qapi-schema/string-code-point-127.err2
-rw-r--r--tests/qapi-schema/string-code-point-127.exit1
-rw-r--r--tests/qapi-schema/string-code-point-31.err2
-rw-r--r--tests/qapi-schema/string-code-point-31.exit1
-rw-r--r--tests/qapi-schema/struct-base-clash-deep.err4
-rw-r--r--tests/qapi-schema/struct-base-clash-deep.exit1
-rw-r--r--tests/qapi-schema/struct-base-clash.err4
-rw-r--r--tests/qapi-schema/struct-base-clash.exit1
-rw-r--r--tests/qapi-schema/struct-data-invalid.err4
-rw-r--r--tests/qapi-schema/struct-data-invalid.exit1
-rw-r--r--tests/qapi-schema/struct-member-if-invalid.err4
-rw-r--r--tests/qapi-schema/struct-member-if-invalid.exit1
-rw-r--r--tests/qapi-schema/struct-member-invalid-dict.err4
-rw-r--r--tests/qapi-schema/struct-member-invalid-dict.exit1
-rw-r--r--tests/qapi-schema/struct-member-invalid.err4
-rw-r--r--tests/qapi-schema/struct-member-invalid.exit1
-rwxr-xr-x[-rw-r--r--]tests/qapi-schema/test-qapi.py151
-rw-r--r--tests/qapi-schema/trailing-comma-list.err2
-rw-r--r--tests/qapi-schema/trailing-comma-list.exit1
-rw-r--r--tests/qapi-schema/trailing-comma-object.err2
-rw-r--r--tests/qapi-schema/trailing-comma-object.exit1
-rw-r--r--tests/qapi-schema/type-bypass-bad-gen.err4
-rw-r--r--tests/qapi-schema/type-bypass-bad-gen.exit1
-rw-r--r--tests/qapi-schema/unclosed-list.err2
-rw-r--r--tests/qapi-schema/unclosed-list.exit1
-rw-r--r--tests/qapi-schema/unclosed-object.err2
-rw-r--r--tests/qapi-schema/unclosed-object.exit1
-rw-r--r--tests/qapi-schema/unclosed-string.err2
-rw-r--r--tests/qapi-schema/unclosed-string.exit1
-rw-r--r--tests/qapi-schema/union-base-empty.err4
-rw-r--r--tests/qapi-schema/union-base-empty.exit1
-rw-r--r--tests/qapi-schema/union-base-no-discriminator.err4
-rw-r--r--tests/qapi-schema/union-base-no-discriminator.exit1
-rw-r--r--tests/qapi-schema/union-branch-case.err4
-rw-r--r--tests/qapi-schema/union-branch-case.exit1
-rw-r--r--tests/qapi-schema/union-branch-if-invalid.err4
-rw-r--r--tests/qapi-schema/union-branch-if-invalid.exit1
-rw-r--r--tests/qapi-schema/union-branch-invalid-dict.err4
-rw-r--r--tests/qapi-schema/union-branch-invalid-dict.exit1
-rw-r--r--tests/qapi-schema/union-clash-branches.err4
-rw-r--r--tests/qapi-schema/union-clash-branches.exit1
-rw-r--r--tests/qapi-schema/union-empty.err4
-rw-r--r--tests/qapi-schema/union-empty.exit1
-rw-r--r--tests/qapi-schema/union-invalid-base.err4
-rw-r--r--tests/qapi-schema/union-invalid-base.exit1
-rw-r--r--tests/qapi-schema/union-optional-branch.err4
-rw-r--r--tests/qapi-schema/union-optional-branch.exit1
-rw-r--r--tests/qapi-schema/union-unknown.err4
-rw-r--r--tests/qapi-schema/union-unknown.exit1
-rw-r--r--tests/qapi-schema/unknown-escape.err2
-rw-r--r--tests/qapi-schema/unknown-escape.exit1
-rw-r--r--tests/qapi-schema/unknown-expr-key.err4
-rw-r--r--tests/qapi-schema/unknown-expr-key.exit1
-rw-r--r--tests/test-qmp-cmds.c24
390 files changed, 3116 insertions, 2961 deletions
diff --git a/Makefile b/Makefile
index d20e7ffce3..0e994a275d 100644
--- a/Makefile
+++ b/Makefile
@@ -582,13 +582,20 @@ qemu-ga$(EXESUF): QEMU_CFLAGS += -I qga/qapi-generated
qemu-keymap$(EXESUF): LIBS += $(XKBCOMMON_LIBS)
qemu-keymap$(EXESUF): QEMU_CFLAGS += $(XKBCOMMON_CFLAGS)
-qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
+qapi-py = $(SRC_PATH)/scripts/qapi/__init__.py \
+$(SRC_PATH)/scripts/qapi/commands.py \
+$(SRC_PATH)/scripts/qapi/common.py \
+$(SRC_PATH)/scripts/qapi/doc.py \
+$(SRC_PATH)/scripts/qapi/error.py \
$(SRC_PATH)/scripts/qapi/events.py \
+$(SRC_PATH)/scripts/qapi/expr.py \
+$(SRC_PATH)/scripts/qapi/gen.py \
$(SRC_PATH)/scripts/qapi/introspect.py \
+$(SRC_PATH)/scripts/qapi/parser.py \
+$(SRC_PATH)/scripts/qapi/schema.py \
+$(SRC_PATH)/scripts/qapi/source.py \
$(SRC_PATH)/scripts/qapi/types.py \
$(SRC_PATH)/scripts/qapi/visit.py \
-$(SRC_PATH)/scripts/qapi/common.py \
-$(SRC_PATH)/scripts/qapi/doc.py \
$(SRC_PATH)/scripts/qapi-gen.py
qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h \
diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt
index 64d9e4c6a9..45c93a43cc 100644
--- a/docs/devel/qapi-code-gen.txt
+++ b/docs/devel/qapi-code-gen.txt
@@ -457,7 +457,8 @@ Syntax:
'*gen': false,
'*allow-oob': true,
'*allow-preconfig': true,
- '*if': COND }
+ '*if': COND,
+ '*features': FEATURES }
Member 'command' names the command.
@@ -640,9 +641,10 @@ change in the QMP syntax (usually by allowing values or operations
that previously resulted in an error). QMP clients may still need to
know whether the extension is available.
-For this purpose, a list of features can be specified for a struct type.
-This is exposed to the client as a list of string, where each string
-signals that this build of QEMU shows a certain behaviour.
+For this purpose, a list of features can be specified for a command or
+struct type. This is exposed to the client as a list of strings,
+where each string signals that this build of QEMU shows a certain
+behaviour.
Each member of the 'features' array defines a feature. It can either
be { 'name': STRING, '*if': COND }, or STRING, which is shorthand for
diff --git a/qapi/introspect.json b/qapi/introspect.json
index 1843c1cb17..031a954fa9 100644
--- a/qapi/introspect.json
+++ b/qapi/introspect.json
@@ -266,13 +266,17 @@
# @allow-oob: whether the command allows out-of-band execution,
# defaults to false (Since: 2.12)
#
+# @features: names of features associated with the command, in no particular
+# order. (since 4.2)
+#
# TODO: @success-response (currently irrelevant, because it's QGA, not QMP)
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoCommand',
'data': { 'arg-type': 'str', 'ret-type': 'str',
- '*allow-oob': 'bool' } }
+ '*allow-oob': 'bool',
+ '*features': [ 'str' ] } }
##
# @SchemaInfoEvent:
diff --git a/qapi/misc.json b/qapi/misc.json
index 6bd11f50e6..33b94e3589 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -1020,6 +1020,12 @@
#
# @cpu-index: The CPU to use for commands that require an implicit CPU
#
+# Features:
+# @savevm-monitor-nodes: If present, HMP command savevm only snapshots
+# monitor-owned nodes if they have no parents.
+# This allows the use of 'savevm' with
+# -blockdev. (since 4.2)
+#
# Returns: the output of the command as a string
#
# Since: 0.14.0
@@ -1047,7 +1053,8 @@
##
{ 'command': 'human-monitor-command',
'data': {'command-line': 'str', '*cpu-index': 'int'},
- 'returns': 'str' }
+ 'returns': 'str',
+ 'features': [ 'savevm-monitor-nodes' ] }
##
# @change:
diff --git a/scripts/qapi-gen.py b/scripts/qapi-gen.py
index 3d98ca2e0c..f93f3c7c23 100755
--- a/scripts/qapi-gen.py
+++ b/scripts/qapi-gen.py
@@ -5,16 +5,18 @@
# See the COPYING file in the top-level directory.
from __future__ import print_function
+
import argparse
import re
import sys
-from qapi.common import QAPIError, QAPISchema
-from qapi.types import gen_types
-from qapi.visit import gen_visit
+
from qapi.commands import gen_commands
+from qapi.doc import gen_doc
from qapi.events import gen_events
from qapi.introspect import gen_introspect
-from qapi.doc import gen_doc
+from qapi.schema import QAPIError, QAPISchema
+from qapi.types import gen_types
+from qapi.visit import gen_visit
def main(argv):
diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
index 7e3dd1068a..ab98e504f3 100644
--- a/scripts/qapi/commands.py
+++ b/scripts/qapi/commands.py
@@ -14,6 +14,7 @@ See the COPYING file in the top-level directory.
"""
from qapi.common import *
+from qapi.gen import QAPIGenCCode, QAPISchemaModularCVisitor, ifcontext
def gen_command_decl(name, arg_type, boxed, ret_type):
@@ -276,7 +277,8 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
genc.add(gen_registry(self._regy.get_content(), self._prefix))
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
- success_response, boxed, allow_oob, allow_preconfig):
+ success_response, boxed, allow_oob, allow_preconfig,
+ features):
if not gen:
return
# FIXME: If T is a user-defined type, the user is responsible
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index d6e00c80ea..e00dcafce7 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -11,2053 +11,8 @@
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
-from __future__ import print_function
-from contextlib import contextmanager
-import copy
-import errno
-import os
import re
import string
-import sys
-from collections import OrderedDict
-
-# Are documentation comments required?
-doc_required = False
-
-# Whitelist of commands allowed to return a non-dictionary
-returns_whitelist = []
-
-# Whitelist of entities allowed to violate case conventions
-name_case_whitelist = []
-
-
-#
-# Parsing the schema into expressions
-#
-
-class QAPISourceInfo(object):
- def __init__(self, fname, line, parent):
- self.fname = fname
- self.line = line
- self.parent = parent
- self.defn_meta = None
- self.defn_name = None
-
- def set_defn(self, meta, name):
- self.defn_meta = meta
- self.defn_name = name
-
- def next_line(self):
- info = copy.copy(self)
- info.line += 1
- return info
-
- def loc(self):
- if self.fname is None:
- return sys.argv[0]
- ret = self.fname
- if self.line is not None:
- ret += ':%d' % self.line
- return ret
-
- def in_defn(self):
- if self.defn_name:
- return "%s: In %s '%s':\n" % (self.fname,
- self.defn_meta, self.defn_name)
- return ''
-
- def include_path(self):
- ret = ''
- parent = self.parent
- while parent:
- ret = 'In file included from %s:\n' % parent.loc() + ret
- parent = parent.parent
- return ret
-
- def __str__(self):
- return self.include_path() + self.in_defn() + self.loc()
-
-
-class QAPIError(Exception):
- def __init__(self, info, col, msg):
- Exception.__init__(self)
- self.info = info
- self.col = col
- self.msg = msg
-
- def __str__(self):
- loc = str(self.info)
- if self.col is not None:
- assert self.info.line is not None
- loc += ':%s' % self.col
- return loc + ': ' + self.msg
-
-
-class QAPIParseError(QAPIError):
- def __init__(self, parser, msg):
- col = 1
- for ch in parser.src[parser.line_pos:parser.pos]:
- if ch == '\t':
- col = (col + 7) % 8 + 1
- else:
- col += 1
- QAPIError.__init__(self, parser.info, col, msg)
-
-
-class QAPISemError(QAPIError):
- def __init__(self, info, msg):
- QAPIError.__init__(self, info, None, msg)
-
-
-class QAPIDoc(object):
- """
- A documentation comment block, either definition or free-form
-
- Definition documentation blocks consist of
-
- * a body section: one line naming the definition, followed by an
- overview (any number of lines)
-
- * argument sections: a description of each argument (for commands
- and events) or member (for structs, unions and alternates)
-
- * features sections: a description of each feature flag
-
- * additional (non-argument) sections, possibly tagged
-
- Free-form documentation blocks consist only of a body section.
- """
-
- class Section(object):
- def __init__(self, name=None):
- # optional section name (argument/member or section name)
- self.name = name
- # the list of lines for this section
- self.text = ''
-
- def append(self, line):
- self.text += line.rstrip() + '\n'
-
- class ArgSection(Section):
- def __init__(self, name):
- QAPIDoc.Section.__init__(self, name)
- self.member = None
-
- def connect(self, member):
- self.member = member
-
- def __init__(self, parser, info):
- # self._parser is used to report errors with QAPIParseError. The
- # resulting error position depends on the state of the parser.
- # It happens to be the beginning of the comment. More or less
- # servicable, but action at a distance.
- self._parser = parser
- self.info = info
- self.symbol = None
- self.body = QAPIDoc.Section()
- # dict mapping parameter name to ArgSection
- self.args = OrderedDict()
- self.features = OrderedDict()
- # a list of Section
- self.sections = []
- # the current section
- self._section = self.body
- self._append_line = self._append_body_line
-
- def has_section(self, name):
- """Return True if we have a section with this name."""
- for i in self.sections:
- if i.name == name:
- return True
- return False
-
- def append(self, line):
- """
- Parse a comment line and add it to the documentation.
-
- The way that the line is dealt with depends on which part of
- the documentation we're parsing right now:
- * The body section: ._append_line is ._append_body_line
- * An argument section: ._append_line is ._append_args_line
- * A features section: ._append_line is ._append_features_line
- * An additional section: ._append_line is ._append_various_line
- """
- line = line[1:]
- if not line:
- self._append_freeform(line)
- return
-
- if line[0] != ' ':
- raise QAPIParseError(self._parser, "missing space after #")
- line = line[1:]
- self._append_line(line)
-
- def end_comment(self):
- self._end_section()
-
- @staticmethod
- def _is_section_tag(name):
- return name in ('Returns:', 'Since:',
- # those are often singular or plural
- 'Note:', 'Notes:',
- 'Example:', 'Examples:',
- 'TODO:')
-
- def _append_body_line(self, line):
- """
- Process a line of documentation text in the body section.
-
- If this a symbol line and it is the section's first line, this
- is a definition documentation block for that symbol.
-
- If it's a definition documentation block, another symbol line
- begins the argument section for the argument named by it, and
- a section tag begins an additional section. Start that
- section and append the line to it.
-
- Else, append the line to the current section.
- """
- name = line.split(' ', 1)[0]
- # FIXME not nice: things like '# @foo:' and '# @foo: ' aren't
- # recognized, and get silently treated as ordinary text
- if not self.symbol and not self.body.text and line.startswith('@'):
- if not line.endswith(':'):
- raise QAPIParseError(self._parser, "line should end with ':'")
- self.symbol = line[1:-1]
- # FIXME invalid names other than the empty string aren't flagged
- if not self.symbol:
- raise QAPIParseError(self._parser, "invalid name")
- elif self.symbol:
- # This is a definition documentation block
- if name.startswith('@') and name.endswith(':'):
- self._append_line = self._append_args_line
- self._append_args_line(line)
- elif line == 'Features:':
- self._append_line = self._append_features_line
- elif self._is_section_tag(name):
- self._append_line = self._append_various_line
- self._append_various_line(line)
- else:
- self._append_freeform(line.strip())
- else:
- # This is a free-form documentation block
- self._append_freeform(line.strip())
-
- def _append_args_line(self, line):
- """
- Process a line of documentation text in an argument section.
-
- A symbol line begins the next argument section, a section tag
- section or a non-indented line after a blank line begins an
- additional section. Start that section and append the line to
- it.
-
- Else, append the line to the current section.
-
- """
- name = line.split(' ', 1)[0]
-
- if name.startswith('@') and name.endswith(':'):
- line = line[len(name)+1:]
- self._start_args_section(name[1:-1])
- elif self._is_section_tag(name):
- self._append_line = self._append_various_line
- self._append_various_line(line)
- return
- elif (self._section.text.endswith('\n\n')
- and line and not line[0].isspace()):
- if line == 'Features:':
- self._append_line = self._append_features_line
- else:
- self._start_section()
- self._append_line = self._append_various_line
- self._append_various_line(line)
- return
-
- self._append_freeform(line.strip())
-
- def _append_features_line(self, line):
- name = line.split(' ', 1)[0]
-
- if name.startswith('@') and name.endswith(':'):
- line = line[len(name)+1:]
- self._start_features_section(name[1:-1])
- elif self._is_section_tag(name):
- self._append_line = self._append_various_line
- self._append_various_line(line)
- return
- elif (self._section.text.endswith('\n\n')
- and line and not line[0].isspace()):
- self._start_section()
- self._append_line = self._append_various_line
- self._append_various_line(line)
- return
-
- self._append_freeform(line.strip())
-
- def _append_various_line(self, line):
- """
- Process a line of documentation text in an additional section.
-
- A symbol line is an error.
-
- A section tag begins an additional section. Start that
- section and append the line to it.
-
- Else, append the line to the current section.
- """
- name = line.split(' ', 1)[0]
-
- if name.startswith('@') and name.endswith(':'):
- raise QAPIParseError(self._parser,
- "'%s' can't follow '%s' section"
- % (name, self.sections[0].name))
- elif self._is_section_tag(name):
- line = line[len(name)+1:]
- self._start_section(name[:-1])
-
- if (not self._section.name or
- not self._section.name.startswith('Example')):
- line = line.strip()
-
- self._append_freeform(line)
-
- def _start_symbol_section(self, symbols_dict, name):
- # FIXME invalid names other than the empty string aren't flagged
- if not name:
- raise QAPIParseError(self._parser, "invalid parameter name")
- if name in symbols_dict:
- raise QAPIParseError(self._parser,
- "'%s' parameter name duplicated" % name)
- assert not self.sections
- self._end_section()
- self._section = QAPIDoc.ArgSection(name)
- symbols_dict[name] = self._section
-
- def _start_args_section(self, name):
- self._start_symbol_section(self.args, name)
-
- def _start_features_section(self, name):
- self._start_symbol_section(self.features, name)
-
- def _start_section(self, name=None):
- if name in ('Returns', 'Since') and self.has_section(name):
- raise QAPIParseError(self._parser,
- "duplicated '%s' section" % name)
- self._end_section()
- self._section = QAPIDoc.Section(name)
- self.sections.append(self._section)
-
- def _end_section(self):
- if self._section:
- text = self._section.text = self._section.text.strip()
- if self._section.name and (not text or text.isspace()):
- raise QAPIParseError(
- self._parser,
- "empty doc section '%s'" % self._section.name)
- self._section = None
-
- def _append_freeform(self, line):
- match = re.match(r'(@\S+:)', line)
- if match:
- raise QAPIParseError(self._parser,
- "'%s' not allowed in free-form documentation"
- % match.group(1))
- self._section.append(line)
-
- def connect_member(self, member):
- if member.name not in self.args:
- # Undocumented TODO outlaw
- self.args[member.name] = QAPIDoc.ArgSection(member.name)
- self.args[member.name].connect(member)
-
- def check_expr(self, expr):
- if self.has_section('Returns') and 'command' not in expr:
- raise QAPISemError(self.info,
- "'Returns:' is only valid for commands")
-
- def check(self):
- bogus = [name for name, section in self.args.items()
- if not section.member]
- if bogus:
- raise QAPISemError(
- self.info,
- "the following documented members are not in "
- "the declaration: %s" % ", ".join(bogus))
-
-
-class QAPISchemaParser(object):
-
- def __init__(self, fname, previously_included=[], incl_info=None):
- previously_included.append(os.path.abspath(fname))
-
- try:
- if sys.version_info[0] >= 3:
- fp = open(fname, 'r', encoding='utf-8')
- else:
- fp = open(fname, 'r')
- self.src = fp.read()
- except IOError as e:
- raise QAPISemError(incl_info or QAPISourceInfo(None, None, None),
- "can't read %s file '%s': %s"
- % ("include" if incl_info else "schema",
- fname,
- e.strerror))
-
- if self.src == '' or self.src[-1] != '\n':
- self.src += '\n'
- self.cursor = 0
- self.info = QAPISourceInfo(fname, 1, incl_info)
- self.line_pos = 0
- self.exprs = []
- self.docs = []
- self.accept()
- cur_doc = None
-
- while self.tok is not None:
- info = self.info
- if self.tok == '#':
- self.reject_expr_doc(cur_doc)
- cur_doc = self.get_doc(info)
- self.docs.append(cur_doc)
- continue
-
- expr = self.get_expr(False)
- if 'include' in expr:
- self.reject_expr_doc(cur_doc)
- if len(expr) != 1:
- raise QAPISemError(info, "invalid 'include' directive")
- include = expr['include']
- if not isinstance(include, str):
- raise QAPISemError(info,
- "value of 'include' must be a string")
- incl_fname = os.path.join(os.path.dirname(fname),
- include)
- self.exprs.append({'expr': {'include': incl_fname},
- 'info': info})
- exprs_include = self._include(include, info, incl_fname,
- previously_included)
- if exprs_include:
- self.exprs.extend(exprs_include.exprs)
- self.docs.extend(exprs_include.docs)
- elif "pragma" in expr:
- self.reject_expr_doc(cur_doc)
- if len(expr) != 1:
- raise QAPISemError(info, "invalid 'pragma' directive")
- pragma = expr['pragma']
- if not isinstance(pragma, dict):
- raise QAPISemError(
- info, "value of 'pragma' must be an object")
- for name, value in pragma.items():
- self._pragma(name, value, info)
- else:
- expr_elem = {'expr': expr,
- 'info': info}
- if cur_doc:
- if not cur_doc.symbol:
- raise QAPISemError(
- cur_doc.info, "definition documentation required")
- expr_elem['doc'] = cur_doc
- self.exprs.append(expr_elem)
- cur_doc = None
- self.reject_expr_doc(cur_doc)
-
- @staticmethod
- def reject_expr_doc(doc):
- if doc and doc.symbol:
- raise QAPISemError(
- doc.info,
- "documentation for '%s' is not followed by the definition"
- % doc.symbol)
-
- def _include(self, include, info, incl_fname, previously_included):
- incl_abs_fname = os.path.abspath(incl_fname)
- # catch inclusion cycle
- inf = info
- while inf:
- if incl_abs_fname == os.path.abspath(inf.fname):
- raise QAPISemError(info, "inclusion loop for %s" % include)
- inf = inf.parent
-
- # skip multiple include of the same file
- if incl_abs_fname in previously_included:
- return None
-
- return QAPISchemaParser(incl_fname, previously_included, info)
-
- def _pragma(self, name, value, info):
- global doc_required, returns_whitelist, name_case_whitelist
- if name == 'doc-required':
- if not isinstance(value, bool):
- raise QAPISemError(info,
- "pragma 'doc-required' must be boolean")
- doc_required = value
- elif name == 'returns-whitelist':
- if (not isinstance(value, list)
- or any([not isinstance(elt, str) for elt in value])):
- raise QAPISemError(
- info,
- "pragma returns-whitelist must be a list of strings")
- returns_whitelist = value
- elif name == 'name-case-whitelist':
- if (not isinstance(value, list)
- or any([not isinstance(elt, str) for elt in value])):
- raise QAPISemError(
- info,
- "pragma name-case-whitelist must be a list of strings")
- name_case_whitelist = value
- else:
- raise QAPISemError(info, "unknown pragma '%s'" % name)
-
- def accept(self, skip_comment=True):
- while True:
- self.tok = self.src[self.cursor]
- self.pos = self.cursor
- self.cursor += 1
- self.val = None
-
- if self.tok == '#':
- if self.src[self.cursor] == '#':
- # Start of doc comment
- skip_comment = False
- self.cursor = self.src.find('\n', self.cursor)
- if not skip_comment:
- self.val = self.src[self.pos:self.cursor]
- return
- elif self.tok in '{}:,[]':
- return
- elif self.tok == "'":
- # Note: we accept only printable ASCII
- string = ''
- esc = False
- while True:
- ch = self.src[self.cursor]
- self.cursor += 1
- if ch == '\n':
- raise QAPIParseError(self, "missing terminating \"'\"")
- if esc:
- # Note: we recognize only \\ because we have
- # no use for funny characters in strings
- if ch != '\\':
- raise QAPIParseError(self,
- "unknown escape \\%s" % ch)
- esc = False
- elif ch == '\\':
- esc = True
- continue
- elif ch == "'":
- self.val = string
- return
- if ord(ch) < 32 or ord(ch) >= 127:
- raise QAPIParseError(
- self, "funny character in string")
- string += ch
- elif self.src.startswith('true', self.pos):
- self.val = True
- self.cursor += 3
- return
- elif self.src.startswith('false', self.pos):
- self.val = False
- self.cursor += 4
- return
- elif self.tok == '\n':
- if self.cursor == len(self.src):
- self.tok = None
- return
- self.info = self.info.next_line()
- self.line_pos = self.cursor
- elif not self.tok.isspace():
- # Show up to next structural, whitespace or quote
- # character
- match = re.match('[^[\\]{}:,\\s\'"]+',
- self.src[self.cursor-1:])
- raise QAPIParseError(self, "stray '%s'" % match.group(0))
-
- def get_members(self):
- expr = OrderedDict()
- if self.tok == '}':
- self.accept()
- return expr
- if self.tok != "'":
- raise QAPIParseError(self, "expected string or '}'")
- while True:
- key = self.val
- self.accept()
- if self.tok != ':':
- raise QAPIParseError(self, "expected ':'")
- self.accept()
- if key in expr:
- raise QAPIParseError(self, "duplicate key '%s'" % key)
- expr[key] = self.get_expr(True)
- if self.tok == '}':
- self.accept()
- return expr
- if self.tok != ',':
- raise QAPIParseError(self, "expected ',' or '}'")
- self.accept()
- if self.tok != "'":
- raise QAPIParseError(self, "expected string")
-
- def get_values(self):
- expr = []
- if self.tok == ']':
- self.accept()
- return expr
- if self.tok not in "{['tfn":
- raise QAPIParseError(
- self, "expected '{', '[', ']', string, boolean or 'null'")
- while True:
- expr.append(self.get_expr(True))
- if self.tok == ']':
- self.accept()
- return expr
- if self.tok != ',':
- raise QAPIParseError(self, "expected ',' or ']'")
- self.accept()
-
- def get_expr(self, nested):
- if self.tok != '{' and not nested:
- raise QAPIParseError(self, "expected '{'")
- if self.tok == '{':
- self.accept()
- expr = self.get_members()
- elif self.tok == '[':
- self.accept()
- expr = self.get_values()
- elif self.tok in "'tfn":
- expr = self.val
- self.accept()
- else:
- raise QAPIParseError(
- self, "expected '{', '[', string, boolean or 'null'")
- return expr
-
- def get_doc(self, info):
- if self.val != '##':
- raise QAPIParseError(
- self, "junk after '##' at start of documentation comment")
-
- doc = QAPIDoc(self, info)
- self.accept(False)
- while self.tok == '#':
- if self.val.startswith('##'):
- # End of doc comment
- if self.val != '##':
- raise QAPIParseError(
- self,
- "junk after '##' at end of documentation comment")
- doc.end_comment()
- self.accept()
- return doc
- else:
- doc.append(self.val)
- self.accept(False)
-
- raise QAPIParseError(self, "documentation comment must end with '##'")
-
-
-#
-# Check (context-free) schema expression structure
-#
-
-# Names must be letters, numbers, -, and _. They must start with letter,
-# except for downstream extensions which must start with __RFQDN_.
-# Dots are only valid in the downstream extension prefix.
-valid_name = re.compile(r'^(__[a-zA-Z0-9.-]+_)?'
- '[a-zA-Z][a-zA-Z0-9_-]*$')
-
-
-def check_name_is_str(name, info, source):
- if not isinstance(name, str):
- raise QAPISemError(info, "%s requires a string name" % source)
-
-
-def check_name_str(name, info, source,
- allow_optional=False, enum_member=False,
- permit_upper=False):
- global valid_name
- membername = name
-
- if allow_optional and name.startswith('*'):
- membername = name[1:]
- # Enum members can start with a digit, because the generated C
- # code always prefixes it with the enum name
- if enum_member and membername[0].isdigit():
- membername = 'D' + membername
- # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
- # and 'q_obj_*' implicit type names.
- if not valid_name.match(membername) or \
- c_name(membername, False).startswith('q_'):
- raise QAPISemError(info, "%s has an invalid name" % source)
- if not permit_upper and name.lower() != name:
- raise QAPISemError(
- info, "%s uses uppercase in name" % source)
- assert not membername.startswith('*')
-
-
-def check_defn_name_str(name, info, meta):
- check_name_str(name, info, meta, permit_upper=True)
- if name.endswith('Kind') or name.endswith('List'):
- raise QAPISemError(
- info, "%s name should not end in '%s'" % (meta, name[-4:]))
-
-
-def check_if(expr, info, source):
-
- def check_if_str(ifcond, info):
- if not isinstance(ifcond, str):
- raise QAPISemError(
- info,
- "'if' condition of %s must be a string or a list of strings"
- % source)
- if ifcond.strip() == '':
- raise QAPISemError(
- info,
- "'if' condition '%s' of %s makes no sense"
- % (ifcond, source))
-
- ifcond = expr.get('if')
- if ifcond is None:
- return
- if isinstance(ifcond, list):
- if ifcond == []:
- raise QAPISemError(
- info, "'if' condition [] of %s is useless" % source)
- for elt in ifcond:
- check_if_str(elt, info)
- else:
- check_if_str(ifcond, info)
-
-
-def check_type(value, info, source,
- allow_array=False, allow_dict=False):
- if value is None:
- return
-
- # Array type
- if isinstance(value, list):
- if not allow_array:
- raise QAPISemError(info, "%s cannot be an array" % source)
- if len(value) != 1 or not isinstance(value[0], str):
- raise QAPISemError(info,
- "%s: array type must contain single type name" %
- source)
- return
-
- # Type name
- if isinstance(value, str):
- return
-
- # Anonymous type
-
- if not allow_dict:
- raise QAPISemError(info, "%s should be a type name" % source)
-
- if not isinstance(value, OrderedDict):
- raise QAPISemError(info,
- "%s should be an object or type name" % source)
-
- permit_upper = allow_dict in name_case_whitelist
-
- # value is a dictionary, check that each member is okay
- for (key, arg) in value.items():
- key_source = "%s member '%s'" % (source, key)
- check_name_str(key, info, key_source,
- allow_optional=True, permit_upper=permit_upper)
- if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
- raise QAPISemError(info, "%s uses reserved name" % key_source)
- check_keys(arg, info, key_source, ['type'], ['if'])
- check_if(arg, info, key_source)
- normalize_if(arg)
- check_type(arg['type'], info, key_source, allow_array=True)
-
-
-def check_command(expr, info):
- args = expr.get('data')
- rets = expr.get('returns')
- boxed = expr.get('boxed', False)
-
- if boxed and args is None:
- raise QAPISemError(info, "'boxed': true requires 'data'")
- check_type(args, info, "'data'", allow_dict=not boxed)
- check_type(rets, info, "'returns'", allow_array=True)
-
-
-def check_event(expr, info):
- args = expr.get('data')
- boxed = expr.get('boxed', False)
-
- if boxed and args is None:
- raise QAPISemError(info, "'boxed': true requires 'data'")
- check_type(args, info, "'data'", allow_dict=not boxed)
-
-
-def check_union(expr, info):
- name = expr['union']
- base = expr.get('base')
- discriminator = expr.get('discriminator')
- members = expr['data']
-
- if discriminator is None: # simple union
- if base is not None:
- raise QAPISemError(info, "'base' requires 'discriminator'")
- else: # flat union
- check_type(base, info, "'base'", allow_dict=name)
- if not base:
- raise QAPISemError(info, "'discriminator' requires 'base'")
- check_name_is_str(discriminator, info, "'discriminator'")
-
- for (key, value) in members.items():
- source = "'data' member '%s'" % key
- check_name_str(key, info, source)
- check_keys(value, info, source, ['type'], ['if'])
- check_if(value, info, source)
- normalize_if(value)
- check_type(value['type'], info, source, allow_array=not base)
-
-
-def check_alternate(expr, info):
- members = expr['data']
-
- if len(members) == 0:
- raise QAPISemError(info, "'data' must not be empty")
- for (key, value) in members.items():
- source = "'data' member '%s'" % key
- check_name_str(key, info, source)
- check_keys(value, info, source, ['type'], ['if'])
- check_if(value, info, source)
- normalize_if(value)
- check_type(value['type'], info, source)
-
-
-def check_enum(expr, info):
- name = expr['enum']
- members = expr['data']
- prefix = expr.get('prefix')
-
- if not isinstance(members, list):
- raise QAPISemError(info, "'data' must be an array")
- if prefix is not None and not isinstance(prefix, str):
- raise QAPISemError(info, "'prefix' must be a string")
-
- permit_upper = name in name_case_whitelist
-
- for member in members:
- source = "'data' member"
- check_keys(member, info, source, ['name'], ['if'])
- check_name_is_str(member['name'], info, source)
- source = "%s '%s'" % (source, member['name'])
- check_name_str(member['name'], info, source,
- enum_member=True, permit_upper=permit_upper)
- check_if(member, info, source)
- normalize_if(member)
-
-
-def check_struct(expr, info):
- name = expr['struct']
- members = expr['data']
- features = expr.get('features')
-
- check_type(members, info, "'data'", allow_dict=name)
- check_type(expr.get('base'), info, "'base'")
-
- if features:
- if not isinstance(features, list):
- raise QAPISemError(info, "'features' must be an array")
- for f in features:
- source = "'features' member"
- assert isinstance(f, dict)
- check_keys(f, info, source, ['name'], ['if'])
- check_name_is_str(f['name'], info, source)
- source = "%s '%s'" % (source, f['name'])
- check_name_str(f['name'], info, source)
- check_if(f, info, source)
- normalize_if(f)
-
-
-def check_keys(value, info, source, required, optional):
-
- def pprint(elems):
- return ', '.join("'" + e + "'" for e in sorted(elems))
-
- missing = set(required) - set(value)
- if missing:
- raise QAPISemError(
- info,
- "%s misses key%s %s"
- % (source, 's' if len(missing) > 1 else '',
- pprint(missing)))
- allowed = set(required + optional)
- unknown = set(value) - allowed
- if unknown:
- raise QAPISemError(
- info,
- "%s has unknown key%s %s\nValid keys are %s."
- % (source, 's' if len(unknown) > 1 else '',
- pprint(unknown), pprint(allowed)))
-
-
-def check_flags(expr, info):
- for key in ['gen', 'success-response']:
- if key in expr and expr[key] is not False:
- raise QAPISemError(
- info, "flag '%s' may only use false value" % key)
- for key in ['boxed', 'allow-oob', 'allow-preconfig']:
- if key in expr and expr[key] is not True:
- raise QAPISemError(
- info, "flag '%s' may only use true value" % key)
-
-
-def normalize_enum(expr):
- if isinstance(expr['data'], list):
- expr['data'] = [m if isinstance(m, dict) else {'name': m}
- for m in expr['data']]
-
-
-def normalize_members(members):
- if isinstance(members, OrderedDict):
- for key, arg in members.items():
- if isinstance(arg, dict):
- continue
- members[key] = {'type': arg}
-
-
-def normalize_features(features):
- if isinstance(features, list):
- features[:] = [f if isinstance(f, dict) else {'name': f}
- for f in features]
-
-
-def normalize_if(expr):
- ifcond = expr.get('if')
- if isinstance(ifcond, str):
- expr['if'] = [ifcond]
-
-
-def check_exprs(exprs):
- for expr_elem in exprs:
- expr = expr_elem['expr']
- info = expr_elem['info']
- doc = expr_elem.get('doc')
-
- if 'include' in expr:
- continue
-
- if 'enum' in expr:
- meta = 'enum'
- elif 'union' in expr:
- meta = 'union'
- elif 'alternate' in expr:
- meta = 'alternate'
- elif 'struct' in expr:
- meta = 'struct'
- elif 'command' in expr:
- meta = 'command'
- elif 'event' in expr:
- meta = 'event'
- else:
- raise QAPISemError(info, "expression is missing metatype")
-
- name = expr[meta]
- check_name_is_str(name, info, "'%s'" % meta)
- info.set_defn(meta, name)
- check_defn_name_str(name, info, meta)
-
- if doc:
- if doc.symbol != name:
- raise QAPISemError(
- info, "documentation comment is for '%s'" % doc.symbol)
- doc.check_expr(expr)
- elif doc_required:
- raise QAPISemError(info,
- "documentation comment required")
-
- if meta == 'enum':
- check_keys(expr, info, meta,
- ['enum', 'data'], ['if', 'prefix'])
- normalize_enum(expr)
- check_enum(expr, info)
- elif meta == 'union':
- check_keys(expr, info, meta,
- ['union', 'data'],
- ['base', 'discriminator', 'if'])
- normalize_members(expr.get('base'))
- normalize_members(expr['data'])
- check_union(expr, info)
- elif meta == 'alternate':
- check_keys(expr, info, meta,
- ['alternate', 'data'], ['if'])
- normalize_members(expr['data'])
- check_alternate(expr, info)
- elif meta == 'struct':
- check_keys(expr, info, meta,
- ['struct', 'data'], ['base', 'if', 'features'])
- normalize_members(expr['data'])
- normalize_features(expr.get('features'))
- check_struct(expr, info)
- elif meta == 'command':
- check_keys(expr, info, meta,
- ['command'],
- ['data', 'returns', 'boxed', 'if',
- 'gen', 'success-response', 'allow-oob',
- 'allow-preconfig'])
- normalize_members(expr.get('data'))
- check_command(expr, info)
- elif meta == 'event':
- check_keys(expr, info, meta,
- ['event'], ['data', 'boxed', 'if'])
- normalize_members(expr.get('data'))
- check_event(expr, info)
- else:
- assert False, 'unexpected meta type'
-
- normalize_if(expr)
- check_if(expr, info, meta)
- check_flags(expr, info)
-
- return exprs
-
-
-#
-# Schema compiler frontend
-# TODO catching name collisions in generated code would be nice
-#
-
-class QAPISchemaEntity(object):
- meta = None
-
- def __init__(self, name, info, doc, ifcond=None):
- assert name is None or isinstance(name, str)
- self.name = name
- self._module = None
- # For explicitly defined entities, info points to the (explicit)
- # definition. For builtins (and their arrays), info is None.
- # For implicitly defined entities, info points to a place that
- # triggered the implicit definition (there may be more than one
- # such place).
- self.info = info
- self.doc = doc
- self._ifcond = ifcond or []
- self._checked = False
-
- def c_name(self):
- return c_name(self.name)
-
- def check(self, schema):
- assert not self._checked
- if self.info:
- self._module = os.path.relpath(self.info.fname,
- os.path.dirname(schema.fname))
- self._checked = True
-
- @property
- def ifcond(self):
- assert self._checked
- return self._ifcond
-
- @property
- def module(self):
- assert self._checked
- return self._module
-
- def is_implicit(self):
- return not self.info
-
- def visit(self, visitor):
- assert self._checked
-
- def describe(self):
- assert self.meta
- return "%s '%s'" % (self.meta, self.name)
-
-
-class QAPISchemaVisitor(object):
- def visit_begin(self, schema):
- pass
-
- def visit_end(self):
- pass
-
- def visit_module(self, fname):
- pass
-
- def visit_needed(self, entity):
- # Default to visiting everything
- return True
-
- def visit_include(self, fname, info):
- pass
-
- def visit_builtin_type(self, name, info, json_type):
- pass
-
- def visit_enum_type(self, name, info, ifcond, members, prefix):
- pass
-
- def visit_array_type(self, name, info, ifcond, element_type):
- pass
-
- def visit_object_type(self, name, info, ifcond, base, members, variants,
- features):
- pass
-
- def visit_object_type_flat(self, name, info, ifcond, members, variants,
- features):
- pass
-
- def visit_alternate_type(self, name, info, ifcond, variants):
- pass
-
- def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
- success_response, boxed, allow_oob, allow_preconfig):
- pass
-
- def visit_event(self, name, info, ifcond, arg_type, boxed):
- pass
-
-
-class QAPISchemaInclude(QAPISchemaEntity):
-
- def __init__(self, fname, info):
- QAPISchemaEntity.__init__(self, None, info, None)
- self.fname = fname
-
- def visit(self, visitor):
- QAPISchemaEntity.visit(self, visitor)
- visitor.visit_include(self.fname, self.info)
-
-
-class QAPISchemaType(QAPISchemaEntity):
- # Return the C type for common use.
- # For the types we commonly box, this is a pointer type.
- def c_type(self):
- pass
-
- # Return the C type to be used in a parameter list.
- def c_param_type(self):
- return self.c_type()
-
- # Return the C type to be used where we suppress boxing.
- def c_unboxed_type(self):
- return self.c_type()
-
- def json_type(self):
- pass
-
- def alternate_qtype(self):
- json2qtype = {
- 'null': 'QTYPE_QNULL',
- 'string': 'QTYPE_QSTRING',
- 'number': 'QTYPE_QNUM',
- 'int': 'QTYPE_QNUM',
- 'boolean': 'QTYPE_QBOOL',
- 'object': 'QTYPE_QDICT'
- }
- return json2qtype.get(self.json_type())
-
- def doc_type(self):
- if self.is_implicit():
- return None
- return self.name
-
- def describe(self):
- assert self.meta
- return "%s type '%s'" % (self.meta, self.name)
-
-
-class QAPISchemaBuiltinType(QAPISchemaType):
- meta = 'built-in'
-
- def __init__(self, name, json_type, c_type):
- QAPISchemaType.__init__(self, name, None, None)
- assert not c_type or isinstance(c_type, str)
- assert json_type in ('string', 'number', 'int', 'boolean', 'null',
- 'value')
- self._json_type_name = json_type
- self._c_type_name = c_type
-
- def c_name(self):
- return self.name
-
- def c_type(self):
- return self._c_type_name
-
- def c_param_type(self):
- if self.name == 'str':
- return 'const ' + self._c_type_name
- return self._c_type_name
-
- def json_type(self):
- return self._json_type_name
-
- def doc_type(self):
- return self.json_type()
-
- def visit(self, visitor):
- QAPISchemaType.visit(self, visitor)
- visitor.visit_builtin_type(self.name, self.info, self.json_type())
-
-
-class QAPISchemaEnumType(QAPISchemaType):
- meta = 'enum'
-
- def __init__(self, name, info, doc, ifcond, members, prefix):
- QAPISchemaType.__init__(self, name, info, doc, ifcond)
- for m in members:
- assert isinstance(m, QAPISchemaEnumMember)
- m.set_defined_in(name)
- assert prefix is None or isinstance(prefix, str)
- self.members = members
- self.prefix = prefix
-
- def check(self, schema):
- QAPISchemaType.check(self, schema)
- seen = {}
- for m in self.members:
- m.check_clash(self.info, seen)
- if self.doc:
- self.doc.connect_member(m)
-
- def is_implicit(self):
- # See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
- return self.name.endswith('Kind') or self.name == 'QType'
-
- def c_type(self):
- return c_name(self.name)
-
- def member_names(self):
- return [m.name for m in self.members]
-
- def json_type(self):
- return 'string'
-
- def visit(self, visitor):
- QAPISchemaType.visit(self, visitor)
- visitor.visit_enum_type(self.name, self.info, self.ifcond,
- self.members, self.prefix)
-
-
-class QAPISchemaArrayType(QAPISchemaType):
- meta = 'array'
-
- def __init__(self, name, info, element_type):
- QAPISchemaType.__init__(self, name, info, None, None)
- assert isinstance(element_type, str)
- self._element_type_name = element_type
- self.element_type = None
-
- def check(self, schema):
- QAPISchemaType.check(self, schema)
- self.element_type = schema.resolve_type(
- self._element_type_name, self.info,
- self.info and self.info.defn_meta)
- assert not isinstance(self.element_type, QAPISchemaArrayType)
-
- @property
- def ifcond(self):
- assert self._checked
- return self.element_type.ifcond
-
- @property
- def module(self):
- assert self._checked
- return self.element_type.module
-
- def is_implicit(self):
- return True
-
- def c_type(self):
- return c_name(self.name) + pointer_suffix
-
- def json_type(self):
- return 'array'
-
- def doc_type(self):
- elt_doc_type = self.element_type.doc_type()
- if not elt_doc_type:
- return None
- return 'array of ' + elt_doc_type
-
- def visit(self, visitor):
- QAPISchemaType.visit(self, visitor)
- visitor.visit_array_type(self.name, self.info, self.ifcond,
- self.element_type)
-
- def describe(self):
- assert self.meta
- return "%s type ['%s']" % (self.meta, self._element_type_name)
-
-
-class QAPISchemaObjectType(QAPISchemaType):
- def __init__(self, name, info, doc, ifcond,
- base, local_members, variants, features):
- # struct has local_members, optional base, and no variants
- # flat union has base, variants, and no local_members
- # simple union has local_members, variants, and no base
- QAPISchemaType.__init__(self, name, info, doc, ifcond)
- self.meta = 'union' if variants else 'struct'
- assert base is None or isinstance(base, str)
- for m in local_members:
- assert isinstance(m, QAPISchemaObjectTypeMember)
- m.set_defined_in(name)
- if variants is not None:
- assert isinstance(variants, QAPISchemaObjectTypeVariants)
- variants.set_defined_in(name)
- for f in features:
- assert isinstance(f, QAPISchemaFeature)
- f.set_defined_in(name)
- self._base_name = base
- self.base = None
- self.local_members = local_members
- self.variants = variants
- self.members = None
- self.features = features
-
- def check(self, schema):
- # This calls another type T's .check() exactly when the C
- # struct emitted by gen_object() contains that T's C struct
- # (pointers don't count).
- if self.members is not None:
- # A previous .check() completed: nothing to do
- return
- if self._checked:
- # Recursed: C struct contains itself
- raise QAPISemError(self.info,
- "object %s contains itself" % self.name)
-
- QAPISchemaType.check(self, schema)
- assert self._checked and self.members is None
-
- seen = OrderedDict()
- if self._base_name:
- self.base = schema.resolve_type(self._base_name, self.info,
- "'base'")
- if (not isinstance(self.base, QAPISchemaObjectType)
- or self.base.variants):
- raise QAPISemError(
- self.info,
- "'base' requires a struct type, %s isn't"
- % self.base.describe())
- self.base.check(schema)
- self.base.check_clash(self.info, seen)
- for m in self.local_members:
- m.check(schema)
- m.check_clash(self.info, seen)
- if self.doc:
- self.doc.connect_member(m)
- members = seen.values()
-
- if self.variants:
- self.variants.check(schema, seen)
- self.variants.check_clash(self.info, seen)
-
- # Features are in a name space separate from members
- seen = {}
- for f in self.features:
- f.check_clash(self.info, seen)
-
- if self.doc:
- self.doc.check()
-
- self.members = members # mark completed
-
- # Check that the members of this type do not cause duplicate JSON members,
- # and update seen to track the members seen so far. Report any errors
- # on behalf of info, which is not necessarily self.info
- def check_clash(self, info, seen):
- assert self._checked
- assert not self.variants # not implemented
- for m in self.members:
- m.check_clash(info, seen)
-
- @property
- def ifcond(self):
- assert self._checked
- if isinstance(self._ifcond, QAPISchemaType):
- # Simple union wrapper type inherits from wrapped type;
- # see _make_implicit_object_type()
- return self._ifcond.ifcond
- return self._ifcond
-
- def is_implicit(self):
- # See QAPISchema._make_implicit_object_type(), as well as
- # _def_predefineds()
- return self.name.startswith('q_')
-
- def is_empty(self):
- assert self.members is not None
- return not self.members and not self.variants
-
- def c_name(self):
- assert self.name != 'q_empty'
- return QAPISchemaType.c_name(self)
-
- def c_type(self):
- assert not self.is_implicit()
- return c_name(self.name) + pointer_suffix
-
- def c_unboxed_type(self):
- return c_name(self.name)
-
- def json_type(self):
- return 'object'
-
- def visit(self, visitor):
- QAPISchemaType.visit(self, visitor)
- visitor.visit_object_type(self.name, self.info, self.ifcond,
- self.base, self.local_members, self.variants,
- self.features)
- visitor.visit_object_type_flat(self.name, self.info, self.ifcond,
- self.members, self.variants,
- self.features)
-
-
-class QAPISchemaMember(object):
- """ Represents object members, enum members and features """
- role = 'member'
-
- def __init__(self, name, info, ifcond=None):
- assert isinstance(name, str)
- self.name = name
- self.info = info
- self.ifcond = ifcond or []
- self.defined_in = None
-
- def set_defined_in(self, name):
- assert not self.defined_in
- self.defined_in = name
-
- def check_clash(self, info, seen):
- cname = c_name(self.name)
- if cname in seen:
- raise QAPISemError(
- info,
- "%s collides with %s"
- % (self.describe(info), seen[cname].describe(info)))
- seen[cname] = self
-
- def describe(self, info):
- role = self.role
- defined_in = self.defined_in
- assert defined_in
-
- if defined_in.startswith('q_obj_'):
- # See QAPISchema._make_implicit_object_type() - reverse the
- # mapping there to create a nice human-readable description
- defined_in = defined_in[6:]
- if defined_in.endswith('-arg'):
- # Implicit type created for a command's dict 'data'
- assert role == 'member'
- role = 'parameter'
- elif defined_in.endswith('-base'):
- # Implicit type created for a flat union's dict 'base'
- role = 'base ' + role
- else:
- # Implicit type created for a simple union's branch
- assert defined_in.endswith('-wrapper')
- # Unreachable and not implemented
- assert False
- elif defined_in.endswith('Kind'):
- # See QAPISchema._make_implicit_enum_type()
- # Implicit enum created for simple union's branches
- assert role == 'value'
- role = 'branch'
- elif defined_in != info.defn_name:
- return "%s '%s' of type '%s'" % (role, self.name, defined_in)
- return "%s '%s'" % (role, self.name)
-
-
-class QAPISchemaEnumMember(QAPISchemaMember):
- role = 'value'
-
-
-class QAPISchemaFeature(QAPISchemaMember):
- role = 'feature'
-
-
-class QAPISchemaObjectTypeMember(QAPISchemaMember):
- def __init__(self, name, info, typ, optional, ifcond=None):
- QAPISchemaMember.__init__(self, name, info, ifcond)
- assert isinstance(typ, str)
- assert isinstance(optional, bool)
- self._type_name = typ
- self.type = None
- self.optional = optional
-
- def check(self, schema):
- assert self.defined_in
- self.type = schema.resolve_type(self._type_name, self.info,
- self.describe)
-
-
-class QAPISchemaObjectTypeVariants(object):
- def __init__(self, tag_name, info, tag_member, variants):
- # Flat unions pass tag_name but not tag_member.
- # Simple unions and alternates pass tag_member but not tag_name.
- # After check(), tag_member is always set, and tag_name remains
- # a reliable witness of being used by a flat union.
- assert bool(tag_member) != bool(tag_name)
- assert (isinstance(tag_name, str) or
- isinstance(tag_member, QAPISchemaObjectTypeMember))
- for v in variants:
- assert isinstance(v, QAPISchemaObjectTypeVariant)
- self._tag_name = tag_name
- self.info = info
- self.tag_member = tag_member
- self.variants = variants
-
- def set_defined_in(self, name):
- for v in self.variants:
- v.set_defined_in(name)
-
- def check(self, schema, seen):
- if not self.tag_member: # flat union
- self.tag_member = seen.get(c_name(self._tag_name))
- base = "'base'"
- # Pointing to the base type when not implicit would be
- # nice, but we don't know it here
- if not self.tag_member or self._tag_name != self.tag_member.name:
- raise QAPISemError(
- self.info,
- "discriminator '%s' is not a member of %s"
- % (self._tag_name, base))
- # Here we do:
- base_type = schema.lookup_type(self.tag_member.defined_in)
- assert base_type
- if not base_type.is_implicit():
- base = "base type '%s'" % self.tag_member.defined_in
- if not isinstance(self.tag_member.type, QAPISchemaEnumType):
- raise QAPISemError(
- self.info,
- "discriminator member '%s' of %s must be of enum type"
- % (self._tag_name, base))
- if self.tag_member.optional:
- raise QAPISemError(
- self.info,
- "discriminator member '%s' of %s must not be optional"
- % (self._tag_name, base))
- if self.tag_member.ifcond:
- raise QAPISemError(
- self.info,
- "discriminator member '%s' of %s must not be conditional"
- % (self._tag_name, base))
- else: # simple union
- assert isinstance(self.tag_member.type, QAPISchemaEnumType)
- assert not self.tag_member.optional
- assert self.tag_member.ifcond == []
- if self._tag_name: # flat union
- # branches that are not explicitly covered get an empty type
- cases = set([v.name for v in self.variants])
- for m in self.tag_member.type.members:
- if m.name not in cases:
- v = QAPISchemaObjectTypeVariant(m.name, self.info,
- 'q_empty', m.ifcond)
- v.set_defined_in(self.tag_member.defined_in)
- self.variants.append(v)
- if not self.variants:
- raise QAPISemError(self.info, "union has no branches")
- for v in self.variants:
- v.check(schema)
- # Union names must match enum values; alternate names are
- # checked separately. Use 'seen' to tell the two apart.
- if seen:
- if v.name not in self.tag_member.type.member_names():
- raise QAPISemError(
- self.info,
- "branch '%s' is not a value of %s"
- % (v.name, self.tag_member.type.describe()))
- if (not isinstance(v.type, QAPISchemaObjectType)
- or v.type.variants):
- raise QAPISemError(
- self.info,
- "%s cannot use %s"
- % (v.describe(self.info), v.type.describe()))
- v.type.check(schema)
-
- def check_clash(self, info, seen):
- for v in self.variants:
- # Reset seen map for each variant, since qapi names from one
- # branch do not affect another branch
- v.type.check_clash(info, dict(seen))
-
-
-class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
- role = 'branch'
-
- def __init__(self, name, info, typ, ifcond=None):
- QAPISchemaObjectTypeMember.__init__(self, name, info, typ,
- False, ifcond)
-
-
-class QAPISchemaAlternateType(QAPISchemaType):
- meta = 'alternate'
-
- def __init__(self, name, info, doc, ifcond, variants):
- QAPISchemaType.__init__(self, name, info, doc, ifcond)
- assert isinstance(variants, QAPISchemaObjectTypeVariants)
- assert variants.tag_member
- variants.set_defined_in(name)
- variants.tag_member.set_defined_in(self.name)
- self.variants = variants
-
- def check(self, schema):
- QAPISchemaType.check(self, schema)
- self.variants.tag_member.check(schema)
- # Not calling self.variants.check_clash(), because there's nothing
- # to clash with
- self.variants.check(schema, {})
- # Alternate branch names have no relation to the tag enum values;
- # so we have to check for potential name collisions ourselves.
- seen = {}
- types_seen = {}
- for v in self.variants.variants:
- v.check_clash(self.info, seen)
- qtype = v.type.alternate_qtype()
- if not qtype:
- raise QAPISemError(
- self.info,
- "%s cannot use %s"
- % (v.describe(self.info), v.type.describe()))
- conflicting = set([qtype])
- if qtype == 'QTYPE_QSTRING':
- if isinstance(v.type, QAPISchemaEnumType):
- for m in v.type.members:
- if m.name in ['on', 'off']:
- conflicting.add('QTYPE_QBOOL')
- if re.match(r'[-+0-9.]', m.name):
- # lazy, could be tightened
- conflicting.add('QTYPE_QNUM')
- else:
- conflicting.add('QTYPE_QNUM')
- conflicting.add('QTYPE_QBOOL')
- for qt in conflicting:
- if qt in types_seen:
- raise QAPISemError(
- self.info,
- "%s can't be distinguished from '%s'"
- % (v.describe(self.info), types_seen[qt]))
- types_seen[qt] = v.name
- if self.doc:
- self.doc.connect_member(v)
- if self.doc:
- self.doc.check()
-
- def c_type(self):
- return c_name(self.name) + pointer_suffix
-
- def json_type(self):
- return 'value'
-
- def visit(self, visitor):
- QAPISchemaType.visit(self, visitor)
- visitor.visit_alternate_type(self.name, self.info, self.ifcond,
- self.variants)
-
-
-class QAPISchemaCommand(QAPISchemaEntity):
- meta = 'command'
-
- def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
- gen, success_response, boxed, allow_oob, allow_preconfig):
- QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
- assert not arg_type or isinstance(arg_type, str)
- assert not ret_type or isinstance(ret_type, str)
- self._arg_type_name = arg_type
- self.arg_type = None
- self._ret_type_name = ret_type
- self.ret_type = None
- self.gen = gen
- self.success_response = success_response
- self.boxed = boxed
- self.allow_oob = allow_oob
- self.allow_preconfig = allow_preconfig
-
- def check(self, schema):
- QAPISchemaEntity.check(self, schema)
- if self._arg_type_name:
- self.arg_type = schema.resolve_type(
- self._arg_type_name, self.info, "command's 'data'")
- if not isinstance(self.arg_type, QAPISchemaObjectType):
- raise QAPISemError(
- self.info,
- "command's 'data' cannot take %s"
- % self.arg_type.describe())
- if self.arg_type.variants and not self.boxed:
- raise QAPISemError(
- self.info,
- "command's 'data' can take %s only with 'boxed': true"
- % self.arg_type.describe())
- if self._ret_type_name:
- self.ret_type = schema.resolve_type(
- self._ret_type_name, self.info, "command's 'returns'")
- if self.name not in returns_whitelist:
- if not (isinstance(self.ret_type, QAPISchemaObjectType)
- or (isinstance(self.ret_type, QAPISchemaArrayType)
- and isinstance(self.ret_type.element_type,
- QAPISchemaObjectType))):
- raise QAPISemError(
- self.info,
- "command's 'returns' cannot take %s"
- % self.ret_type.describe())
-
- def visit(self, visitor):
- QAPISchemaEntity.visit(self, visitor)
- visitor.visit_command(self.name, self.info, self.ifcond,
- self.arg_type, self.ret_type,
- self.gen, self.success_response,
- self.boxed, self.allow_oob,
- self.allow_preconfig)
-
-
-class QAPISchemaEvent(QAPISchemaEntity):
- meta = 'event'
-
- def __init__(self, name, info, doc, ifcond, arg_type, boxed):
- QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
- assert not arg_type or isinstance(arg_type, str)
- self._arg_type_name = arg_type
- self.arg_type = None
- self.boxed = boxed
-
- def check(self, schema):
- QAPISchemaEntity.check(self, schema)
- if self._arg_type_name:
- self.arg_type = schema.resolve_type(
- self._arg_type_name, self.info, "event's 'data'")
- if not isinstance(self.arg_type, QAPISchemaObjectType):
- raise QAPISemError(
- self.info,
- "event's 'data' cannot take %s"
- % self.arg_type.describe())
- if self.arg_type.variants and not self.boxed:
- raise QAPISemError(
- self.info,
- "event's 'data' can take %s only with 'boxed': true"
- % self.arg_type.describe())
-
- def visit(self, visitor):
- QAPISchemaEntity.visit(self, visitor)
- visitor.visit_event(self.name, self.info, self.ifcond,
- self.arg_type, self.boxed)
-
-
-class QAPISchema(object):
- def __init__(self, fname):
- self.fname = fname
- parser = QAPISchemaParser(fname)
- exprs = check_exprs(parser.exprs)
- self.docs = parser.docs
- self._entity_list = []
- self._entity_dict = {}
- self._predefining = True
- self._def_predefineds()
- self._predefining = False
- self._def_exprs(exprs)
- self.check()
-
- def _def_entity(self, ent):
- # Only the predefined types are allowed to not have info
- assert ent.info or self._predefining
- self._entity_list.append(ent)
- if ent.name is None:
- return
- # TODO reject names that differ only in '_' vs. '.' vs. '-',
- # because they're liable to clash in generated C.
- other_ent = self._entity_dict.get(ent.name)
- if other_ent:
- if other_ent.info:
- where = QAPIError(other_ent.info, None, "previous definition")
- raise QAPISemError(
- ent.info,
- "'%s' is already defined\n%s" % (ent.name, where))
- raise QAPISemError(
- ent.info, "%s is already defined" % other_ent.describe())
- self._entity_dict[ent.name] = ent
-
- def lookup_entity(self, name, typ=None):
- ent = self._entity_dict.get(name)
- if typ and not isinstance(ent, typ):
- return None
- return ent
-
- def lookup_type(self, name):
- return self.lookup_entity(name, QAPISchemaType)
-
- def resolve_type(self, name, info, what):
- typ = self.lookup_type(name)
- if not typ:
- if callable(what):
- what = what(info)
- raise QAPISemError(
- info, "%s uses unknown type '%s'" % (what, name))
- return typ
-
- def _def_include(self, expr, info, doc):
- include = expr['include']
- assert doc is None
- main_info = info
- while main_info.parent:
- main_info = main_info.parent
- fname = os.path.relpath(include, os.path.dirname(main_info.fname))
- self._def_entity(QAPISchemaInclude(fname, info))
-
- def _def_builtin_type(self, name, json_type, c_type):
- self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
- # Instantiating only the arrays that are actually used would
- # be nice, but we can't as long as their generated code
- # (qapi-builtin-types.[ch]) may be shared by some other
- # schema.
- self._make_array_type(name, None)
-
- def _def_predefineds(self):
- for t in [('str', 'string', 'char' + pointer_suffix),
- ('number', 'number', 'double'),
- ('int', 'int', 'int64_t'),
- ('int8', 'int', 'int8_t'),
- ('int16', 'int', 'int16_t'),
- ('int32', 'int', 'int32_t'),
- ('int64', 'int', 'int64_t'),
- ('uint8', 'int', 'uint8_t'),
- ('uint16', 'int', 'uint16_t'),
- ('uint32', 'int', 'uint32_t'),
- ('uint64', 'int', 'uint64_t'),
- ('size', 'int', 'uint64_t'),
- ('bool', 'boolean', 'bool'),
- ('any', 'value', 'QObject' + pointer_suffix),
- ('null', 'null', 'QNull' + pointer_suffix)]:
- self._def_builtin_type(*t)
- self.the_empty_object_type = QAPISchemaObjectType(
- 'q_empty', None, None, None, None, [], None, [])
- self._def_entity(self.the_empty_object_type)
-
- qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
- 'qbool']
- qtype_values = self._make_enum_members(
- [{'name': n} for n in qtypes], None)
-
- self._def_entity(QAPISchemaEnumType('QType', None, None, None,
- qtype_values, 'QTYPE'))
-
- def _make_features(self, features, info):
- return [QAPISchemaFeature(f['name'], info, f.get('if'))
- for f in features]
-
- def _make_enum_members(self, values, info):
- return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
- for v in values]
-
- def _make_implicit_enum_type(self, name, info, ifcond, values):
- # See also QAPISchemaObjectTypeMember.describe()
- name = name + 'Kind' # reserved by check_defn_name_str()
- self._def_entity(QAPISchemaEnumType(
- name, info, None, ifcond, self._make_enum_members(values, info),
- None))
- return name
-
- def _make_array_type(self, element_type, info):
- name = element_type + 'List' # reserved by check_defn_name_str()
- if not self.lookup_type(name):
- self._def_entity(QAPISchemaArrayType(name, info, element_type))
- return name
-
- def _make_implicit_object_type(self, name, info, doc, ifcond,
- role, members):
- if not members:
- return None
- # See also QAPISchemaObjectTypeMember.describe()
- name = 'q_obj_%s-%s' % (name, role)
- typ = self.lookup_entity(name, QAPISchemaObjectType)
- if typ:
- # The implicit object type has multiple users. This can
- # happen only for simple unions' implicit wrapper types.
- # Its ifcond should be the disjunction of its user's
- # ifconds. Not implemented. Instead, we always pass the
- # wrapped type's ifcond, which is trivially the same for all
- # users. It's also necessary for the wrapper to compile.
- # But it's not tight: the disjunction need not imply it. We
- # may end up compiling useless wrapper types.
- # TODO kill simple unions or implement the disjunction
- assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access
- else:
- self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
- None, members, None, []))
- return name
-
- def _def_enum_type(self, expr, info, doc):
- name = expr['enum']
- data = expr['data']
- prefix = expr.get('prefix')
- ifcond = expr.get('if')
- self._def_entity(QAPISchemaEnumType(
- name, info, doc, ifcond,
- self._make_enum_members(data, info), prefix))
-
- def _make_member(self, name, typ, ifcond, info):
- optional = False
- if name.startswith('*'):
- name = name[1:]
- optional = True
- if isinstance(typ, list):
- assert len(typ) == 1
- typ = self._make_array_type(typ[0], info)
- return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond)
-
- def _make_members(self, data, info):
- return [self._make_member(key, value['type'], value.get('if'), info)
- for (key, value) in data.items()]
-
- def _def_struct_type(self, expr, info, doc):
- name = expr['struct']
- base = expr.get('base')
- data = expr['data']
- ifcond = expr.get('if')
- features = expr.get('features', [])
- self._def_entity(QAPISchemaObjectType(
- name, info, doc, ifcond, base,
- self._make_members(data, info),
- None,
- self._make_features(features, info)))
-
- def _make_variant(self, case, typ, ifcond, info):
- return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
-
- def _make_simple_variant(self, case, typ, ifcond, info):
- if isinstance(typ, list):
- assert len(typ) == 1
- typ = self._make_array_type(typ[0], info)
- typ = self._make_implicit_object_type(
- typ, info, None, self.lookup_type(typ),
- 'wrapper', [self._make_member('data', typ, None, info)])
- return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
-
- def _def_union_type(self, expr, info, doc):
- name = expr['union']
- data = expr['data']
- base = expr.get('base')
- ifcond = expr.get('if')
- tag_name = expr.get('discriminator')
- tag_member = None
- if isinstance(base, dict):
- base = self._make_implicit_object_type(
- name, info, doc, ifcond,
- 'base', self._make_members(base, info))
- if tag_name:
- variants = [self._make_variant(key, value['type'],
- value.get('if'), info)
- for (key, value) in data.items()]
- members = []
- else:
- variants = [self._make_simple_variant(key, value['type'],
- value.get('if'), info)
- for (key, value) in data.items()]
- enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
- typ = self._make_implicit_enum_type(name, info, ifcond, enum)
- tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
- members = [tag_member]
- self._def_entity(
- QAPISchemaObjectType(name, info, doc, ifcond, base, members,
- QAPISchemaObjectTypeVariants(
- tag_name, info, tag_member, variants),
- []))
-
- def _def_alternate_type(self, expr, info, doc):
- name = expr['alternate']
- data = expr['data']
- ifcond = expr.get('if')
- variants = [self._make_variant(key, value['type'], value.get('if'),
- info)
- for (key, value) in data.items()]
- tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
- self._def_entity(
- QAPISchemaAlternateType(name, info, doc, ifcond,
- QAPISchemaObjectTypeVariants(
- None, info, tag_member, variants)))
-
- def _def_command(self, expr, info, doc):
- name = expr['command']
- data = expr.get('data')
- rets = expr.get('returns')
- gen = expr.get('gen', True)
- success_response = expr.get('success-response', True)
- boxed = expr.get('boxed', False)
- allow_oob = expr.get('allow-oob', False)
- allow_preconfig = expr.get('allow-preconfig', False)
- ifcond = expr.get('if')
- if isinstance(data, OrderedDict):
- data = self._make_implicit_object_type(
- name, info, doc, ifcond, 'arg', self._make_members(data, info))
- if isinstance(rets, list):
- assert len(rets) == 1
- rets = self._make_array_type(rets[0], info)
- self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
- gen, success_response,
- boxed, allow_oob, allow_preconfig))
-
- def _def_event(self, expr, info, doc):
- name = expr['event']
- data = expr.get('data')
- boxed = expr.get('boxed', False)
- ifcond = expr.get('if')
- if isinstance(data, OrderedDict):
- data = self._make_implicit_object_type(
- name, info, doc, ifcond, 'arg', self._make_members(data, info))
- self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
-
- def _def_exprs(self, exprs):
- for expr_elem in exprs:
- expr = expr_elem['expr']
- info = expr_elem['info']
- doc = expr_elem.get('doc')
- if 'enum' in expr:
- self._def_enum_type(expr, info, doc)
- elif 'struct' in expr:
- self._def_struct_type(expr, info, doc)
- elif 'union' in expr:
- self._def_union_type(expr, info, doc)
- elif 'alternate' in expr:
- self._def_alternate_type(expr, info, doc)
- elif 'command' in expr:
- self._def_command(expr, info, doc)
- elif 'event' in expr:
- self._def_event(expr, info, doc)
- elif 'include' in expr:
- self._def_include(expr, info, doc)
- else:
- assert False
-
- def check(self):
- for ent in self._entity_list:
- ent.check(self)
-
- def visit(self, visitor):
- visitor.visit_begin(self)
- module = None
- visitor.visit_module(module)
- for entity in self._entity_list:
- if visitor.visit_needed(entity):
- if entity.module != module:
- module = entity.module
- visitor.visit_module(module)
- entity.visit(visitor)
- visitor.visit_end()
-
-
-#
-# Code generation helpers
-#
-
-def camel_case(name):
- new_name = ''
- first = True
- for ch in name:
- if ch in ['_', '-']:
- first = True
- elif first:
- new_name += ch.upper()
- first = False
- else:
- new_name += ch.lower()
- return new_name
# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
@@ -2220,81 +175,6 @@ def gen_endif(ifcond):
return ret
-def _wrap_ifcond(ifcond, before, after):
- if before == after:
- return after # suppress empty #if ... #endif
-
- assert after.startswith(before)
- out = before
- added = after[len(before):]
- if added[0] == '\n':
- out += '\n'
- added = added[1:]
- out += gen_if(ifcond)
- out += added
- out += gen_endif(ifcond)
- return out
-
-
-def gen_enum_lookup(name, members, prefix=None):
- ret = mcgen('''
-
-const QEnumLookup %(c_name)s_lookup = {
- .array = (const char *const[]) {
-''',
- c_name=c_name(name))
- for m in members:
- ret += gen_if(m.ifcond)
- index = c_enum_const(name, m.name, prefix)
- ret += mcgen('''
- [%(index)s] = "%(name)s",
-''',
- index=index, name=m.name)
- ret += gen_endif(m.ifcond)
-
- ret += mcgen('''
- },
- .size = %(max_index)s
-};
-''',
- max_index=c_enum_const(name, '_MAX', prefix))
- return ret
-
-
-def gen_enum(name, members, prefix=None):
- # append automatically generated _MAX value
- enum_members = members + [QAPISchemaEnumMember('_MAX', None)]
-
- ret = mcgen('''
-
-typedef enum %(c_name)s {
-''',
- c_name=c_name(name))
-
- for m in enum_members:
- ret += gen_if(m.ifcond)
- ret += mcgen('''
- %(c_enum)s,
-''',
- c_enum=c_enum_const(name, m.name, prefix))
- ret += gen_endif(m.ifcond)
-
- ret += mcgen('''
-} %(c_name)s;
-''',
- c_name=c_name(name))
-
- ret += mcgen('''
-
-#define %(c_name)s_str(val) \\
- qapi_enum_lookup(&%(c_name)s_lookup, (val))
-
-extern const QEnumLookup %(c_name)s_lookup;
-''',
- c_name=c_name(name))
- return ret
-
-
def build_params(arg_type, boxed, extra=None):
ret = ''
sep = ''
@@ -2314,260 +194,3 @@ def build_params(arg_type, boxed, extra=None):
if extra:
ret += sep + extra
return ret if ret else 'void'
-
-
-#
-# Accumulate and write output
-#
-
-class QAPIGen(object):
-
- def __init__(self, fname):
- self.fname = fname
- self._preamble = ''
- self._body = ''
-
- def preamble_add(self, text):
- self._preamble += text
-
- def add(self, text):
- self._body += text
-
- def get_content(self):
- return self._top() + self._preamble + self._body + self._bottom()
-
- def _top(self):
- return ''
-
- def _bottom(self):
- return ''
-
- def write(self, output_dir):
- pathname = os.path.join(output_dir, self.fname)
- dir = os.path.dirname(pathname)
- if dir:
- try:
- os.makedirs(dir)
- except os.error as e:
- if e.errno != errno.EEXIST:
- raise
- fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
- if sys.version_info[0] >= 3:
- f = open(fd, 'r+', encoding='utf-8')
- else:
- f = os.fdopen(fd, 'r+')
- text = self.get_content()
- oldtext = f.read(len(text) + 1)
- if text != oldtext:
- f.seek(0)
- f.truncate(0)
- f.write(text)
- f.close()
-
-
-@contextmanager
-def ifcontext(ifcond, *args):
- """A 'with' statement context manager to wrap with start_if()/end_if()
-
- *args: any number of QAPIGenCCode
-
- Example::
-
- with ifcontext(ifcond, self._genh, self._genc):
- modify self._genh and self._genc ...
-
- Is equivalent to calling::
-
- self._genh.start_if(ifcond)
- self._genc.start_if(ifcond)
- modify self._genh and self._genc ...
- self._genh.end_if()
- self._genc.end_if()
- """
- for arg in args:
- arg.start_if(ifcond)
- yield
- for arg in args:
- arg.end_if()
-
-
-class QAPIGenCCode(QAPIGen):
-
- def __init__(self, fname):
- QAPIGen.__init__(self, fname)
- self._start_if = None
-
- def start_if(self, ifcond):
- assert self._start_if is None
- self._start_if = (ifcond, self._body, self._preamble)
-
- def end_if(self):
- assert self._start_if
- self._wrap_ifcond()
- self._start_if = None
-
- def _wrap_ifcond(self):
- self._body = _wrap_ifcond(self._start_if[0],
- self._start_if[1], self._body)
- self._preamble = _wrap_ifcond(self._start_if[0],
- self._start_if[2], self._preamble)
-
- def get_content(self):
- assert self._start_if is None
- return QAPIGen.get_content(self)
-
-
-class QAPIGenC(QAPIGenCCode):
-
- def __init__(self, fname, blurb, pydoc):
- QAPIGenCCode.__init__(self, fname)
- self._blurb = blurb
- self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
- re.MULTILINE))
-
- def _top(self):
- return mcgen('''
-/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
-
-/*
-%(blurb)s
- *
- * %(copyright)s
- *
- * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
- * See the COPYING.LIB file in the top-level directory.
- */
-
-''',
- blurb=self._blurb, copyright=self._copyright)
-
- def _bottom(self):
- return mcgen('''
-
-/* Dummy declaration to prevent empty .o file */
-char qapi_dummy_%(name)s;
-''',
- name=c_fname(self.fname))
-
-
-class QAPIGenH(QAPIGenC):
-
- def _top(self):
- return QAPIGenC._top(self) + guardstart(self.fname)
-
- def _bottom(self):
- return guardend(self.fname)
-
-
-class QAPIGenDoc(QAPIGen):
-
- def _top(self):
- return (QAPIGen._top(self)
- + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
-
-
-class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
-
- def __init__(self, prefix, what, blurb, pydoc):
- self._prefix = prefix
- self._what = what
- self._genc = QAPIGenC(self._prefix + self._what + '.c',
- blurb, pydoc)
- self._genh = QAPIGenH(self._prefix + self._what + '.h',
- blurb, pydoc)
-
- def write(self, output_dir):
- self._genc.write(output_dir)
- self._genh.write(output_dir)
-
-
-class QAPISchemaModularCVisitor(QAPISchemaVisitor):
-
- def __init__(self, prefix, what, blurb, pydoc):
- self._prefix = prefix
- self._what = what
- self._blurb = blurb
- self._pydoc = pydoc
- self._genc = None
- self._genh = None
- self._module = {}
- self._main_module = None
-
- @staticmethod
- def _is_user_module(name):
- return name and not name.startswith('./')
-
- @staticmethod
- def _is_builtin_module(name):
- return not name
-
- def _module_dirname(self, what, name):
- if self._is_user_module(name):
- return os.path.dirname(name)
- return ''
-
- def _module_basename(self, what, name):
- ret = '' if self._is_builtin_module(name) else self._prefix
- if self._is_user_module(name):
- basename = os.path.basename(name)
- ret += what
- if name != self._main_module:
- ret += '-' + os.path.splitext(basename)[0]
- else:
- name = name[2:] if name else 'builtin'
- ret += re.sub(r'-', '-' + name + '-', what)
- return ret
-
- def _module_filename(self, what, name):
- return os.path.join(self._module_dirname(what, name),
- self._module_basename(what, name))
-
- def _add_module(self, name, blurb):
- basename = self._module_filename(self._what, name)
- genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
- genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
- self._module[name] = (genc, genh)
- self._set_module(name)
-
- def _add_user_module(self, name, blurb):
- assert self._is_user_module(name)
- if self._main_module is None:
- self._main_module = name
- self._add_module(name, blurb)
-
- def _add_system_module(self, name, blurb):
- self._add_module(name and './' + name, blurb)
-
- def _set_module(self, name):
- self._genc, self._genh = self._module[name]
-
- def write(self, output_dir, opt_builtins=False):
- for name in self._module:
- if self._is_builtin_module(name) and not opt_builtins:
- continue
- (genc, genh) = self._module[name]
- genc.write(output_dir)
- genh.write(output_dir)
-
- def _begin_user_module(self, name):
- pass
-
- def visit_module(self, name):
- if name in self._module:
- self._set_module(name)
- elif self._is_builtin_module(name):
- # The built-in module has not been created. No code may
- # be generated.
- self._genc = None
- self._genh = None
- else:
- self._add_user_module(name, self._blurb)
- self._begin_user_module(name)
-
- def visit_include(self, name, info):
- relname = os.path.relpath(self._module_filename(self._what, name),
- os.path.dirname(self._genh.fname))
- self._genh.preamble_add(mcgen('''
-#include "%(relname)s.h"
-''',
- relname=relname))
diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 5fc0fc7e06..6d5726cf6e 100755..100644
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# QAPI texi generator
#
# This work is licensed under the terms of the GNU LGPL, version 2+.
@@ -7,7 +6,8 @@
from __future__ import print_function
import re
-import qapi.common
+from qapi.gen import QAPIGenDoc, QAPISchemaVisitor
+
MSG_FMT = """
@deftypefn {type} {{}} {name}
@@ -216,10 +216,10 @@ def texi_entity(doc, what, ifcond, base=None, variants=None,
+ texi_sections(doc, ifcond))
-class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
+class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
def __init__(self, prefix):
self._prefix = prefix
- self._gen = qapi.common.QAPIGenDoc(self._prefix + 'qapi-doc.texi')
+ self._gen = QAPIGenDoc(self._prefix + 'qapi-doc.texi')
self.cur_doc = None
def write(self, output_dir):
@@ -249,12 +249,14 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
body=texi_entity(doc, 'Members', ifcond)))
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
- success_response, boxed, allow_oob, allow_preconfig):
+ success_response, boxed, allow_oob, allow_preconfig,
+ features):
doc = self.cur_doc
if boxed:
body = texi_body(doc)
body += ('\n@b{Arguments:} the members of @code{%s}\n'
% arg_type.name)
+ body += texi_features(doc)
body += texi_sections(doc, ifcond)
else:
body = texi_entity(doc, 'Arguments', ifcond)
@@ -283,8 +285,6 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
def gen_doc(schema, output_dir, prefix):
- if not qapi.common.doc_required:
- return
vis = QAPISchemaGenDocVisitor(prefix)
vis.visit_begin(schema)
for doc in schema.docs:
diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
new file mode 100644
index 0000000000..b9f3751bea
--- /dev/null
+++ b/scripts/qapi/error.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+#
+# QAPI error classes
+#
+# Copyright (c) 2017-2019 Red Hat Inc.
+#
+# Authors:
+# Markus Armbruster <armbru@redhat.com>
+# Marc-André Lureau <marcandre.lureau@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+
+
+class QAPIError(Exception):
+ def __init__(self, info, col, msg):
+ Exception.__init__(self)
+ self.info = info
+ self.col = col
+ self.msg = msg
+
+ def __str__(self):
+ loc = str(self.info)
+ if self.col is not None:
+ assert self.info.line is not None
+ loc += ':%s' % self.col
+ return loc + ': ' + self.msg
+
+
+class QAPIParseError(QAPIError):
+ def __init__(self, parser, msg):
+ col = 1
+ for ch in parser.src[parser.line_pos:parser.pos]:
+ if ch == '\t':
+ col = (col + 7) % 8 + 1
+ else:
+ col += 1
+ QAPIError.__init__(self, parser.info, col, msg)
+
+
+class QAPISemError(QAPIError):
+ def __init__(self, info, msg):
+ QAPIError.__init__(self, info, None, msg)
diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py
index 7308e8e589..10fc509fa9 100644
--- a/scripts/qapi/events.py
+++ b/scripts/qapi/events.py
@@ -13,6 +13,9 @@ See the COPYING file in the top-level directory.
"""
from qapi.common import *
+from qapi.gen import QAPISchemaModularCVisitor, ifcontext
+from qapi.schema import QAPISchemaEnumMember
+from qapi.types import gen_enum, gen_enum_lookup
def build_event_send_proto(name, arg_type, boxed):
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
new file mode 100644
index 0000000000..7c7394f835
--- /dev/null
+++ b/scripts/qapi/expr.py
@@ -0,0 +1,383 @@
+# -*- coding: utf-8 -*-
+#
+# Check (context-free) QAPI schema expression structure
+#
+# Copyright IBM, Corp. 2011
+# Copyright (c) 2013-2019 Red Hat Inc.
+#
+# Authors:
+# Anthony Liguori <aliguori@us.ibm.com>
+# Markus Armbruster <armbru@redhat.com>
+# Eric Blake <eblake@redhat.com>
+# Marc-André Lureau <marcandre.lureau@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+
+import re
+from collections import OrderedDict
+from qapi.common import c_name
+from qapi.error import QAPISemError
+
+
+# Names must be letters, numbers, -, and _. They must start with letter,
+# except for downstream extensions which must start with __RFQDN_.
+# Dots are only valid in the downstream extension prefix.
+valid_name = re.compile(r'^(__[a-zA-Z0-9.-]+_)?'
+ '[a-zA-Z][a-zA-Z0-9_-]*$')
+
+
+def check_name_is_str(name, info, source):
+ if not isinstance(name, str):
+ raise QAPISemError(info, "%s requires a string name" % source)
+
+
+def check_name_str(name, info, source,
+ allow_optional=False, enum_member=False,
+ permit_upper=False):
+ global valid_name
+ membername = name
+
+ if allow_optional and name.startswith('*'):
+ membername = name[1:]
+ # Enum members can start with a digit, because the generated C
+ # code always prefixes it with the enum name
+ if enum_member and membername[0].isdigit():
+ membername = 'D' + membername
+ # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
+ # and 'q_obj_*' implicit type names.
+ if not valid_name.match(membername) or \
+ c_name(membername, False).startswith('q_'):
+ raise QAPISemError(info, "%s has an invalid name" % source)
+ if not permit_upper and name.lower() != name:
+ raise QAPISemError(
+ info, "%s uses uppercase in name" % source)
+ assert not membername.startswith('*')
+
+
+def check_defn_name_str(name, info, meta):
+ check_name_str(name, info, meta, permit_upper=True)
+ if name.endswith('Kind') or name.endswith('List'):
+ raise QAPISemError(
+ info, "%s name should not end in '%s'" % (meta, name[-4:]))
+
+
+def check_keys(value, info, source, required, optional):
+
+ def pprint(elems):
+ return ', '.join("'" + e + "'" for e in sorted(elems))
+
+ missing = set(required) - set(value)
+ if missing:
+ raise QAPISemError(
+ info,
+ "%s misses key%s %s"
+ % (source, 's' if len(missing) > 1 else '',
+ pprint(missing)))
+ allowed = set(required + optional)
+ unknown = set(value) - allowed
+ if unknown:
+ raise QAPISemError(
+ info,
+ "%s has unknown key%s %s\nValid keys are %s."
+ % (source, 's' if len(unknown) > 1 else '',
+ pprint(unknown), pprint(allowed)))
+
+
+def check_flags(expr, info):
+ for key in ['gen', 'success-response']:
+ if key in expr and expr[key] is not False:
+ raise QAPISemError(
+ info, "flag '%s' may only use false value" % key)
+ for key in ['boxed', 'allow-oob', 'allow-preconfig']:
+ if key in expr and expr[key] is not True:
+ raise QAPISemError(
+ info, "flag '%s' may only use true value" % key)
+
+
+def normalize_if(expr):
+ ifcond = expr.get('if')
+ if isinstance(ifcond, str):
+ expr['if'] = [ifcond]
+
+
+def check_if(expr, info, source):
+
+ def check_if_str(ifcond, info):
+ if not isinstance(ifcond, str):
+ raise QAPISemError(
+ info,
+ "'if' condition of %s must be a string or a list of strings"
+ % source)
+ if ifcond.strip() == '':
+ raise QAPISemError(
+ info,
+ "'if' condition '%s' of %s makes no sense"
+ % (ifcond, source))
+
+ ifcond = expr.get('if')
+ if ifcond is None:
+ return
+ if isinstance(ifcond, list):
+ if ifcond == []:
+ raise QAPISemError(
+ info, "'if' condition [] of %s is useless" % source)
+ for elt in ifcond:
+ check_if_str(elt, info)
+ else:
+ check_if_str(ifcond, info)
+
+
+def normalize_members(members):
+ if isinstance(members, OrderedDict):
+ for key, arg in members.items():
+ if isinstance(arg, dict):
+ continue
+ members[key] = {'type': arg}
+
+
+def check_type(value, info, source,
+ allow_array=False, allow_dict=False):
+ if value is None:
+ return
+
+ # Array type
+ if isinstance(value, list):
+ if not allow_array:
+ raise QAPISemError(info, "%s cannot be an array" % source)
+ if len(value) != 1 or not isinstance(value[0], str):
+ raise QAPISemError(info,
+ "%s: array type must contain single type name" %
+ source)
+ return
+
+ # Type name
+ if isinstance(value, str):
+ return
+
+ # Anonymous type
+
+ if not allow_dict:
+ raise QAPISemError(info, "%s should be a type name" % source)
+
+ if not isinstance(value, OrderedDict):
+ raise QAPISemError(info,
+ "%s should be an object or type name" % source)
+
+ permit_upper = allow_dict in info.pragma.name_case_whitelist
+
+ # value is a dictionary, check that each member is okay
+ for (key, arg) in value.items():
+ key_source = "%s member '%s'" % (source, key)
+ check_name_str(key, info, key_source,
+ allow_optional=True, permit_upper=permit_upper)
+ if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
+ raise QAPISemError(info, "%s uses reserved name" % key_source)
+ check_keys(arg, info, key_source, ['type'], ['if'])
+ check_if(arg, info, key_source)
+ normalize_if(arg)
+ check_type(arg['type'], info, key_source, allow_array=True)
+
+
+def normalize_features(features):
+ if isinstance(features, list):
+ features[:] = [f if isinstance(f, dict) else {'name': f}
+ for f in features]
+
+
+def check_features(features, info):
+ if features is None:
+ return
+ if not isinstance(features, list):
+ raise QAPISemError(info, "'features' must be an array")
+ for f in features:
+ source = "'features' member"
+ assert isinstance(f, dict)
+ check_keys(f, info, source, ['name'], ['if'])
+ check_name_is_str(f['name'], info, source)
+ source = "%s '%s'" % (source, f['name'])
+ check_name_str(f['name'], info, source)
+ check_if(f, info, source)
+ normalize_if(f)
+
+
+def normalize_enum(expr):
+ if isinstance(expr['data'], list):
+ expr['data'] = [m if isinstance(m, dict) else {'name': m}
+ for m in expr['data']]
+
+
+def check_enum(expr, info):
+ name = expr['enum']
+ members = expr['data']
+ prefix = expr.get('prefix')
+
+ if not isinstance(members, list):
+ raise QAPISemError(info, "'data' must be an array")
+ if prefix is not None and not isinstance(prefix, str):
+ raise QAPISemError(info, "'prefix' must be a string")
+
+ permit_upper = name in info.pragma.name_case_whitelist
+
+ for member in members:
+ source = "'data' member"
+ check_keys(member, info, source, ['name'], ['if'])
+ check_name_is_str(member['name'], info, source)
+ source = "%s '%s'" % (source, member['name'])
+ check_name_str(member['name'], info, source,
+ enum_member=True, permit_upper=permit_upper)
+ check_if(member, info, source)
+ normalize_if(member)
+
+
+def check_struct(expr, info):
+ name = expr['struct']
+ members = expr['data']
+
+ check_type(members, info, "'data'", allow_dict=name)
+ check_type(expr.get('base'), info, "'base'")
+ check_features(expr.get('features'), info)
+
+
+def check_union(expr, info):
+ name = expr['union']
+ base = expr.get('base')
+ discriminator = expr.get('discriminator')
+ members = expr['data']
+
+ if discriminator is None: # simple union
+ if base is not None:
+ raise QAPISemError(info, "'base' requires 'discriminator'")
+ else: # flat union
+ check_type(base, info, "'base'", allow_dict=name)
+ if not base:
+ raise QAPISemError(info, "'discriminator' requires 'base'")
+ check_name_is_str(discriminator, info, "'discriminator'")
+
+ for (key, value) in members.items():
+ source = "'data' member '%s'" % key
+ check_name_str(key, info, source)
+ check_keys(value, info, source, ['type'], ['if'])
+ check_if(value, info, source)
+ normalize_if(value)
+ check_type(value['type'], info, source, allow_array=not base)
+
+
+def check_alternate(expr, info):
+ members = expr['data']
+
+ if len(members) == 0:
+ raise QAPISemError(info, "'data' must not be empty")
+ for (key, value) in members.items():
+ source = "'data' member '%s'" % key
+ check_name_str(key, info, source)
+ check_keys(value, info, source, ['type'], ['if'])
+ check_if(value, info, source)
+ normalize_if(value)
+ check_type(value['type'], info, source)
+
+
+def check_command(expr, info):
+ args = expr.get('data')
+ rets = expr.get('returns')
+ boxed = expr.get('boxed', False)
+
+ if boxed and args is None:
+ raise QAPISemError(info, "'boxed': true requires 'data'")
+ check_type(args, info, "'data'", allow_dict=not boxed)
+ check_type(rets, info, "'returns'", allow_array=True)
+ check_features(expr.get('features'), info)
+
+
+def check_event(expr, info):
+ args = expr.get('data')
+ boxed = expr.get('boxed', False)
+
+ if boxed and args is None:
+ raise QAPISemError(info, "'boxed': true requires 'data'")
+ check_type(args, info, "'data'", allow_dict=not boxed)
+
+
+def check_exprs(exprs):
+ for expr_elem in exprs:
+ expr = expr_elem['expr']
+ info = expr_elem['info']
+ doc = expr_elem.get('doc')
+
+ if 'include' in expr:
+ continue
+
+ if 'enum' in expr:
+ meta = 'enum'
+ elif 'union' in expr:
+ meta = 'union'
+ elif 'alternate' in expr:
+ meta = 'alternate'
+ elif 'struct' in expr:
+ meta = 'struct'
+ elif 'command' in expr:
+ meta = 'command'
+ elif 'event' in expr:
+ meta = 'event'
+ else:
+ raise QAPISemError(info, "expression is missing metatype")
+
+ name = expr[meta]
+ check_name_is_str(name, info, "'%s'" % meta)
+ info.set_defn(meta, name)
+ check_defn_name_str(name, info, meta)
+
+ if doc:
+ if doc.symbol != name:
+ raise QAPISemError(
+ info, "documentation comment is for '%s'" % doc.symbol)
+ doc.check_expr(expr)
+ elif info.pragma.doc_required:
+ raise QAPISemError(info,
+ "documentation comment required")
+
+ if meta == 'enum':
+ check_keys(expr, info, meta,
+ ['enum', 'data'], ['if', 'prefix'])
+ normalize_enum(expr)
+ check_enum(expr, info)
+ elif meta == 'union':
+ check_keys(expr, info, meta,
+ ['union', 'data'],
+ ['base', 'discriminator', 'if'])
+ normalize_members(expr.get('base'))
+ normalize_members(expr['data'])
+ check_union(expr, info)
+ elif meta == 'alternate':
+ check_keys(expr, info, meta,
+ ['alternate', 'data'], ['if'])
+ normalize_members(expr['data'])
+ check_alternate(expr, info)
+ elif meta == 'struct':
+ check_keys(expr, info, meta,
+ ['struct', 'data'], ['base', 'if', 'features'])
+ normalize_members(expr['data'])
+ normalize_features(expr.get('features'))
+ check_struct(expr, info)
+ elif meta == 'command':
+ check_keys(expr, info, meta,
+ ['command'],
+ ['data', 'returns', 'boxed', 'if', 'features',
+ 'gen', 'success-response', 'allow-oob',
+ 'allow-preconfig'])
+ normalize_members(expr.get('data'))
+ normalize_features(expr.get('features'))
+ check_command(expr, info)
+ elif meta == 'event':
+ check_keys(expr, info, meta,
+ ['event'], ['data', 'boxed', 'if'])
+ normalize_members(expr.get('data'))
+ check_event(expr, info)
+ else:
+ assert False, 'unexpected meta type'
+
+ normalize_if(expr)
+ check_if(expr, info, meta)
+ check_flags(expr, info)
+
+ return exprs
diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
new file mode 100644
index 0000000000..112b6d94c5
--- /dev/null
+++ b/scripts/qapi/gen.py
@@ -0,0 +1,291 @@
+# -*- coding: utf-8 -*-
+#
+# QAPI code generation
+#
+# Copyright (c) 2018-2019 Red Hat Inc.
+#
+# Authors:
+# Markus Armbruster <armbru@redhat.com>
+# Marc-André Lureau <marcandre.lureau@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+
+
+import errno
+import os
+import re
+import sys
+from contextlib import contextmanager
+
+from qapi.common import *
+from qapi.schema import QAPISchemaVisitor
+
+
+class QAPIGen(object):
+
+ def __init__(self, fname):
+ self.fname = fname
+ self._preamble = ''
+ self._body = ''
+
+ def preamble_add(self, text):
+ self._preamble += text
+
+ def add(self, text):
+ self._body += text
+
+ def get_content(self):
+ return self._top() + self._preamble + self._body + self._bottom()
+
+ def _top(self):
+ return ''
+
+ def _bottom(self):
+ return ''
+
+ def write(self, output_dir):
+ pathname = os.path.join(output_dir, self.fname)
+ dir = os.path.dirname(pathname)
+ if dir:
+ try:
+ os.makedirs(dir)
+ except os.error as e:
+ if e.errno != errno.EEXIST:
+ raise
+ fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
+ if sys.version_info[0] >= 3:
+ f = open(fd, 'r+', encoding='utf-8')
+ else:
+ f = os.fdopen(fd, 'r+')
+ text = self.get_content()
+ oldtext = f.read(len(text) + 1)
+ if text != oldtext:
+ f.seek(0)
+ f.truncate(0)
+ f.write(text)
+ f.close()
+
+
+def _wrap_ifcond(ifcond, before, after):
+ if before == after:
+ return after # suppress empty #if ... #endif
+
+ assert after.startswith(before)
+ out = before
+ added = after[len(before):]
+ if added[0] == '\n':
+ out += '\n'
+ added = added[1:]
+ out += gen_if(ifcond)
+ out += added
+ out += gen_endif(ifcond)
+ return out
+
+
+class QAPIGenCCode(QAPIGen):
+
+ def __init__(self, fname):
+ QAPIGen.__init__(self, fname)
+ self._start_if = None
+
+ def start_if(self, ifcond):
+ assert self._start_if is None
+ self._start_if = (ifcond, self._body, self._preamble)
+
+ def end_if(self):
+ assert self._start_if
+ self._wrap_ifcond()
+ self._start_if = None
+
+ def _wrap_ifcond(self):
+ self._body = _wrap_ifcond(self._start_if[0],
+ self._start_if[1], self._body)
+ self._preamble = _wrap_ifcond(self._start_if[0],
+ self._start_if[2], self._preamble)
+
+ def get_content(self):
+ assert self._start_if is None
+ return QAPIGen.get_content(self)
+
+
+class QAPIGenC(QAPIGenCCode):
+
+ def __init__(self, fname, blurb, pydoc):
+ QAPIGenCCode.__init__(self, fname)
+ self._blurb = blurb
+ self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
+ re.MULTILINE))
+
+ def _top(self):
+ return mcgen('''
+/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
+
+/*
+%(blurb)s
+ *
+ * %(copyright)s
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ */
+
+''',
+ blurb=self._blurb, copyright=self._copyright)
+
+ def _bottom(self):
+ return mcgen('''
+
+/* Dummy declaration to prevent empty .o file */
+char qapi_dummy_%(name)s;
+''',
+ name=c_fname(self.fname))
+
+
+class QAPIGenH(QAPIGenC):
+
+ def _top(self):
+ return QAPIGenC._top(self) + guardstart(self.fname)
+
+ def _bottom(self):
+ return guardend(self.fname)
+
+
+@contextmanager
+def ifcontext(ifcond, *args):
+ """A 'with' statement context manager to wrap with start_if()/end_if()
+
+ *args: any number of QAPIGenCCode
+
+ Example::
+
+ with ifcontext(ifcond, self._genh, self._genc):
+ modify self._genh and self._genc ...
+
+ Is equivalent to calling::
+
+ self._genh.start_if(ifcond)
+ self._genc.start_if(ifcond)
+ modify self._genh and self._genc ...
+ self._genh.end_if()
+ self._genc.end_if()
+ """
+ for arg in args:
+ arg.start_if(ifcond)
+ yield
+ for arg in args:
+ arg.end_if()
+
+
+class QAPIGenDoc(QAPIGen):
+
+ def _top(self):
+ return (QAPIGen._top(self)
+ + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
+
+
+class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
+
+ def __init__(self, prefix, what, blurb, pydoc):
+ self._prefix = prefix
+ self._what = what
+ self._genc = QAPIGenC(self._prefix + self._what + '.c',
+ blurb, pydoc)
+ self._genh = QAPIGenH(self._prefix + self._what + '.h',
+ blurb, pydoc)
+
+ def write(self, output_dir):
+ self._genc.write(output_dir)
+ self._genh.write(output_dir)
+
+
+class QAPISchemaModularCVisitor(QAPISchemaVisitor):
+
+ def __init__(self, prefix, what, blurb, pydoc):
+ self._prefix = prefix
+ self._what = what
+ self._blurb = blurb
+ self._pydoc = pydoc
+ self._genc = None
+ self._genh = None
+ self._module = {}
+ self._main_module = None
+
+ @staticmethod
+ def _is_user_module(name):
+ return name and not name.startswith('./')
+
+ @staticmethod
+ def _is_builtin_module(name):
+ return not name
+
+ def _module_dirname(self, what, name):
+ if self._is_user_module(name):
+ return os.path.dirname(name)
+ return ''
+
+ def _module_basename(self, what, name):
+ ret = '' if self._is_builtin_module(name) else self._prefix
+ if self._is_user_module(name):
+ basename = os.path.basename(name)
+ ret += what
+ if name != self._main_module:
+ ret += '-' + os.path.splitext(basename)[0]
+ else:
+ name = name[2:] if name else 'builtin'
+ ret += re.sub(r'-', '-' + name + '-', what)
+ return ret
+
+ def _module_filename(self, what, name):
+ return os.path.join(self._module_dirname(what, name),
+ self._module_basename(what, name))
+
+ def _add_module(self, name, blurb):
+ basename = self._module_filename(self._what, name)
+ genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
+ genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
+ self._module[name] = (genc, genh)
+ self._set_module(name)
+
+ def _add_user_module(self, name, blurb):
+ assert self._is_user_module(name)
+ if self._main_module is None:
+ self._main_module = name
+ self._add_module(name, blurb)
+
+ def _add_system_module(self, name, blurb):
+ self._add_module(name and './' + name, blurb)
+
+ def _set_module(self, name):
+ self._genc, self._genh = self._module[name]
+
+ def write(self, output_dir, opt_builtins=False):
+ for name in self._module:
+ if self._is_builtin_module(name) and not opt_builtins:
+ continue
+ (genc, genh) = self._module[name]
+ genc.write(output_dir)
+ genh.write(output_dir)
+
+ def _begin_user_module(self, name):
+ pass
+
+ def visit_module(self, name):
+ if name in self._module:
+ self._set_module(name)
+ elif self._is_builtin_module(name):
+ # The built-in module has not been created. No code may
+ # be generated.
+ self._genc = None
+ self._genh = None
+ else:
+ self._add_user_module(name, self._blurb)
+ self._begin_user_module(name)
+
+ def visit_include(self, name, info):
+ relname = os.path.relpath(self._module_filename(self._what, name),
+ os.path.dirname(self._genh.fname))
+ self._genh.preamble_add(mcgen('''
+#include "%(relname)s.h"
+''',
+ relname=relname))
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index f62cf0a2e1..b3a463dd8b 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -10,7 +10,12 @@ This work is licensed under the terms of the GNU GPL, version 2.
See the COPYING file in the top-level directory.
"""
+import string
+
from qapi.common import *
+from qapi.gen import QAPISchemaMonolithicCVisitor
+from qapi.schema import (QAPISchemaArrayType, QAPISchemaBuiltinType,
+ QAPISchemaType)
def to_qlit(obj, level=0, suppress_first_indent=False):
@@ -206,13 +211,18 @@ const QLitObject %(c_name)s = %(c_string)s;
for m in variants.variants]}, ifcond)
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
- success_response, boxed, allow_oob, allow_preconfig):
+ success_response, boxed, allow_oob, allow_preconfig,
+ features):
arg_type = arg_type or self._schema.the_empty_object_type
ret_type = ret_type or self._schema.the_empty_object_type
obj = {'arg-type': self._use_type(arg_type),
'ret-type': self._use_type(ret_type)}
if allow_oob:
obj['allow-oob'] = allow_oob
+
+ if features:
+ obj['features'] = [(f.name, {'if': f.ifcond}) for f in features]
+
self._gen_qlit(name, 'command', obj, ifcond)
def visit_event(self, name, info, ifcond, arg_type, boxed):
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
new file mode 100644
index 0000000000..e800876ad1
--- /dev/null
+++ b/scripts/qapi/parser.py
@@ -0,0 +1,570 @@
+# -*- coding: utf-8 -*-
+#
+# QAPI schema parser
+#
+# Copyright IBM, Corp. 2011
+# Copyright (c) 2013-2019 Red Hat Inc.
+#
+# Authors:
+# Anthony Liguori <aliguori@us.ibm.com>
+# Markus Armbruster <armbru@redhat.com>
+# Marc-André Lureau <marcandre.lureau@redhat.com>
+# Kevin Wolf <kwolf@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+
+import os
+import re
+import sys
+from collections import OrderedDict
+
+from qapi.error import QAPIParseError, QAPISemError
+from qapi.source import QAPISourceInfo
+
+
+class QAPISchemaParser(object):
+
+ def __init__(self, fname, previously_included=None, incl_info=None):
+ previously_included = previously_included or set()
+ previously_included.add(os.path.abspath(fname))
+
+ try:
+ if sys.version_info[0] >= 3:
+ fp = open(fname, 'r', encoding='utf-8')
+ else:
+ fp = open(fname, 'r')
+ self.src = fp.read()
+ except IOError as e:
+ raise QAPISemError(incl_info or QAPISourceInfo(None, None, None),
+ "can't read %s file '%s': %s"
+ % ("include" if incl_info else "schema",
+ fname,
+ e.strerror))
+
+ if self.src == '' or self.src[-1] != '\n':
+ self.src += '\n'
+ self.cursor = 0
+ self.info = QAPISourceInfo(fname, 1, incl_info)
+ self.line_pos = 0
+ self.exprs = []
+ self.docs = []
+ self.accept()
+ cur_doc = None
+
+ while self.tok is not None:
+ info = self.info
+ if self.tok == '#':
+ self.reject_expr_doc(cur_doc)
+ cur_doc = self.get_doc(info)
+ self.docs.append(cur_doc)
+ continue
+
+ expr = self.get_expr(False)
+ if 'include' in expr:
+ self.reject_expr_doc(cur_doc)
+ if len(expr) != 1:
+ raise QAPISemError(info, "invalid 'include' directive")
+ include = expr['include']
+ if not isinstance(include, str):
+ raise QAPISemError(info,
+ "value of 'include' must be a string")
+ incl_fname = os.path.join(os.path.dirname(fname),
+ include)
+ self.exprs.append({'expr': {'include': incl_fname},
+ 'info': info})
+ exprs_include = self._include(include, info, incl_fname,
+ previously_included)
+ if exprs_include:
+ self.exprs.extend(exprs_include.exprs)
+ self.docs.extend(exprs_include.docs)
+ elif "pragma" in expr:
+ self.reject_expr_doc(cur_doc)
+ if len(expr) != 1:
+ raise QAPISemError(info, "invalid 'pragma' directive")
+ pragma = expr['pragma']
+ if not isinstance(pragma, dict):
+ raise QAPISemError(
+ info, "value of 'pragma' must be an object")
+ for name, value in pragma.items():
+ self._pragma(name, value, info)
+ else:
+ expr_elem = {'expr': expr,
+ 'info': info}
+ if cur_doc:
+ if not cur_doc.symbol:
+ raise QAPISemError(
+ cur_doc.info, "definition documentation required")
+ expr_elem['doc'] = cur_doc
+ self.exprs.append(expr_elem)
+ cur_doc = None
+ self.reject_expr_doc(cur_doc)
+
+ @staticmethod
+ def reject_expr_doc(doc):
+ if doc and doc.symbol:
+ raise QAPISemError(
+ doc.info,
+ "documentation for '%s' is not followed by the definition"
+ % doc.symbol)
+
+ def _include(self, include, info, incl_fname, previously_included):
+ incl_abs_fname = os.path.abspath(incl_fname)
+ # catch inclusion cycle
+ inf = info
+ while inf:
+ if incl_abs_fname == os.path.abspath(inf.fname):
+ raise QAPISemError(info, "inclusion loop for %s" % include)
+ inf = inf.parent
+
+ # skip multiple include of the same file
+ if incl_abs_fname in previously_included:
+ return None
+
+ return QAPISchemaParser(incl_fname, previously_included, info)
+
+ def _pragma(self, name, value, info):
+ if name == 'doc-required':
+ if not isinstance(value, bool):
+ raise QAPISemError(info,
+ "pragma 'doc-required' must be boolean")
+ info.pragma.doc_required = value
+ elif name == 'returns-whitelist':
+ if (not isinstance(value, list)
+ or any([not isinstance(elt, str) for elt in value])):
+ raise QAPISemError(
+ info,
+ "pragma returns-whitelist must be a list of strings")
+ info.pragma.returns_whitelist = value
+ elif name == 'name-case-whitelist':
+ if (not isinstance(value, list)
+ or any([not isinstance(elt, str) for elt in value])):
+ raise QAPISemError(
+ info,
+ "pragma name-case-whitelist must be a list of strings")
+ info.pragma.name_case_whitelist = value
+ else:
+ raise QAPISemError(info, "unknown pragma '%s'" % name)
+
+ def accept(self, skip_comment=True):
+ while True:
+ self.tok = self.src[self.cursor]
+ self.pos = self.cursor
+ self.cursor += 1
+ self.val = None
+
+ if self.tok == '#':
+ if self.src[self.cursor] == '#':
+ # Start of doc comment
+ skip_comment = False
+ self.cursor = self.src.find('\n', self.cursor)
+ if not skip_comment:
+ self.val = self.src[self.pos:self.cursor]
+ return
+ elif self.tok in '{}:,[]':
+ return
+ elif self.tok == "'":
+ # Note: we accept only printable ASCII
+ string = ''
+ esc = False
+ while True:
+ ch = self.src[self.cursor]
+ self.cursor += 1
+ if ch == '\n':
+ raise QAPIParseError(self, "missing terminating \"'\"")
+ if esc:
+ # Note: we recognize only \\ because we have
+ # no use for funny characters in strings
+ if ch != '\\':
+ raise QAPIParseError(self,
+ "unknown escape \\%s" % ch)
+ esc = False
+ elif ch == '\\':
+ esc = True
+ continue
+ elif ch == "'":
+ self.val = string
+ return
+ if ord(ch) < 32 or ord(ch) >= 127:
+ raise QAPIParseError(
+ self, "funny character in string")
+ string += ch
+ elif self.src.startswith('true', self.pos):
+ self.val = True
+ self.cursor += 3
+ return
+ elif self.src.startswith('false', self.pos):
+ self.val = False
+ self.cursor += 4
+ return
+ elif self.tok == '\n':
+ if self.cursor == len(self.src):
+ self.tok = None
+ return
+ self.info = self.info.next_line()
+ self.line_pos = self.cursor
+ elif not self.tok.isspace():
+ # Show up to next structural, whitespace or quote
+ # character
+ match = re.match('[^[\\]{}:,\\s\'"]+',
+ self.src[self.cursor-1:])
+ raise QAPIParseError(self, "stray '%s'" % match.group(0))
+
+ def get_members(self):
+ expr = OrderedDict()
+ if self.tok == '}':
+ self.accept()
+ return expr
+ if self.tok != "'":
+ raise QAPIParseError(self, "expected string or '}'")
+ while True:
+ key = self.val
+ self.accept()
+ if self.tok != ':':
+ raise QAPIParseError(self, "expected ':'")
+ self.accept()
+ if key in expr:
+ raise QAPIParseError(self, "duplicate key '%s'" % key)
+ expr[key] = self.get_expr(True)
+ if self.tok == '}':
+ self.accept()
+ return expr
+ if self.tok != ',':
+ raise QAPIParseError(self, "expected ',' or '}'")
+ self.accept()
+ if self.tok != "'":
+ raise QAPIParseError(self, "expected string")
+
+ def get_values(self):
+ expr = []
+ if self.tok == ']':
+ self.accept()
+ return expr
+ if self.tok not in "{['tfn":
+ raise QAPIParseError(
+ self, "expected '{', '[', ']', string, boolean or 'null'")
+ while True:
+ expr.append(self.get_expr(True))
+ if self.tok == ']':
+ self.accept()
+ return expr
+ if self.tok != ',':
+ raise QAPIParseError(self, "expected ',' or ']'")
+ self.accept()
+
+ def get_expr(self, nested):
+ if self.tok != '{' and not nested:
+ raise QAPIParseError(self, "expected '{'")
+ if self.tok == '{':
+ self.accept()
+ expr = self.get_members()
+ elif self.tok == '[':
+ self.accept()
+ expr = self.get_values()
+ elif self.tok in "'tfn":
+ expr = self.val
+ self.accept()
+ else:
+ raise QAPIParseError(
+ self, "expected '{', '[', string, boolean or 'null'")
+ return expr
+
+ def get_doc(self, info):
+ if self.val != '##':
+ raise QAPIParseError(
+ self, "junk after '##' at start of documentation comment")
+
+ doc = QAPIDoc(self, info)
+ self.accept(False)
+ while self.tok == '#':
+ if self.val.startswith('##'):
+ # End of doc comment
+ if self.val != '##':
+ raise QAPIParseError(
+ self,
+ "junk after '##' at end of documentation comment")
+ doc.end_comment()
+ self.accept()
+ return doc
+ else:
+ doc.append(self.val)
+ self.accept(False)
+
+ raise QAPIParseError(self, "documentation comment must end with '##'")
+
+
+class QAPIDoc(object):
+ """
+ A documentation comment block, either definition or free-form
+
+ Definition documentation blocks consist of
+
+ * a body section: one line naming the definition, followed by an
+ overview (any number of lines)
+
+ * argument sections: a description of each argument (for commands
+ and events) or member (for structs, unions and alternates)
+
+ * features sections: a description of each feature flag
+
+ * additional (non-argument) sections, possibly tagged
+
+ Free-form documentation blocks consist only of a body section.
+ """
+
+ class Section(object):
+ def __init__(self, name=None):
+ # optional section name (argument/member or section name)
+ self.name = name
+ # the list of lines for this section
+ self.text = ''
+
+ def append(self, line):
+ self.text += line.rstrip() + '\n'
+
+ class ArgSection(Section):
+ def __init__(self, name):
+ QAPIDoc.Section.__init__(self, name)
+ self.member = None
+
+ def connect(self, member):
+ self.member = member
+
+ def __init__(self, parser, info):
+ # self._parser is used to report errors with QAPIParseError. The
+ # resulting error position depends on the state of the parser.
+ # It happens to be the beginning of the comment. More or less
+ # servicable, but action at a distance.
+ self._parser = parser
+ self.info = info
+ self.symbol = None
+ self.body = QAPIDoc.Section()
+ # dict mapping parameter name to ArgSection
+ self.args = OrderedDict()
+ self.features = OrderedDict()
+ # a list of Section
+ self.sections = []
+ # the current section
+ self._section = self.body
+ self._append_line = self._append_body_line
+
+ def has_section(self, name):
+ """Return True if we have a section with this name."""
+ for i in self.sections:
+ if i.name == name:
+ return True
+ return False
+
+ def append(self, line):
+ """
+ Parse a comment line and add it to the documentation.
+
+ The way that the line is dealt with depends on which part of
+ the documentation we're parsing right now:
+ * The body section: ._append_line is ._append_body_line
+ * An argument section: ._append_line is ._append_args_line
+ * A features section: ._append_line is ._append_features_line
+ * An additional section: ._append_line is ._append_various_line
+ """
+ line = line[1:]
+ if not line:
+ self._append_freeform(line)
+ return
+
+ if line[0] != ' ':
+ raise QAPIParseError(self._parser, "missing space after #")
+ line = line[1:]
+ self._append_line(line)
+
+ def end_comment(self):
+ self._end_section()
+
+ @staticmethod
+ def _is_section_tag(name):
+ return name in ('Returns:', 'Since:',
+ # those are often singular or plural
+ 'Note:', 'Notes:',
+ 'Example:', 'Examples:',
+ 'TODO:')
+
+ def _append_body_line(self, line):
+ """
+ Process a line of documentation text in the body section.
+
+ If this a symbol line and it is the section's first line, this
+ is a definition documentation block for that symbol.
+
+ If it's a definition documentation block, another symbol line
+ begins the argument section for the argument named by it, and
+ a section tag begins an additional section. Start that
+ section and append the line to it.
+
+ Else, append the line to the current section.
+ """
+ name = line.split(' ', 1)[0]
+ # FIXME not nice: things like '# @foo:' and '# @foo: ' aren't
+ # recognized, and get silently treated as ordinary text
+ if not self.symbol and not self.body.text and line.startswith('@'):
+ if not line.endswith(':'):
+ raise QAPIParseError(self._parser, "line should end with ':'")
+ self.symbol = line[1:-1]
+ # FIXME invalid names other than the empty string aren't flagged
+ if not self.symbol:
+ raise QAPIParseError(self._parser, "invalid name")
+ elif self.symbol:
+ # This is a definition documentation block
+ if name.startswith('@') and name.endswith(':'):
+ self._append_line = self._append_args_line
+ self._append_args_line(line)
+ elif line == 'Features:':
+ self._append_line = self._append_features_line
+ elif self._is_section_tag(name):
+ self._append_line = self._append_various_line
+ self._append_various_line(line)
+ else:
+ self._append_freeform(line.strip())
+ else:
+ # This is a free-form documentation block
+ self._append_freeform(line.strip())
+
+ def _append_args_line(self, line):
+ """
+ Process a line of documentation text in an argument section.
+
+ A symbol line begins the next argument section, a section tag
+ section or a non-indented line after a blank line begins an
+ additional section. Start that section and append the line to
+ it.
+
+ Else, append the line to the current section.
+
+ """
+ name = line.split(' ', 1)[0]
+
+ if name.startswith('@') and name.endswith(':'):
+ line = line[len(name)+1:]
+ self._start_args_section(name[1:-1])
+ elif self._is_section_tag(name):
+ self._append_line = self._append_various_line
+ self._append_various_line(line)
+ return
+ elif (self._section.text.endswith('\n\n')
+ and line and not line[0].isspace()):
+ if line == 'Features:':
+ self._append_line = self._append_features_line
+ else:
+ self._start_section()
+ self._append_line = self._append_various_line
+ self._append_various_line(line)
+ return
+
+ self._append_freeform(line.strip())
+
+ def _append_features_line(self, line):
+ name = line.split(' ', 1)[0]
+
+ if name.startswith('@') and name.endswith(':'):
+ line = line[len(name)+1:]
+ self._start_features_section(name[1:-1])
+ elif self._is_section_tag(name):
+ self._append_line = self._append_various_line
+ self._append_various_line(line)
+ return
+ elif (self._section.text.endswith('\n\n')
+ and line and not line[0].isspace()):
+ self._start_section()
+ self._append_line = self._append_various_line
+ self._append_various_line(line)
+ return
+
+ self._append_freeform(line.strip())
+
+ def _append_various_line(self, line):
+ """
+ Process a line of documentation text in an additional section.
+
+ A symbol line is an error.
+
+ A section tag begins an additional section. Start that
+ section and append the line to it.
+
+ Else, append the line to the current section.
+ """
+ name = line.split(' ', 1)[0]
+
+ if name.startswith('@') and name.endswith(':'):
+ raise QAPIParseError(self._parser,
+ "'%s' can't follow '%s' section"
+ % (name, self.sections[0].name))
+ elif self._is_section_tag(name):
+ line = line[len(name)+1:]
+ self._start_section(name[:-1])
+
+ if (not self._section.name or
+ not self._section.name.startswith('Example')):
+ line = line.strip()
+
+ self._append_freeform(line)
+
+ def _start_symbol_section(self, symbols_dict, name):
+ # FIXME invalid names other than the empty string aren't flagged
+ if not name:
+ raise QAPIParseError(self._parser, "invalid parameter name")
+ if name in symbols_dict:
+ raise QAPIParseError(self._parser,
+ "'%s' parameter name duplicated" % name)
+ assert not self.sections
+ self._end_section()
+ self._section = QAPIDoc.ArgSection(name)
+ symbols_dict[name] = self._section
+
+ def _start_args_section(self, name):
+ self._start_symbol_section(self.args, name)
+
+ def _start_features_section(self, name):
+ self._start_symbol_section(self.features, name)
+
+ def _start_section(self, name=None):
+ if name in ('Returns', 'Since') and self.has_section(name):
+ raise QAPIParseError(self._parser,
+ "duplicated '%s' section" % name)
+ self._end_section()
+ self._section = QAPIDoc.Section(name)
+ self.sections.append(self._section)
+
+ def _end_section(self):
+ if self._section:
+ text = self._section.text = self._section.text.strip()
+ if self._section.name and (not text or text.isspace()):
+ raise QAPIParseError(
+ self._parser,
+ "empty doc section '%s'" % self._section.name)
+ self._section = None
+
+ def _append_freeform(self, line):
+ match = re.match(r'(@\S+:)', line)
+ if match:
+ raise QAPIParseError(self._parser,
+ "'%s' not allowed in free-form documentation"
+ % match.group(1))
+ self._section.append(line)
+
+ def connect_member(self, member):
+ if member.name not in self.args:
+ # Undocumented TODO outlaw
+ self.args[member.name] = QAPIDoc.ArgSection(member.name)
+ self.args[member.name].connect(member)
+
+ def check_expr(self, expr):
+ if self.has_section('Returns') and 'command' not in expr:
+ raise QAPISemError(self.info,
+ "'Returns:' is only valid for commands")
+
+ def check(self):
+ bogus = [name for name, section in self.args.items()
+ if not section.member]
+ if bogus:
+ raise QAPISemError(
+ self.info,
+ "the following documented members are not in "
+ "the declaration: %s" % ", ".join(bogus))
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
new file mode 100644
index 0000000000..f7d68a35f4
--- /dev/null
+++ b/scripts/qapi/schema.py
@@ -0,0 +1,1057 @@
+# -*- coding: utf-8 -*-
+#
+# QAPI schema internal representation
+#
+# Copyright (c) 2015-2019 Red Hat Inc.
+#
+# Authors:
+# Markus Armbruster <armbru@redhat.com>
+# Eric Blake <eblake@redhat.com>
+# Marc-André Lureau <marcandre.lureau@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+
+# TODO catching name collisions in generated code would be nice
+
+import os
+import re
+from collections import OrderedDict
+
+from qapi.common import c_name, pointer_suffix
+from qapi.error import QAPIError, QAPIParseError, QAPISemError
+from qapi.expr import check_exprs
+from qapi.parser import QAPISchemaParser
+
+
+class QAPISchemaEntity(object):
+ meta = None
+
+ def __init__(self, name, info, doc, ifcond=None):
+ assert name is None or isinstance(name, str)
+ self.name = name
+ self._module = None
+ # For explicitly defined entities, info points to the (explicit)
+ # definition. For builtins (and their arrays), info is None.
+ # For implicitly defined entities, info points to a place that
+ # triggered the implicit definition (there may be more than one
+ # such place).
+ self.info = info
+ self.doc = doc
+ self._ifcond = ifcond or []
+ self._checked = False
+
+ def c_name(self):
+ return c_name(self.name)
+
+ def check(self, schema):
+ assert not self._checked
+ if self.info:
+ self._module = os.path.relpath(self.info.fname,
+ os.path.dirname(schema.fname))
+ self._checked = True
+
+ @property
+ def ifcond(self):
+ assert self._checked
+ return self._ifcond
+
+ @property
+ def module(self):
+ assert self._checked
+ return self._module
+
+ def is_implicit(self):
+ return not self.info
+
+ def visit(self, visitor):
+ assert self._checked
+
+ def describe(self):
+ assert self.meta
+ return "%s '%s'" % (self.meta, self.name)
+
+
+class QAPISchemaVisitor(object):
+ def visit_begin(self, schema):
+ pass
+
+ def visit_end(self):
+ pass
+
+ def visit_module(self, fname):
+ pass
+
+ def visit_needed(self, entity):
+ # Default to visiting everything
+ return True
+
+ def visit_include(self, fname, info):
+ pass
+
+ def visit_builtin_type(self, name, info, json_type):
+ pass
+
+ def visit_enum_type(self, name, info, ifcond, members, prefix):
+ pass
+
+ def visit_array_type(self, name, info, ifcond, element_type):
+ pass
+
+ def visit_object_type(self, name, info, ifcond, base, members, variants,
+ features):
+ pass
+
+ def visit_object_type_flat(self, name, info, ifcond, members, variants,
+ features):
+ pass
+
+ def visit_alternate_type(self, name, info, ifcond, variants):
+ pass
+
+ def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
+ success_response, boxed, allow_oob, allow_preconfig,
+ features):
+ pass
+
+ def visit_event(self, name, info, ifcond, arg_type, boxed):
+ pass
+
+
+class QAPISchemaInclude(QAPISchemaEntity):
+
+ def __init__(self, fname, info):
+ QAPISchemaEntity.__init__(self, None, info, None)
+ self.fname = fname
+
+ def visit(self, visitor):
+ QAPISchemaEntity.visit(self, visitor)
+ visitor.visit_include(self.fname, self.info)
+
+
+class QAPISchemaType(QAPISchemaEntity):
+ # Return the C type for common use.
+ # For the types we commonly box, this is a pointer type.
+ def c_type(self):
+ pass
+
+ # Return the C type to be used in a parameter list.
+ def c_param_type(self):
+ return self.c_type()
+
+ # Return the C type to be used where we suppress boxing.
+ def c_unboxed_type(self):
+ return self.c_type()
+
+ def json_type(self):
+ pass
+
+ def alternate_qtype(self):
+ json2qtype = {
+ 'null': 'QTYPE_QNULL',
+ 'string': 'QTYPE_QSTRING',
+ 'number': 'QTYPE_QNUM',
+ 'int': 'QTYPE_QNUM',
+ 'boolean': 'QTYPE_QBOOL',
+ 'object': 'QTYPE_QDICT'
+ }
+ return json2qtype.get(self.json_type())
+
+ def doc_type(self):
+ if self.is_implicit():
+ return None
+ return self.name
+
+ def describe(self):
+ assert self.meta
+ return "%s type '%s'" % (self.meta, self.name)
+
+
+class QAPISchemaBuiltinType(QAPISchemaType):
+ meta = 'built-in'
+
+ def __init__(self, name, json_type, c_type):
+ QAPISchemaType.__init__(self, name, None, None)
+ assert not c_type or isinstance(c_type, str)
+ assert json_type in ('string', 'number', 'int', 'boolean', 'null',
+ 'value')
+ self._json_type_name = json_type
+ self._c_type_name = c_type
+
+ def c_name(self):
+ return self.name
+
+ def c_type(self):
+ return self._c_type_name
+
+ def c_param_type(self):
+ if self.name == 'str':
+ return 'const ' + self._c_type_name
+ return self._c_type_name
+
+ def json_type(self):
+ return self._json_type_name
+
+ def doc_type(self):
+ return self.json_type()
+
+ def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
+ visitor.visit_builtin_type(self.name, self.info, self.json_type())
+
+
+class QAPISchemaEnumType(QAPISchemaType):
+ meta = 'enum'
+
+ def __init__(self, name, info, doc, ifcond, members, prefix):
+ QAPISchemaType.__init__(self, name, info, doc, ifcond)
+ for m in members:
+ assert isinstance(m, QAPISchemaEnumMember)
+ m.set_defined_in(name)
+ assert prefix is None or isinstance(prefix, str)
+ self.members = members
+ self.prefix = prefix
+
+ def check(self, schema):
+ QAPISchemaType.check(self, schema)
+ seen = {}
+ for m in self.members:
+ m.check_clash(self.info, seen)
+ if self.doc:
+ self.doc.connect_member(m)
+
+ def is_implicit(self):
+ # See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
+ return self.name.endswith('Kind') or self.name == 'QType'
+
+ def c_type(self):
+ return c_name(self.name)
+
+ def member_names(self):
+ return [m.name for m in self.members]
+
+ def json_type(self):
+ return 'string'
+
+ def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
+ visitor.visit_enum_type(self.name, self.info, self.ifcond,
+ self.members, self.prefix)
+
+
+class QAPISchemaArrayType(QAPISchemaType):
+ meta = 'array'
+
+ def __init__(self, name, info, element_type):
+ QAPISchemaType.__init__(self, name, info, None, None)
+ assert isinstance(element_type, str)
+ self._element_type_name = element_type
+ self.element_type = None
+
+ def check(self, schema):
+ QAPISchemaType.check(self, schema)
+ self.element_type = schema.resolve_type(
+ self._element_type_name, self.info,
+ self.info and self.info.defn_meta)
+ assert not isinstance(self.element_type, QAPISchemaArrayType)
+
+ @property
+ def ifcond(self):
+ assert self._checked
+ return self.element_type.ifcond
+
+ @property
+ def module(self):
+ assert self._checked
+ return self.element_type.module
+
+ def is_implicit(self):
+ return True
+
+ def c_type(self):
+ return c_name(self.name) + pointer_suffix
+
+ def json_type(self):
+ return 'array'
+
+ def doc_type(self):
+ elt_doc_type = self.element_type.doc_type()
+ if not elt_doc_type:
+ return None
+ return 'array of ' + elt_doc_type
+
+ def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
+ visitor.visit_array_type(self.name, self.info, self.ifcond,
+ self.element_type)
+
+ def describe(self):
+ assert self.meta
+ return "%s type ['%s']" % (self.meta, self._element_type_name)
+
+
+class QAPISchemaObjectType(QAPISchemaType):
+ def __init__(self, name, info, doc, ifcond,
+ base, local_members, variants, features):
+ # struct has local_members, optional base, and no variants
+ # flat union has base, variants, and no local_members
+ # simple union has local_members, variants, and no base
+ QAPISchemaType.__init__(self, name, info, doc, ifcond)
+ self.meta = 'union' if variants else 'struct'
+ assert base is None or isinstance(base, str)
+ for m in local_members:
+ assert isinstance(m, QAPISchemaObjectTypeMember)
+ m.set_defined_in(name)
+ if variants is not None:
+ assert isinstance(variants, QAPISchemaObjectTypeVariants)
+ variants.set_defined_in(name)
+ for f in features:
+ assert isinstance(f, QAPISchemaFeature)
+ f.set_defined_in(name)
+ self._base_name = base
+ self.base = None
+ self.local_members = local_members
+ self.variants = variants
+ self.members = None
+ self.features = features
+
+ def check(self, schema):
+ # This calls another type T's .check() exactly when the C
+ # struct emitted by gen_object() contains that T's C struct
+ # (pointers don't count).
+ if self.members is not None:
+ # A previous .check() completed: nothing to do
+ return
+ if self._checked:
+ # Recursed: C struct contains itself
+ raise QAPISemError(self.info,
+ "object %s contains itself" % self.name)
+
+ QAPISchemaType.check(self, schema)
+ assert self._checked and self.members is None
+
+ seen = OrderedDict()
+ if self._base_name:
+ self.base = schema.resolve_type(self._base_name, self.info,
+ "'base'")
+ if (not isinstance(self.base, QAPISchemaObjectType)
+ or self.base.variants):
+ raise QAPISemError(
+ self.info,
+ "'base' requires a struct type, %s isn't"
+ % self.base.describe())
+ self.base.check(schema)
+ self.base.check_clash(self.info, seen)
+ for m in self.local_members:
+ m.check(schema)
+ m.check_clash(self.info, seen)
+ if self.doc:
+ self.doc.connect_member(m)
+ members = seen.values()
+
+ if self.variants:
+ self.variants.check(schema, seen)
+ self.variants.check_clash(self.info, seen)
+
+ # Features are in a name space separate from members
+ seen = {}
+ for f in self.features:
+ f.check_clash(self.info, seen)
+
+ if self.doc:
+ self.doc.check()
+
+ self.members = members # mark completed
+
+ # Check that the members of this type do not cause duplicate JSON members,
+ # and update seen to track the members seen so far. Report any errors
+ # on behalf of info, which is not necessarily self.info
+ def check_clash(self, info, seen):
+ assert self._checked
+ assert not self.variants # not implemented
+ for m in self.members:
+ m.check_clash(info, seen)
+
+ @property
+ def ifcond(self):
+ assert self._checked
+ if isinstance(self._ifcond, QAPISchemaType):
+ # Simple union wrapper type inherits from wrapped type;
+ # see _make_implicit_object_type()
+ return self._ifcond.ifcond
+ return self._ifcond
+
+ def is_implicit(self):
+ # See QAPISchema._make_implicit_object_type(), as well as
+ # _def_predefineds()
+ return self.name.startswith('q_')
+
+ def is_empty(self):
+ assert self.members is not None
+ return not self.members and not self.variants
+
+ def c_name(self):
+ assert self.name != 'q_empty'
+ return QAPISchemaType.c_name(self)
+
+ def c_type(self):
+ assert not self.is_implicit()
+ return c_name(self.name) + pointer_suffix
+
+ def c_unboxed_type(self):
+ return c_name(self.name)
+
+ def json_type(self):
+ return 'object'
+
+ def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
+ visitor.visit_object_type(self.name, self.info, self.ifcond,
+ self.base, self.local_members, self.variants,
+ self.features)
+ visitor.visit_object_type_flat(self.name, self.info, self.ifcond,
+ self.members, self.variants,
+ self.features)
+
+
+class QAPISchemaMember(object):
+ """ Represents object members, enum members and features """
+ role = 'member'
+
+ def __init__(self, name, info, ifcond=None):
+ assert isinstance(name, str)
+ self.name = name
+ self.info = info
+ self.ifcond = ifcond or []
+ self.defined_in = None
+
+ def set_defined_in(self, name):
+ assert not self.defined_in
+ self.defined_in = name
+
+ def check_clash(self, info, seen):
+ cname = c_name(self.name)
+ if cname in seen:
+ raise QAPISemError(
+ info,
+ "%s collides with %s"
+ % (self.describe(info), seen[cname].describe(info)))
+ seen[cname] = self
+
+ def describe(self, info):
+ role = self.role
+ defined_in = self.defined_in
+ assert defined_in
+
+ if defined_in.startswith('q_obj_'):
+ # See QAPISchema._make_implicit_object_type() - reverse the
+ # mapping there to create a nice human-readable description
+ defined_in = defined_in[6:]
+ if defined_in.endswith('-arg'):
+ # Implicit type created for a command's dict 'data'
+ assert role == 'member'
+ role = 'parameter'
+ elif defined_in.endswith('-base'):
+ # Implicit type created for a flat union's dict 'base'
+ role = 'base ' + role
+ else:
+ # Implicit type created for a simple union's branch
+ assert defined_in.endswith('-wrapper')
+ # Unreachable and not implemented
+ assert False
+ elif defined_in.endswith('Kind'):
+ # See QAPISchema._make_implicit_enum_type()
+ # Implicit enum created for simple union's branches
+ assert role == 'value'
+ role = 'branch'
+ elif defined_in != info.defn_name:
+ return "%s '%s' of type '%s'" % (role, self.name, defined_in)
+ return "%s '%s'" % (role, self.name)
+
+
+class QAPISchemaEnumMember(QAPISchemaMember):
+ role = 'value'
+
+
+class QAPISchemaFeature(QAPISchemaMember):
+ role = 'feature'
+
+
+class QAPISchemaObjectTypeMember(QAPISchemaMember):
+ def __init__(self, name, info, typ, optional, ifcond=None):
+ QAPISchemaMember.__init__(self, name, info, ifcond)
+ assert isinstance(typ, str)
+ assert isinstance(optional, bool)
+ self._type_name = typ
+ self.type = None
+ self.optional = optional
+
+ def check(self, schema):
+ assert self.defined_in
+ self.type = schema.resolve_type(self._type_name, self.info,
+ self.describe)
+
+
+class QAPISchemaObjectTypeVariants(object):
+ def __init__(self, tag_name, info, tag_member, variants):
+ # Flat unions pass tag_name but not tag_member.
+ # Simple unions and alternates pass tag_member but not tag_name.
+ # After check(), tag_member is always set, and tag_name remains
+ # a reliable witness of being used by a flat union.
+ assert bool(tag_member) != bool(tag_name)
+ assert (isinstance(tag_name, str) or
+ isinstance(tag_member, QAPISchemaObjectTypeMember))
+ for v in variants:
+ assert isinstance(v, QAPISchemaObjectTypeVariant)
+ self._tag_name = tag_name
+ self.info = info
+ self.tag_member = tag_member
+ self.variants = variants
+
+ def set_defined_in(self, name):
+ for v in self.variants:
+ v.set_defined_in(name)
+
+ def check(self, schema, seen):
+ if not self.tag_member: # flat union
+ self.tag_member = seen.get(c_name(self._tag_name))
+ base = "'base'"
+ # Pointing to the base type when not implicit would be
+ # nice, but we don't know it here
+ if not self.tag_member or self._tag_name != self.tag_member.name:
+ raise QAPISemError(
+ self.info,
+ "discriminator '%s' is not a member of %s"
+ % (self._tag_name, base))
+ # Here we do:
+ base_type = schema.lookup_type(self.tag_member.defined_in)
+ assert base_type
+ if not base_type.is_implicit():
+ base = "base type '%s'" % self.tag_member.defined_in
+ if not isinstance(self.tag_member.type, QAPISchemaEnumType):
+ raise QAPISemError(
+ self.info,
+ "discriminator member '%s' of %s must be of enum type"
+ % (self._tag_name, base))
+ if self.tag_member.optional:
+ raise QAPISemError(
+ self.info,
+ "discriminator member '%s' of %s must not be optional"
+ % (self._tag_name, base))
+ if self.tag_member.ifcond:
+ raise QAPISemError(
+ self.info,
+ "discriminator member '%s' of %s must not be conditional"
+ % (self._tag_name, base))
+ else: # simple union
+ assert isinstance(self.tag_member.type, QAPISchemaEnumType)
+ assert not self.tag_member.optional
+ assert self.tag_member.ifcond == []
+ if self._tag_name: # flat union
+ # branches that are not explicitly covered get an empty type
+ cases = set([v.name for v in self.variants])
+ for m in self.tag_member.type.members:
+ if m.name not in cases:
+ v = QAPISchemaObjectTypeVariant(m.name, self.info,
+ 'q_empty', m.ifcond)
+ v.set_defined_in(self.tag_member.defined_in)
+ self.variants.append(v)
+ if not self.variants:
+ raise QAPISemError(self.info, "union has no branches")
+ for v in self.variants:
+ v.check(schema)
+ # Union names must match enum values; alternate names are
+ # checked separately. Use 'seen' to tell the two apart.
+ if seen:
+ if v.name not in self.tag_member.type.member_names():
+ raise QAPISemError(
+ self.info,
+ "branch '%s' is not a value of %s"
+ % (v.name, self.tag_member.type.describe()))
+ if (not isinstance(v.type, QAPISchemaObjectType)
+ or v.type.variants):
+ raise QAPISemError(
+ self.info,
+ "%s cannot use %s"
+ % (v.describe(self.info), v.type.describe()))
+ v.type.check(schema)
+
+ def check_clash(self, info, seen):
+ for v in self.variants:
+ # Reset seen map for each variant, since qapi names from one
+ # branch do not affect another branch
+ v.type.check_clash(info, dict(seen))
+
+
+class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
+ role = 'branch'
+
+ def __init__(self, name, info, typ, ifcond=None):
+ QAPISchemaObjectTypeMember.__init__(self, name, info, typ,
+ False, ifcond)
+
+
+class QAPISchemaAlternateType(QAPISchemaType):
+ meta = 'alternate'
+
+ def __init__(self, name, info, doc, ifcond, variants):
+ QAPISchemaType.__init__(self, name, info, doc, ifcond)
+ assert isinstance(variants, QAPISchemaObjectTypeVariants)
+ assert variants.tag_member
+ variants.set_defined_in(name)
+ variants.tag_member.set_defined_in(self.name)
+ self.variants = variants
+
+ def check(self, schema):
+ QAPISchemaType.check(self, schema)
+ self.variants.tag_member.check(schema)
+ # Not calling self.variants.check_clash(), because there's nothing
+ # to clash with
+ self.variants.check(schema, {})
+ # Alternate branch names have no relation to the tag enum values;
+ # so we have to check for potential name collisions ourselves.
+ seen = {}
+ types_seen = {}
+ for v in self.variants.variants:
+ v.check_clash(self.info, seen)
+ qtype = v.type.alternate_qtype()
+ if not qtype:
+ raise QAPISemError(
+ self.info,
+ "%s cannot use %s"
+ % (v.describe(self.info), v.type.describe()))
+ conflicting = set([qtype])
+ if qtype == 'QTYPE_QSTRING':
+ if isinstance(v.type, QAPISchemaEnumType):
+ for m in v.type.members:
+ if m.name in ['on', 'off']:
+ conflicting.add('QTYPE_QBOOL')
+ if re.match(r'[-+0-9.]', m.name):
+ # lazy, could be tightened
+ conflicting.add('QTYPE_QNUM')
+ else:
+ conflicting.add('QTYPE_QNUM')
+ conflicting.add('QTYPE_QBOOL')
+ for qt in conflicting:
+ if qt in types_seen:
+ raise QAPISemError(
+ self.info,
+ "%s can't be distinguished from '%s'"
+ % (v.describe(self.info), types_seen[qt]))
+ types_seen[qt] = v.name
+ if self.doc:
+ self.doc.connect_member(v)
+ if self.doc:
+ self.doc.check()
+
+ def c_type(self):
+ return c_name(self.name) + pointer_suffix
+
+ def json_type(self):
+ return 'value'
+
+ def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
+ visitor.visit_alternate_type(self.name, self.info, self.ifcond,
+ self.variants)
+
+
+class QAPISchemaCommand(QAPISchemaEntity):
+ meta = 'command'
+
+ def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
+ gen, success_response, boxed, allow_oob, allow_preconfig,
+ features):
+ QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
+ assert not arg_type or isinstance(arg_type, str)
+ assert not ret_type or isinstance(ret_type, str)
+ for f in features:
+ assert isinstance(f, QAPISchemaFeature)
+ f.set_defined_in(name)
+ self._arg_type_name = arg_type
+ self.arg_type = None
+ self._ret_type_name = ret_type
+ self.ret_type = None
+ self.gen = gen
+ self.success_response = success_response
+ self.boxed = boxed
+ self.allow_oob = allow_oob
+ self.allow_preconfig = allow_preconfig
+ self.features = features
+
+ def check(self, schema):
+ QAPISchemaEntity.check(self, schema)
+ if self._arg_type_name:
+ self.arg_type = schema.resolve_type(
+ self._arg_type_name, self.info, "command's 'data'")
+ if not isinstance(self.arg_type, QAPISchemaObjectType):
+ raise QAPISemError(
+ self.info,
+ "command's 'data' cannot take %s"
+ % self.arg_type.describe())
+ if self.arg_type.variants and not self.boxed:
+ raise QAPISemError(
+ self.info,
+ "command's 'data' can take %s only with 'boxed': true"
+ % self.arg_type.describe())
+ if self._ret_type_name:
+ self.ret_type = schema.resolve_type(
+ self._ret_type_name, self.info, "command's 'returns'")
+ if self.name not in self.info.pragma.returns_whitelist:
+ if not (isinstance(self.ret_type, QAPISchemaObjectType)
+ or (isinstance(self.ret_type, QAPISchemaArrayType)
+ and isinstance(self.ret_type.element_type,
+ QAPISchemaObjectType))):
+ raise QAPISemError(
+ self.info,
+ "command's 'returns' cannot take %s"
+ % self.ret_type.describe())
+
+ # Features are in a name space separate from members
+ seen = {}
+ for f in self.features:
+ f.check_clash(self.info, seen)
+
+ def visit(self, visitor):
+ QAPISchemaEntity.visit(self, visitor)
+ visitor.visit_command(self.name, self.info, self.ifcond,
+ self.arg_type, self.ret_type,
+ self.gen, self.success_response,
+ self.boxed, self.allow_oob,
+ self.allow_preconfig,
+ self.features)
+
+
+class QAPISchemaEvent(QAPISchemaEntity):
+ meta = 'event'
+
+ def __init__(self, name, info, doc, ifcond, arg_type, boxed):
+ QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
+ assert not arg_type or isinstance(arg_type, str)
+ self._arg_type_name = arg_type
+ self.arg_type = None
+ self.boxed = boxed
+
+ def check(self, schema):
+ QAPISchemaEntity.check(self, schema)
+ if self._arg_type_name:
+ self.arg_type = schema.resolve_type(
+ self._arg_type_name, self.info, "event's 'data'")
+ if not isinstance(self.arg_type, QAPISchemaObjectType):
+ raise QAPISemError(
+ self.info,
+ "event's 'data' cannot take %s"
+ % self.arg_type.describe())
+ if self.arg_type.variants and not self.boxed:
+ raise QAPISemError(
+ self.info,
+ "event's 'data' can take %s only with 'boxed': true"
+ % self.arg_type.describe())
+
+ def visit(self, visitor):
+ QAPISchemaEntity.visit(self, visitor)
+ visitor.visit_event(self.name, self.info, self.ifcond,
+ self.arg_type, self.boxed)
+
+
+class QAPISchema(object):
+ def __init__(self, fname):
+ self.fname = fname
+ parser = QAPISchemaParser(fname)
+ exprs = check_exprs(parser.exprs)
+ self.docs = parser.docs
+ self._entity_list = []
+ self._entity_dict = {}
+ self._predefining = True
+ self._def_predefineds()
+ self._predefining = False
+ self._def_exprs(exprs)
+ self.check()
+
+ def _def_entity(self, ent):
+ # Only the predefined types are allowed to not have info
+ assert ent.info or self._predefining
+ self._entity_list.append(ent)
+ if ent.name is None:
+ return
+ # TODO reject names that differ only in '_' vs. '.' vs. '-',
+ # because they're liable to clash in generated C.
+ other_ent = self._entity_dict.get(ent.name)
+ if other_ent:
+ if other_ent.info:
+ where = QAPIError(other_ent.info, None, "previous definition")
+ raise QAPISemError(
+ ent.info,
+ "'%s' is already defined\n%s" % (ent.name, where))
+ raise QAPISemError(
+ ent.info, "%s is already defined" % other_ent.describe())
+ self._entity_dict[ent.name] = ent
+
+ def lookup_entity(self, name, typ=None):
+ ent = self._entity_dict.get(name)
+ if typ and not isinstance(ent, typ):
+ return None
+ return ent
+
+ def lookup_type(self, name):
+ return self.lookup_entity(name, QAPISchemaType)
+
+ def resolve_type(self, name, info, what):
+ typ = self.lookup_type(name)
+ if not typ:
+ if callable(what):
+ what = what(info)
+ raise QAPISemError(
+ info, "%s uses unknown type '%s'" % (what, name))
+ return typ
+
+ def _def_include(self, expr, info, doc):
+ include = expr['include']
+ assert doc is None
+ main_info = info
+ while main_info.parent:
+ main_info = main_info.parent
+ fname = os.path.relpath(include, os.path.dirname(main_info.fname))
+ self._def_entity(QAPISchemaInclude(fname, info))
+
+ def _def_builtin_type(self, name, json_type, c_type):
+ self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
+ # Instantiating only the arrays that are actually used would
+ # be nice, but we can't as long as their generated code
+ # (qapi-builtin-types.[ch]) may be shared by some other
+ # schema.
+ self._make_array_type(name, None)
+
+ def _def_predefineds(self):
+ for t in [('str', 'string', 'char' + pointer_suffix),
+ ('number', 'number', 'double'),
+ ('int', 'int', 'int64_t'),
+ ('int8', 'int', 'int8_t'),
+ ('int16', 'int', 'int16_t'),
+ ('int32', 'int', 'int32_t'),
+ ('int64', 'int', 'int64_t'),
+ ('uint8', 'int', 'uint8_t'),
+ ('uint16', 'int', 'uint16_t'),
+ ('uint32', 'int', 'uint32_t'),
+ ('uint64', 'int', 'uint64_t'),
+ ('size', 'int', 'uint64_t'),
+ ('bool', 'boolean', 'bool'),
+ ('any', 'value', 'QObject' + pointer_suffix),
+ ('null', 'null', 'QNull' + pointer_suffix)]:
+ self._def_builtin_type(*t)
+ self.the_empty_object_type = QAPISchemaObjectType(
+ 'q_empty', None, None, None, None, [], None, [])
+ self._def_entity(self.the_empty_object_type)
+
+ qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
+ 'qbool']
+ qtype_values = self._make_enum_members(
+ [{'name': n} for n in qtypes], None)
+
+ self._def_entity(QAPISchemaEnumType('QType', None, None, None,
+ qtype_values, 'QTYPE'))
+
+ def _make_features(self, features, info):
+ return [QAPISchemaFeature(f['name'], info, f.get('if'))
+ for f in features]
+
+ def _make_enum_members(self, values, info):
+ return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
+ for v in values]
+
+ def _make_implicit_enum_type(self, name, info, ifcond, values):
+ # See also QAPISchemaObjectTypeMember.describe()
+ name = name + 'Kind' # reserved by check_defn_name_str()
+ self._def_entity(QAPISchemaEnumType(
+ name, info, None, ifcond, self._make_enum_members(values, info),
+ None))
+ return name
+
+ def _make_array_type(self, element_type, info):
+ name = element_type + 'List' # reserved by check_defn_name_str()
+ if not self.lookup_type(name):
+ self._def_entity(QAPISchemaArrayType(name, info, element_type))
+ return name
+
+ def _make_implicit_object_type(self, name, info, doc, ifcond,
+ role, members):
+ if not members:
+ return None
+ # See also QAPISchemaObjectTypeMember.describe()
+ name = 'q_obj_%s-%s' % (name, role)
+ typ = self.lookup_entity(name, QAPISchemaObjectType)
+ if typ:
+ # The implicit object type has multiple users. This can
+ # happen only for simple unions' implicit wrapper types.
+ # Its ifcond should be the disjunction of its user's
+ # ifconds. Not implemented. Instead, we always pass the
+ # wrapped type's ifcond, which is trivially the same for all
+ # users. It's also necessary for the wrapper to compile.
+ # But it's not tight: the disjunction need not imply it. We
+ # may end up compiling useless wrapper types.
+ # TODO kill simple unions or implement the disjunction
+ assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access
+ else:
+ self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
+ None, members, None, []))
+ return name
+
+ def _def_enum_type(self, expr, info, doc):
+ name = expr['enum']
+ data = expr['data']
+ prefix = expr.get('prefix')
+ ifcond = expr.get('if')
+ self._def_entity(QAPISchemaEnumType(
+ name, info, doc, ifcond,
+ self._make_enum_members(data, info), prefix))
+
+ def _make_member(self, name, typ, ifcond, info):
+ optional = False
+ if name.startswith('*'):
+ name = name[1:]
+ optional = True
+ if isinstance(typ, list):
+ assert len(typ) == 1
+ typ = self._make_array_type(typ[0], info)
+ return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond)
+
+ def _make_members(self, data, info):
+ return [self._make_member(key, value['type'], value.get('if'), info)
+ for (key, value) in data.items()]
+
+ def _def_struct_type(self, expr, info, doc):
+ name = expr['struct']
+ base = expr.get('base')
+ data = expr['data']
+ ifcond = expr.get('if')
+ features = expr.get('features', [])
+ self._def_entity(QAPISchemaObjectType(
+ name, info, doc, ifcond, base,
+ self._make_members(data, info),
+ None,
+ self._make_features(features, info)))
+
+ def _make_variant(self, case, typ, ifcond, info):
+ return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
+
+ def _make_simple_variant(self, case, typ, ifcond, info):
+ if isinstance(typ, list):
+ assert len(typ) == 1
+ typ = self._make_array_type(typ[0], info)
+ typ = self._make_implicit_object_type(
+ typ, info, None, self.lookup_type(typ),
+ 'wrapper', [self._make_member('data', typ, None, info)])
+ return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
+
+ def _def_union_type(self, expr, info, doc):
+ name = expr['union']
+ data = expr['data']
+ base = expr.get('base')
+ ifcond = expr.get('if')
+ tag_name = expr.get('discriminator')
+ tag_member = None
+ if isinstance(base, dict):
+ base = self._make_implicit_object_type(
+ name, info, doc, ifcond,
+ 'base', self._make_members(base, info))
+ if tag_name:
+ variants = [self._make_variant(key, value['type'],
+ value.get('if'), info)
+ for (key, value) in data.items()]
+ members = []
+ else:
+ variants = [self._make_simple_variant(key, value['type'],
+ value.get('if'), info)
+ for (key, value) in data.items()]
+ enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
+ typ = self._make_implicit_enum_type(name, info, ifcond, enum)
+ tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
+ members = [tag_member]
+ self._def_entity(
+ QAPISchemaObjectType(name, info, doc, ifcond, base, members,
+ QAPISchemaObjectTypeVariants(
+ tag_name, info, tag_member, variants),
+ []))
+
+ def _def_alternate_type(self, expr, info, doc):
+ name = expr['alternate']
+ data = expr['data']
+ ifcond = expr.get('if')
+ variants = [self._make_variant(key, value['type'], value.get('if'),
+ info)
+ for (key, value) in data.items()]
+ tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
+ self._def_entity(
+ QAPISchemaAlternateType(name, info, doc, ifcond,
+ QAPISchemaObjectTypeVariants(
+ None, info, tag_member, variants)))
+
+ def _def_command(self, expr, info, doc):
+ name = expr['command']
+ data = expr.get('data')
+ rets = expr.get('returns')
+ gen = expr.get('gen', True)
+ success_response = expr.get('success-response', True)
+ boxed = expr.get('boxed', False)
+ allow_oob = expr.get('allow-oob', False)
+ allow_preconfig = expr.get('allow-preconfig', False)
+ ifcond = expr.get('if')
+ features = expr.get('features', [])
+ if isinstance(data, OrderedDict):
+ data = self._make_implicit_object_type(
+ name, info, doc, ifcond, 'arg', self._make_members(data, info))
+ if isinstance(rets, list):
+ assert len(rets) == 1
+ rets = self._make_array_type(rets[0], info)
+ self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
+ gen, success_response,
+ boxed, allow_oob, allow_preconfig,
+ self._make_features(features, info)))
+
+ def _def_event(self, expr, info, doc):
+ name = expr['event']
+ data = expr.get('data')
+ boxed = expr.get('boxed', False)
+ ifcond = expr.get('if')
+ if isinstance(data, OrderedDict):
+ data = self._make_implicit_object_type(
+ name, info, doc, ifcond, 'arg', self._make_members(data, info))
+ self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
+
+ def _def_exprs(self, exprs):
+ for expr_elem in exprs:
+ expr = expr_elem['expr']
+ info = expr_elem['info']
+ doc = expr_elem.get('doc')
+ if 'enum' in expr:
+ self._def_enum_type(expr, info, doc)
+ elif 'struct' in expr:
+ self._def_struct_type(expr, info, doc)
+ elif 'union' in expr:
+ self._def_union_type(expr, info, doc)
+ elif 'alternate' in expr:
+ self._def_alternate_type(expr, info, doc)
+ elif 'command' in expr:
+ self._def_command(expr, info, doc)
+ elif 'event' in expr:
+ self._def_event(expr, info, doc)
+ elif 'include' in expr:
+ self._def_include(expr, info, doc)
+ else:
+ assert False
+
+ def check(self):
+ for ent in self._entity_list:
+ ent.check(self)
+
+ def visit(self, visitor):
+ visitor.visit_begin(self)
+ module = None
+ visitor.visit_module(module)
+ for entity in self._entity_list:
+ if visitor.visit_needed(entity):
+ if entity.module != module:
+ module = entity.module
+ visitor.visit_module(module)
+ entity.visit(visitor)
+ visitor.visit_end()
diff --git a/scripts/qapi/source.py b/scripts/qapi/source.py
new file mode 100644
index 0000000000..8956885033
--- /dev/null
+++ b/scripts/qapi/source.py
@@ -0,0 +1,67 @@
+#
+# QAPI frontend source file info
+#
+# Copyright (c) 2019 Red Hat Inc.
+#
+# Authors:
+# Markus Armbruster <armbru@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+
+import copy
+import sys
+
+
+class QAPISchemaPragma(object):
+ def __init__(self):
+ # Are documentation comments required?
+ self.doc_required = False
+ # Whitelist of commands allowed to return a non-dictionary
+ self.returns_whitelist = []
+ # Whitelist of entities allowed to violate case conventions
+ self.name_case_whitelist = []
+
+
+class QAPISourceInfo(object):
+ def __init__(self, fname, line, parent):
+ self.fname = fname
+ self.line = line
+ self.parent = parent
+ self.pragma = parent.pragma if parent else QAPISchemaPragma()
+ self.defn_meta = None
+ self.defn_name = None
+
+ def set_defn(self, meta, name):
+ self.defn_meta = meta
+ self.defn_name = name
+
+ def next_line(self):
+ info = copy.copy(self)
+ info.line += 1
+ return info
+
+ def loc(self):
+ if self.fname is None:
+ return sys.argv[0]
+ ret = self.fname
+ if self.line is not None:
+ ret += ':%d' % self.line
+ return ret
+
+ def in_defn(self):
+ if self.defn_name:
+ return "%s: In %s '%s':\n" % (self.fname,
+ self.defn_meta, self.defn_name)
+ return ''
+
+ def include_path(self):
+ ret = ''
+ parent = self.parent
+ while parent:
+ ret = 'In file included from %s:\n' % parent.loc() + ret
+ parent = parent.parent
+ return ret
+
+ def __str__(self):
+ return self.include_path() + self.in_defn() + self.loc()
diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
index 3edd9374aa..d8751daa04 100644
--- a/scripts/qapi/types.py
+++ b/scripts/qapi/types.py
@@ -14,6 +14,8 @@ This work is licensed under the terms of the GNU GPL, version 2.
"""
from qapi.common import *
+from qapi.gen import QAPISchemaModularCVisitor, ifcontext
+from qapi.schema import QAPISchemaEnumMember, QAPISchemaObjectType
# variants must be emitted before their container; track what has already
@@ -21,6 +23,65 @@ from qapi.common import *
objects_seen = set()
+def gen_enum_lookup(name, members, prefix=None):
+ ret = mcgen('''
+
+const QEnumLookup %(c_name)s_lookup = {
+ .array = (const char *const[]) {
+''',
+ c_name=c_name(name))
+ for m in members:
+ ret += gen_if(m.ifcond)
+ index = c_enum_const(name, m.name, prefix)
+ ret += mcgen('''
+ [%(index)s] = "%(name)s",
+''',
+ index=index, name=m.name)
+ ret += gen_endif(m.ifcond)
+
+ ret += mcgen('''
+ },
+ .size = %(max_index)s
+};
+''',
+ max_index=c_enum_const(name, '_MAX', prefix))
+ return ret
+
+
+def gen_enum(name, members, prefix=None):
+ # append automatically generated _MAX value
+ enum_members = members + [QAPISchemaEnumMember('_MAX', None)]
+
+ ret = mcgen('''
+
+typedef enum %(c_name)s {
+''',
+ c_name=c_name(name))
+
+ for m in enum_members:
+ ret += gen_if(m.ifcond)
+ ret += mcgen('''
+ %(c_enum)s,
+''',
+ c_enum=c_enum_const(name, m.name, prefix))
+ ret += gen_endif(m.ifcond)
+
+ ret += mcgen('''
+} %(c_name)s;
+''',
+ c_name=c_name(name))
+
+ ret += mcgen('''
+
+#define %(c_name)s_str(val) \\
+ qapi_enum_lookup(&%(c_name)s_lookup, (val))
+
+extern const QEnumLookup %(c_name)s_lookup;
+''',
+ c_name=c_name(name))
+ return ret
+
+
def gen_fwd_object_or_array(name):
return mcgen('''
diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
index 484ebb66ad..c72f2bc5c0 100644
--- a/scripts/qapi/visit.py
+++ b/scripts/qapi/visit.py
@@ -14,6 +14,8 @@ See the COPYING file in the top-level directory.
"""
from qapi.common import *
+from qapi.gen import QAPISchemaModularCVisitor, ifcontext
+from qapi.schema import QAPISchemaObjectType
def gen_visit_decl(name, scalar=False):
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 3543451ed3..09e5b410dc 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -31,13 +31,20 @@ ifneq ($(wildcard config-host.mak),)
export SRC_PATH
# TODO don't duplicate $(SRC_PATH)/Makefile's qapi-py here
-qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
+qapi-py = $(SRC_PATH)/scripts/qapi/__init__.py \
+$(SRC_PATH)/scripts/qapi/commands.py \
+$(SRC_PATH)/scripts/qapi/common.py \
+$(SRC_PATH)/scripts/qapi/doc.py \
+$(SRC_PATH)/scripts/qapi/error.py \
$(SRC_PATH)/scripts/qapi/events.py \
+$(SRC_PATH)/scripts/qapi/expr.py \
+$(SRC_PATH)/scripts/qapi/gen.py \
$(SRC_PATH)/scripts/qapi/introspect.py \
+$(SRC_PATH)/scripts/qapi/parser.py \
+$(SRC_PATH)/scripts/qapi/schema.py \
+$(SRC_PATH)/scripts/qapi/source.py \
$(SRC_PATH)/scripts/qapi/types.py \
$(SRC_PATH)/scripts/qapi/visit.py \
-$(SRC_PATH)/scripts/qapi/common.py \
-$(SRC_PATH)/scripts/qapi/doc.py \
$(SRC_PATH)/scripts/qapi-gen.py
# Get the list of all supported sysemu targets
@@ -609,6 +616,7 @@ tests/test-qapi-gen-timestamp: \
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
-o tests -p "test-" $<, \
"GEN","$(@:%-timestamp=%)")
+ @rm -f tests/test-qapi-doc.texi
@>$@
tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.json $(qapi-py)
@@ -1101,17 +1109,11 @@ check-tests/check-block.sh: tests/check-block.sh qemu-img$(EXESUF) \
$(patsubst %,%/all,$(filter %-softmmu,$(TARGET_DIRS)))
@$<
-.PHONY: $(patsubst %, check-%, $(check-qapi-schema-y))
-$(patsubst %, check-%, $(check-qapi-schema-y)): check-%.json: $(SRC_PATH)/%.json
+.PHONY: check-tests/qapi-schema/frontend
+check-tests/qapi-schema/frontend: $(addprefix $(SRC_PATH)/, $(check-qapi-schema-y))
$(call quiet-command, PYTHONPATH=$(SRC_PATH)/scripts \
- PYTHONIOENCODING=utf-8 $(PYTHON) $(SRC_PATH)/tests/qapi-schema/test-qapi.py \
- $^ >$*.test.out 2>$*.test.err; \
- echo $$? >$*.test.exit, \
- "TEST","$*.out")
- @# Sanitize error messages (make them independent of build directory)
- @perl -p -e 's|\Q$(SRC_PATH)\E/||g' $*.test.err | diff -u $(SRC_PATH)/$*.err -
- @diff -u $(SRC_PATH)/$*.out $*.test.out
- @diff -u $(SRC_PATH)/$*.exit $*.test.exit
+ PYTHONIOENCODING=utf-8 $(PYTHON) $(SRC_PATH)/tests/qapi-schema/test-qapi.py $^, \
+ TEST, check-qapi-schema)
.PHONY: check-tests/qapi-schema/doc-good.texi
check-tests/qapi-schema/doc-good.texi: tests/qapi-schema/doc-good.test.texi
@@ -1169,7 +1171,7 @@ check-acceptance: check-venv $(TESTS_RESULTS_DIR)
# Consolidated targets
.PHONY: check-block check-qapi-schema check-qtest check-unit check check-clean
-check-qapi-schema: $(patsubst %,check-%, $(check-qapi-schema-y)) check-tests/qapi-schema/doc-good.texi
+check-qapi-schema: check-tests/qapi-schema/frontend check-tests/qapi-schema/doc-good.texi
check-qtest: $(patsubst %,check-qtest-%, $(QTEST_TARGETS))
check-block: $(patsubst %,check-%, $(check-block-y))
check: check-block check-qapi-schema check-unit check-softfloat check-qtest check-decodetree
diff --git a/tests/qapi-schema/allow-preconfig-test.err b/tests/qapi-schema/allow-preconfig-test.err
index 2a4e6ce663..3cd672bc61 100644
--- a/tests/qapi-schema/allow-preconfig-test.err
+++ b/tests/qapi-schema/allow-preconfig-test.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/allow-preconfig-test.json: In command 'allow-preconfig-test':
-tests/qapi-schema/allow-preconfig-test.json:2: flag 'allow-preconfig' may only use true value
+allow-preconfig-test.json: In command 'allow-preconfig-test':
+allow-preconfig-test.json:2: flag 'allow-preconfig' may only use true value
diff --git a/tests/qapi-schema/allow-preconfig-test.exit b/tests/qapi-schema/allow-preconfig-test.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/allow-preconfig-test.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-any.err b/tests/qapi-schema/alternate-any.err
index 03aaf29506..baeb3f66d1 100644
--- a/tests/qapi-schema/alternate-any.err
+++ b/tests/qapi-schema/alternate-any.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-any.json: In alternate 'Alt':
-tests/qapi-schema/alternate-any.json:2: branch 'one' cannot use built-in type 'any'
+alternate-any.json: In alternate 'Alt':
+alternate-any.json:2: branch 'one' cannot use built-in type 'any'
diff --git a/tests/qapi-schema/alternate-any.exit b/tests/qapi-schema/alternate-any.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-any.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err
index dfbe3ee998..b1aa1f4e8d 100644
--- a/tests/qapi-schema/alternate-array.err
+++ b/tests/qapi-schema/alternate-array.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-array.json: In alternate 'Alt':
-tests/qapi-schema/alternate-array.json:5: 'data' member 'two' cannot be an array
+alternate-array.json: In alternate 'Alt':
+alternate-array.json:5: 'data' member 'two' cannot be an array
diff --git a/tests/qapi-schema/alternate-array.exit b/tests/qapi-schema/alternate-array.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-array.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err
index 04cea97e5c..31ebe56bbf 100644
--- a/tests/qapi-schema/alternate-base.err
+++ b/tests/qapi-schema/alternate-base.err
@@ -1,3 +1,3 @@
-tests/qapi-schema/alternate-base.json: In alternate 'Alt':
-tests/qapi-schema/alternate-base.json:4: alternate has unknown key 'base'
+alternate-base.json: In alternate 'Alt':
+alternate-base.json:4: alternate has unknown key 'base'
Valid keys are 'alternate', 'data', 'if'.
diff --git a/tests/qapi-schema/alternate-base.exit b/tests/qapi-schema/alternate-base.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-base.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-branch-if-invalid.err b/tests/qapi-schema/alternate-branch-if-invalid.err
index 6c68e5a922..d384929c51 100644
--- a/tests/qapi-schema/alternate-branch-if-invalid.err
+++ b/tests/qapi-schema/alternate-branch-if-invalid.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-branch-if-invalid.json: In alternate 'Alt':
-tests/qapi-schema/alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member 'branch' makes no sense
+alternate-branch-if-invalid.json: In alternate 'Alt':
+alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member 'branch' makes no sense
diff --git a/tests/qapi-schema/alternate-branch-if-invalid.exit b/tests/qapi-schema/alternate-branch-if-invalid.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-branch-if-invalid.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err
index 73a52d69d1..f58b977f7b 100644
--- a/tests/qapi-schema/alternate-clash.err
+++ b/tests/qapi-schema/alternate-clash.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-clash.json: In alternate 'Alt1':
-tests/qapi-schema/alternate-clash.json:7: branch 'a_b' collides with branch 'a-b'
+alternate-clash.json: In alternate 'Alt1':
+alternate-clash.json:7: branch 'a_b' collides with branch 'a-b'
diff --git a/tests/qapi-schema/alternate-clash.exit b/tests/qapi-schema/alternate-clash.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-clash.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-conflict-bool-string.err b/tests/qapi-schema/alternate-conflict-bool-string.err
index f7513b9cbe..59ff5efa87 100644
--- a/tests/qapi-schema/alternate-conflict-bool-string.err
+++ b/tests/qapi-schema/alternate-conflict-bool-string.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-conflict-bool-string.json: In alternate 'Alt':
-tests/qapi-schema/alternate-conflict-bool-string.json:2: branch 'two' can't be distinguished from 'one'
+alternate-conflict-bool-string.json: In alternate 'Alt':
+alternate-conflict-bool-string.json:2: branch 'two' can't be distinguished from 'one'
diff --git a/tests/qapi-schema/alternate-conflict-bool-string.exit b/tests/qapi-schema/alternate-conflict-bool-string.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-conflict-bool-string.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-conflict-dict.err b/tests/qapi-schema/alternate-conflict-dict.err
index e5b42d04c9..d4970284ba 100644
--- a/tests/qapi-schema/alternate-conflict-dict.err
+++ b/tests/qapi-schema/alternate-conflict-dict.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-conflict-dict.json: In alternate 'Alt':
-tests/qapi-schema/alternate-conflict-dict.json:6: branch 'two' can't be distinguished from 'one'
+alternate-conflict-dict.json: In alternate 'Alt':
+alternate-conflict-dict.json:6: branch 'two' can't be distinguished from 'one'
diff --git a/tests/qapi-schema/alternate-conflict-dict.exit b/tests/qapi-schema/alternate-conflict-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-conflict-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-conflict-enum-bool.err b/tests/qapi-schema/alternate-conflict-enum-bool.err
index 3d23aeba51..5f35855274 100644
--- a/tests/qapi-schema/alternate-conflict-enum-bool.err
+++ b/tests/qapi-schema/alternate-conflict-enum-bool.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-conflict-enum-bool.json: In alternate 'Alt':
-tests/qapi-schema/alternate-conflict-enum-bool.json:4: branch 'two' can't be distinguished from 'one'
+alternate-conflict-enum-bool.json: In alternate 'Alt':
+alternate-conflict-enum-bool.json:4: branch 'two' can't be distinguished from 'one'
diff --git a/tests/qapi-schema/alternate-conflict-enum-bool.exit b/tests/qapi-schema/alternate-conflict-enum-bool.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-conflict-enum-bool.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-conflict-enum-int.err b/tests/qapi-schema/alternate-conflict-enum-int.err
index b72768caa4..6a6d156664 100644
--- a/tests/qapi-schema/alternate-conflict-enum-int.err
+++ b/tests/qapi-schema/alternate-conflict-enum-int.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-conflict-enum-int.json: In alternate 'Alt':
-tests/qapi-schema/alternate-conflict-enum-int.json:4: branch 'two' can't be distinguished from 'one'
+alternate-conflict-enum-int.json: In alternate 'Alt':
+alternate-conflict-enum-int.json:4: branch 'two' can't be distinguished from 'one'
diff --git a/tests/qapi-schema/alternate-conflict-enum-int.exit b/tests/qapi-schema/alternate-conflict-enum-int.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-conflict-enum-int.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-conflict-num-string.err b/tests/qapi-schema/alternate-conflict-num-string.err
index b8a2bb1829..38c805ea1f 100644
--- a/tests/qapi-schema/alternate-conflict-num-string.err
+++ b/tests/qapi-schema/alternate-conflict-num-string.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-conflict-num-string.json: In alternate 'Alt':
-tests/qapi-schema/alternate-conflict-num-string.json:2: branch 'two' can't be distinguished from 'one'
+alternate-conflict-num-string.json: In alternate 'Alt':
+alternate-conflict-num-string.json:2: branch 'two' can't be distinguished from 'one'
diff --git a/tests/qapi-schema/alternate-conflict-num-string.exit b/tests/qapi-schema/alternate-conflict-num-string.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-conflict-num-string.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-conflict-string.err b/tests/qapi-schema/alternate-conflict-string.err
index 3edec51911..2fa08193db 100644
--- a/tests/qapi-schema/alternate-conflict-string.err
+++ b/tests/qapi-schema/alternate-conflict-string.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-conflict-string.json: In alternate 'Alt':
-tests/qapi-schema/alternate-conflict-string.json:2: branch 'two' can't be distinguished from 'one'
+alternate-conflict-string.json: In alternate 'Alt':
+alternate-conflict-string.json:2: branch 'two' can't be distinguished from 'one'
diff --git a/tests/qapi-schema/alternate-conflict-string.exit b/tests/qapi-schema/alternate-conflict-string.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-conflict-string.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-empty.err b/tests/qapi-schema/alternate-empty.err
index 908c309518..c6f6401d18 100644
--- a/tests/qapi-schema/alternate-empty.err
+++ b/tests/qapi-schema/alternate-empty.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-empty.json: In alternate 'Alt':
-tests/qapi-schema/alternate-empty.json:2: 'data' must not be empty
+alternate-empty.json: In alternate 'Alt':
+alternate-empty.json:2: 'data' must not be empty
diff --git a/tests/qapi-schema/alternate-empty.exit b/tests/qapi-schema/alternate-empty.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-empty.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-invalid-dict.err b/tests/qapi-schema/alternate-invalid-dict.err
index d6a18a294b..e9e5025226 100644
--- a/tests/qapi-schema/alternate-invalid-dict.err
+++ b/tests/qapi-schema/alternate-invalid-dict.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-invalid-dict.json: In alternate 'Alt':
-tests/qapi-schema/alternate-invalid-dict.json:2: 'data' member 'two' misses key 'type'
+alternate-invalid-dict.json: In alternate 'Alt':
+alternate-invalid-dict.json:2: 'data' member 'two' misses key 'type'
diff --git a/tests/qapi-schema/alternate-invalid-dict.exit b/tests/qapi-schema/alternate-invalid-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-invalid-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err
index cd7a076ce5..3ae9cd2f11 100644
--- a/tests/qapi-schema/alternate-nested.err
+++ b/tests/qapi-schema/alternate-nested.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-nested.json: In alternate 'Alt2':
-tests/qapi-schema/alternate-nested.json:4: branch 'nested' cannot use alternate type 'Alt1'
+alternate-nested.json: In alternate 'Alt2':
+alternate-nested.json:4: branch 'nested' cannot use alternate type 'Alt1'
diff --git a/tests/qapi-schema/alternate-nested.exit b/tests/qapi-schema/alternate-nested.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-nested.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err
index df05860bba..17fec1cd17 100644
--- a/tests/qapi-schema/alternate-unknown.err
+++ b/tests/qapi-schema/alternate-unknown.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/alternate-unknown.json: In alternate 'Alt':
-tests/qapi-schema/alternate-unknown.json:2: branch 'unknown' uses unknown type 'MissingType'
+alternate-unknown.json: In alternate 'Alt':
+alternate-unknown.json:2: branch 'unknown' uses unknown type 'MissingType'
diff --git a/tests/qapi-schema/alternate-unknown.exit b/tests/qapi-schema/alternate-unknown.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/alternate-unknown.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-alternate.err b/tests/qapi-schema/args-alternate.err
index 852b81b89c..b1530aa610 100644
--- a/tests/qapi-schema/args-alternate.err
+++ b/tests/qapi-schema/args-alternate.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-alternate.json: In command 'oops':
-tests/qapi-schema/args-alternate.json:3: command's 'data' cannot take alternate type 'Alt'
+args-alternate.json: In command 'oops':
+args-alternate.json:3: command's 'data' cannot take alternate type 'Alt'
diff --git a/tests/qapi-schema/args-alternate.exit b/tests/qapi-schema/args-alternate.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-alternate.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-any.err b/tests/qapi-schema/args-any.err
index 04e11df29f..4b60560247 100644
--- a/tests/qapi-schema/args-any.err
+++ b/tests/qapi-schema/args-any.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-any.json: In command 'oops':
-tests/qapi-schema/args-any.json:2: command's 'data' cannot take built-in type 'any'
+args-any.json: In command 'oops':
+args-any.json:2: command's 'data' cannot take built-in type 'any'
diff --git a/tests/qapi-schema/args-any.exit b/tests/qapi-schema/args-any.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-any.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-array-empty.err b/tests/qapi-schema/args-array-empty.err
index c7d367730e..181222296e 100644
--- a/tests/qapi-schema/args-array-empty.err
+++ b/tests/qapi-schema/args-array-empty.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-array-empty.json: In command 'oops':
-tests/qapi-schema/args-array-empty.json:2: 'data' member 'empty': array type must contain single type name
+args-array-empty.json: In command 'oops':
+args-array-empty.json:2: 'data' member 'empty': array type must contain single type name
diff --git a/tests/qapi-schema/args-array-empty.exit b/tests/qapi-schema/args-array-empty.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-array-empty.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-array-unknown.err b/tests/qapi-schema/args-array-unknown.err
index 218fc4bf9a..cbc598e4ff 100644
--- a/tests/qapi-schema/args-array-unknown.err
+++ b/tests/qapi-schema/args-array-unknown.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-array-unknown.json: In command 'oops':
-tests/qapi-schema/args-array-unknown.json:2: command uses unknown type 'NoSuchType'
+args-array-unknown.json: In command 'oops':
+args-array-unknown.json:2: command uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/args-array-unknown.exit b/tests/qapi-schema/args-array-unknown.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-array-unknown.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-bad-boxed.err b/tests/qapi-schema/args-bad-boxed.err
index 31d39038fc..361276eb29 100644
--- a/tests/qapi-schema/args-bad-boxed.err
+++ b/tests/qapi-schema/args-bad-boxed.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-bad-boxed.json: In command 'foo':
-tests/qapi-schema/args-bad-boxed.json:2: flag 'boxed' may only use true value
+args-bad-boxed.json: In command 'foo':
+args-bad-boxed.json:2: flag 'boxed' may only use true value
diff --git a/tests/qapi-schema/args-bad-boxed.exit b/tests/qapi-schema/args-bad-boxed.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-bad-boxed.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-boxed-anon.err b/tests/qapi-schema/args-boxed-anon.err
index 5e0c2979b7..a89af75bef 100644
--- a/tests/qapi-schema/args-boxed-anon.err
+++ b/tests/qapi-schema/args-boxed-anon.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-boxed-anon.json: In command 'foo':
-tests/qapi-schema/args-boxed-anon.json:2: 'data' should be a type name
+args-boxed-anon.json: In command 'foo':
+args-boxed-anon.json:2: 'data' should be a type name
diff --git a/tests/qapi-schema/args-boxed-anon.exit b/tests/qapi-schema/args-boxed-anon.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-boxed-anon.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-boxed-string.err b/tests/qapi-schema/args-boxed-string.err
index dc2b00f217..415c1148a9 100644
--- a/tests/qapi-schema/args-boxed-string.err
+++ b/tests/qapi-schema/args-boxed-string.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-boxed-string.json: In command 'foo':
-tests/qapi-schema/args-boxed-string.json:2: command's 'data' cannot take built-in type 'str'
+args-boxed-string.json: In command 'foo':
+args-boxed-string.json:2: command's 'data' cannot take built-in type 'str'
diff --git a/tests/qapi-schema/args-boxed-string.exit b/tests/qapi-schema/args-boxed-string.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-boxed-string.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-int.err b/tests/qapi-schema/args-int.err
index 81b6f86b66..a2331c5543 100644
--- a/tests/qapi-schema/args-int.err
+++ b/tests/qapi-schema/args-int.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-int.json: In command 'oops':
-tests/qapi-schema/args-int.json:2: command's 'data' cannot take built-in type 'int'
+args-int.json: In command 'oops':
+args-int.json:2: command's 'data' cannot take built-in type 'int'
diff --git a/tests/qapi-schema/args-int.exit b/tests/qapi-schema/args-int.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-int.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-invalid.err b/tests/qapi-schema/args-invalid.err
index c4971e1399..1527c4a48d 100644
--- a/tests/qapi-schema/args-invalid.err
+++ b/tests/qapi-schema/args-invalid.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-invalid.json: In command 'foo':
-tests/qapi-schema/args-invalid.json:1: 'data' should be an object or type name
+args-invalid.json: In command 'foo':
+args-invalid.json:1: 'data' should be an object or type name
diff --git a/tests/qapi-schema/args-invalid.exit b/tests/qapi-schema/args-invalid.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-invalid.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-member-array-bad.err b/tests/qapi-schema/args-member-array-bad.err
index f95ac01372..194a3052d2 100644
--- a/tests/qapi-schema/args-member-array-bad.err
+++ b/tests/qapi-schema/args-member-array-bad.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-member-array-bad.json: In command 'oops':
-tests/qapi-schema/args-member-array-bad.json:2: 'data' member 'member': array type must contain single type name
+args-member-array-bad.json: In command 'oops':
+args-member-array-bad.json:2: 'data' member 'member': array type must contain single type name
diff --git a/tests/qapi-schema/args-member-array-bad.exit b/tests/qapi-schema/args-member-array-bad.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-member-array-bad.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-member-case.err b/tests/qapi-schema/args-member-case.err
index 3ecd276040..4f33dbbc38 100644
--- a/tests/qapi-schema/args-member-case.err
+++ b/tests/qapi-schema/args-member-case.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-member-case.json: In command 'no-way-this-will-get-whitelisted':
-tests/qapi-schema/args-member-case.json:2: 'data' member 'Arg' uses uppercase in name
+args-member-case.json: In command 'no-way-this-will-get-whitelisted':
+args-member-case.json:2: 'data' member 'Arg' uses uppercase in name
diff --git a/tests/qapi-schema/args-member-case.exit b/tests/qapi-schema/args-member-case.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-member-case.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-member-unknown.err b/tests/qapi-schema/args-member-unknown.err
index 0626e1209d..96b6e5d289 100644
--- a/tests/qapi-schema/args-member-unknown.err
+++ b/tests/qapi-schema/args-member-unknown.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-member-unknown.json: In command 'oops':
-tests/qapi-schema/args-member-unknown.json:2: parameter 'member' uses unknown type 'NoSuchType'
+args-member-unknown.json: In command 'oops':
+args-member-unknown.json:2: parameter 'member' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/args-member-unknown.exit b/tests/qapi-schema/args-member-unknown.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-member-unknown.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-name-clash.err b/tests/qapi-schema/args-name-clash.err
index c5916a80fb..3e04817bc0 100644
--- a/tests/qapi-schema/args-name-clash.err
+++ b/tests/qapi-schema/args-name-clash.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-name-clash.json: In command 'oops':
-tests/qapi-schema/args-name-clash.json:4: parameter 'a_b' collides with parameter 'a-b'
+args-name-clash.json: In command 'oops':
+args-name-clash.json:4: parameter 'a_b' collides with parameter 'a-b'
diff --git a/tests/qapi-schema/args-name-clash.exit b/tests/qapi-schema/args-name-clash.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-name-clash.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-union.err b/tests/qapi-schema/args-union.err
index 3a77b2863f..4bf4955027 100644
--- a/tests/qapi-schema/args-union.err
+++ b/tests/qapi-schema/args-union.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-union.json: In command 'oops':
-tests/qapi-schema/args-union.json:3: command's 'data' can take union type 'Uni' only with 'boxed': true
+args-union.json: In command 'oops':
+args-union.json:3: command's 'data' can take union type 'Uni' only with 'boxed': true
diff --git a/tests/qapi-schema/args-union.exit b/tests/qapi-schema/args-union.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-union.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/args-unknown.err b/tests/qapi-schema/args-unknown.err
index 6857d6bf48..44bf8706ae 100644
--- a/tests/qapi-schema/args-unknown.err
+++ b/tests/qapi-schema/args-unknown.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/args-unknown.json: In command 'oops':
-tests/qapi-schema/args-unknown.json:2: command's 'data' uses unknown type 'NoSuchType'
+args-unknown.json: In command 'oops':
+args-unknown.json:2: command's 'data' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/args-unknown.exit b/tests/qapi-schema/args-unknown.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/args-unknown.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-base.err b/tests/qapi-schema/bad-base.err
index 039678a364..61a1efc2c0 100644
--- a/tests/qapi-schema/bad-base.err
+++ b/tests/qapi-schema/bad-base.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/bad-base.json: In struct 'MyType':
-tests/qapi-schema/bad-base.json:3: 'base' requires a struct type, union type 'Union' isn't
+bad-base.json: In struct 'MyType':
+bad-base.json:3: 'base' requires a struct type, union type 'Union' isn't
diff --git a/tests/qapi-schema/bad-base.exit b/tests/qapi-schema/bad-base.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-base.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-data.err b/tests/qapi-schema/bad-data.err
index 5227bdce7e..7991c8898d 100644
--- a/tests/qapi-schema/bad-data.err
+++ b/tests/qapi-schema/bad-data.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/bad-data.json: In command 'oops':
-tests/qapi-schema/bad-data.json:2: 'data' cannot be an array
+bad-data.json: In command 'oops':
+bad-data.json:2: 'data' cannot be an array
diff --git a/tests/qapi-schema/bad-data.exit b/tests/qapi-schema/bad-data.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-data.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-ident.err b/tests/qapi-schema/bad-ident.err
index ad38a679c7..263fcd3ecd 100644
--- a/tests/qapi-schema/bad-ident.err
+++ b/tests/qapi-schema/bad-ident.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/bad-ident.json: In struct '*oops':
-tests/qapi-schema/bad-ident.json:2: struct has an invalid name
+bad-ident.json: In struct '*oops':
+bad-ident.json:2: struct has an invalid name
diff --git a/tests/qapi-schema/bad-ident.exit b/tests/qapi-schema/bad-ident.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-ident.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-if-empty-list.err b/tests/qapi-schema/bad-if-empty-list.err
index 517519f500..a946376d06 100644
--- a/tests/qapi-schema/bad-if-empty-list.err
+++ b/tests/qapi-schema/bad-if-empty-list.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/bad-if-empty-list.json: In struct 'TestIfStruct':
-tests/qapi-schema/bad-if-empty-list.json:2: 'if' condition [] of struct is useless
+bad-if-empty-list.json: In struct 'TestIfStruct':
+bad-if-empty-list.json:2: 'if' condition [] of struct is useless
diff --git a/tests/qapi-schema/bad-if-empty-list.exit b/tests/qapi-schema/bad-if-empty-list.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-if-empty-list.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-if-empty.err b/tests/qapi-schema/bad-if-empty.err
index 5f1767388e..a0f3effefb 100644
--- a/tests/qapi-schema/bad-if-empty.err
+++ b/tests/qapi-schema/bad-if-empty.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/bad-if-empty.json: In struct 'TestIfStruct':
-tests/qapi-schema/bad-if-empty.json:2: 'if' condition '' of struct makes no sense
+bad-if-empty.json: In struct 'TestIfStruct':
+bad-if-empty.json:2: 'if' condition '' of struct makes no sense
diff --git a/tests/qapi-schema/bad-if-empty.exit b/tests/qapi-schema/bad-if-empty.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-if-empty.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-if-list.err b/tests/qapi-schema/bad-if-list.err
index e5d72b2f39..c462f11b90 100644
--- a/tests/qapi-schema/bad-if-list.err
+++ b/tests/qapi-schema/bad-if-list.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/bad-if-list.json: In struct 'TestIfStruct':
-tests/qapi-schema/bad-if-list.json:2: 'if' condition ' ' of struct makes no sense
+bad-if-list.json: In struct 'TestIfStruct':
+bad-if-list.json:2: 'if' condition ' ' of struct makes no sense
diff --git a/tests/qapi-schema/bad-if-list.exit b/tests/qapi-schema/bad-if-list.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-if-list.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-if.err b/tests/qapi-schema/bad-if.err
index 65d8efd7e4..f83dee65da 100644
--- a/tests/qapi-schema/bad-if.err
+++ b/tests/qapi-schema/bad-if.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/bad-if.json: In struct 'TestIfStruct':
-tests/qapi-schema/bad-if.json:2: 'if' condition of struct must be a string or a list of strings
+bad-if.json: In struct 'TestIfStruct':
+bad-if.json:2: 'if' condition of struct must be a string or a list of strings
diff --git a/tests/qapi-schema/bad-if.exit b/tests/qapi-schema/bad-if.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-if.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-type-bool.err b/tests/qapi-schema/bad-type-bool.err
index 984a77c4e3..42ccc18988 100644
--- a/tests/qapi-schema/bad-type-bool.err
+++ b/tests/qapi-schema/bad-type-bool.err
@@ -1 +1 @@
-tests/qapi-schema/bad-type-bool.json:2: 'struct' requires a string name
+bad-type-bool.json:2: 'struct' requires a string name
diff --git a/tests/qapi-schema/bad-type-bool.exit b/tests/qapi-schema/bad-type-bool.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-type-bool.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-type-dict.err b/tests/qapi-schema/bad-type-dict.err
index e83b8cfb41..3d97cd3f0a 100644
--- a/tests/qapi-schema/bad-type-dict.err
+++ b/tests/qapi-schema/bad-type-dict.err
@@ -1 +1 @@
-tests/qapi-schema/bad-type-dict.json:2: 'command' requires a string name
+bad-type-dict.json:2: 'command' requires a string name
diff --git a/tests/qapi-schema/bad-type-dict.exit b/tests/qapi-schema/bad-type-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-type-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/bad-type-int.err b/tests/qapi-schema/bad-type-int.err
index 7f5916ea29..90d06bd94d 100644
--- a/tests/qapi-schema/bad-type-int.err
+++ b/tests/qapi-schema/bad-type-int.err
@@ -1 +1 @@
-tests/qapi-schema/bad-type-int.json:3:13: stray '123'
+bad-type-int.json:3:13: stray '123'
diff --git a/tests/qapi-schema/bad-type-int.exit b/tests/qapi-schema/bad-type-int.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/bad-type-int.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/base-cycle-direct.err b/tests/qapi-schema/base-cycle-direct.err
index 233e4b8952..4ea6c5064e 100644
--- a/tests/qapi-schema/base-cycle-direct.err
+++ b/tests/qapi-schema/base-cycle-direct.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/base-cycle-direct.json: In struct 'Loopy':
-tests/qapi-schema/base-cycle-direct.json:2: object Loopy contains itself
+base-cycle-direct.json: In struct 'Loopy':
+base-cycle-direct.json:2: object Loopy contains itself
diff --git a/tests/qapi-schema/base-cycle-direct.exit b/tests/qapi-schema/base-cycle-direct.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/base-cycle-direct.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/base-cycle-indirect.err b/tests/qapi-schema/base-cycle-indirect.err
index 4472f30ba1..9b5e7ec174 100644
--- a/tests/qapi-schema/base-cycle-indirect.err
+++ b/tests/qapi-schema/base-cycle-indirect.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/base-cycle-indirect.json: In struct 'Base1':
-tests/qapi-schema/base-cycle-indirect.json:2: object Base1 contains itself
+base-cycle-indirect.json: In struct 'Base1':
+base-cycle-indirect.json:2: object Base1 contains itself
diff --git a/tests/qapi-schema/base-cycle-indirect.exit b/tests/qapi-schema/base-cycle-indirect.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/base-cycle-indirect.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/command-int.err b/tests/qapi-schema/command-int.err
index 3523d50a79..df0e5f5a57 100644
--- a/tests/qapi-schema/command-int.err
+++ b/tests/qapi-schema/command-int.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/command-int.json: In command 'int':
-tests/qapi-schema/command-int.json:2: built-in type 'int' is already defined
+command-int.json: In command 'int':
+command-int.json:2: built-in type 'int' is already defined
diff --git a/tests/qapi-schema/command-int.exit b/tests/qapi-schema/command-int.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/command-int.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/comments.exit b/tests/qapi-schema/comments.exit
deleted file mode 100644
index 573541ac97..0000000000
--- a/tests/qapi-schema/comments.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/doc-bad-alternate-member.err b/tests/qapi-schema/doc-bad-alternate-member.err
index 19a1ffd76e..a1c282f935 100644
--- a/tests/qapi-schema/doc-bad-alternate-member.err
+++ b/tests/qapi-schema/doc-bad-alternate-member.err
@@ -1 +1 @@
-tests/qapi-schema/doc-bad-alternate-member.json:3: the following documented members are not in the declaration: aa, bb
+doc-bad-alternate-member.json:3: the following documented members are not in the declaration: aa, bb
diff --git a/tests/qapi-schema/doc-bad-alternate-member.exit b/tests/qapi-schema/doc-bad-alternate-member.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-bad-alternate-member.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-bad-command-arg.err b/tests/qapi-schema/doc-bad-command-arg.err
index 6962ebed69..153ea0330a 100644
--- a/tests/qapi-schema/doc-bad-command-arg.err
+++ b/tests/qapi-schema/doc-bad-command-arg.err
@@ -1 +1 @@
-tests/qapi-schema/doc-bad-command-arg.json:3: the following documented members are not in the declaration: b
+doc-bad-command-arg.json:3: the following documented members are not in the declaration: b
diff --git a/tests/qapi-schema/doc-bad-command-arg.exit b/tests/qapi-schema/doc-bad-command-arg.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-bad-command-arg.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-bad-section.exit b/tests/qapi-schema/doc-bad-section.exit
deleted file mode 100644
index 573541ac97..0000000000
--- a/tests/qapi-schema/doc-bad-section.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/doc-bad-symbol.err b/tests/qapi-schema/doc-bad-symbol.err
index b23e99d160..166c8dcc18 100644
--- a/tests/qapi-schema/doc-bad-symbol.err
+++ b/tests/qapi-schema/doc-bad-symbol.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/doc-bad-symbol.json: In command 'foo':
-tests/qapi-schema/doc-bad-symbol.json:6: documentation comment is for 'food'
+doc-bad-symbol.json: In command 'foo':
+doc-bad-symbol.json:6: documentation comment is for 'food'
diff --git a/tests/qapi-schema/doc-bad-symbol.exit b/tests/qapi-schema/doc-bad-symbol.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-bad-symbol.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-bad-union-member.err b/tests/qapi-schema/doc-bad-union-member.err
index da3cd806e3..8b9d36eab1 100644
--- a/tests/qapi-schema/doc-bad-union-member.err
+++ b/tests/qapi-schema/doc-bad-union-member.err
@@ -1 +1 @@
-tests/qapi-schema/doc-bad-union-member.json:3: the following documented members are not in the declaration: a, b
+doc-bad-union-member.json:3: the following documented members are not in the declaration: a, b
diff --git a/tests/qapi-schema/doc-bad-union-member.exit b/tests/qapi-schema/doc-bad-union-member.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-bad-union-member.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-before-include.err b/tests/qapi-schema/doc-before-include.err
index e5566f11e9..ae23ea2f57 100644
--- a/tests/qapi-schema/doc-before-include.err
+++ b/tests/qapi-schema/doc-before-include.err
@@ -1 +1 @@
-tests/qapi-schema/doc-before-include.json:3: documentation for 'foo' is not followed by the definition
+doc-before-include.json:3: documentation for 'foo' is not followed by the definition
diff --git a/tests/qapi-schema/doc-before-include.exit b/tests/qapi-schema/doc-before-include.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-before-include.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-before-pragma.err b/tests/qapi-schema/doc-before-pragma.err
index 8a97ebb578..c776252e07 100644
--- a/tests/qapi-schema/doc-before-pragma.err
+++ b/tests/qapi-schema/doc-before-pragma.err
@@ -1 +1 @@
-tests/qapi-schema/doc-before-pragma.json:3: documentation for 'foo' is not followed by the definition
+doc-before-pragma.json:3: documentation for 'foo' is not followed by the definition
diff --git a/tests/qapi-schema/doc-before-pragma.exit b/tests/qapi-schema/doc-before-pragma.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-before-pragma.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-duplicated-arg.err b/tests/qapi-schema/doc-duplicated-arg.err
index 1c3f8e0a54..0d0d777a1f 100644
--- a/tests/qapi-schema/doc-duplicated-arg.err
+++ b/tests/qapi-schema/doc-duplicated-arg.err
@@ -1 +1 @@
-tests/qapi-schema/doc-duplicated-arg.json:6:1: 'a' parameter name duplicated
+doc-duplicated-arg.json:6:1: 'a' parameter name duplicated
diff --git a/tests/qapi-schema/doc-duplicated-arg.exit b/tests/qapi-schema/doc-duplicated-arg.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-duplicated-arg.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-duplicated-return.err b/tests/qapi-schema/doc-duplicated-return.err
index 7631933093..fe97e3db8d 100644
--- a/tests/qapi-schema/doc-duplicated-return.err
+++ b/tests/qapi-schema/doc-duplicated-return.err
@@ -1 +1 @@
-tests/qapi-schema/doc-duplicated-return.json:7:1: duplicated 'Returns' section
+doc-duplicated-return.json:7:1: duplicated 'Returns' section
diff --git a/tests/qapi-schema/doc-duplicated-return.exit b/tests/qapi-schema/doc-duplicated-return.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-duplicated-return.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-duplicated-since.err b/tests/qapi-schema/doc-duplicated-since.err
index 5ee15ae2a1..abca141a2c 100644
--- a/tests/qapi-schema/doc-duplicated-since.err
+++ b/tests/qapi-schema/doc-duplicated-since.err
@@ -1 +1 @@
-tests/qapi-schema/doc-duplicated-since.json:7:1: duplicated 'Since' section
+doc-duplicated-since.json:7:1: duplicated 'Since' section
diff --git a/tests/qapi-schema/doc-duplicated-since.exit b/tests/qapi-schema/doc-duplicated-since.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-duplicated-since.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-empty-arg.err b/tests/qapi-schema/doc-empty-arg.err
index 3c78a37ae1..2d0f35f310 100644
--- a/tests/qapi-schema/doc-empty-arg.err
+++ b/tests/qapi-schema/doc-empty-arg.err
@@ -1 +1 @@
-tests/qapi-schema/doc-empty-arg.json:5:1: invalid parameter name
+doc-empty-arg.json:5:1: invalid parameter name
diff --git a/tests/qapi-schema/doc-empty-arg.exit b/tests/qapi-schema/doc-empty-arg.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-empty-arg.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-empty-section.err b/tests/qapi-schema/doc-empty-section.err
index f6586c566f..ba7ba70125 100644
--- a/tests/qapi-schema/doc-empty-section.err
+++ b/tests/qapi-schema/doc-empty-section.err
@@ -1 +1 @@
-tests/qapi-schema/doc-empty-section.json:7:1: empty doc section 'Note'
+doc-empty-section.json:7:1: empty doc section 'Note'
diff --git a/tests/qapi-schema/doc-empty-section.exit b/tests/qapi-schema/doc-empty-section.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-empty-section.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-empty-symbol.err b/tests/qapi-schema/doc-empty-symbol.err
index 2dcddca7f6..81b90e882a 100644
--- a/tests/qapi-schema/doc-empty-symbol.err
+++ b/tests/qapi-schema/doc-empty-symbol.err
@@ -1 +1 @@
-tests/qapi-schema/doc-empty-symbol.json:4:1: invalid name
+doc-empty-symbol.json:4:1: invalid name
diff --git a/tests/qapi-schema/doc-empty-symbol.exit b/tests/qapi-schema/doc-empty-symbol.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-empty-symbol.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-good.exit b/tests/qapi-schema/doc-good.exit
deleted file mode 100644
index 573541ac97..0000000000
--- a/tests/qapi-schema/doc-good.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json
index f7fb48af38..7dc21e58a3 100644
--- a/tests/qapi-schema/doc-good.json
+++ b/tests/qapi-schema/doc-good.json
@@ -71,8 +71,12 @@
# A paragraph
#
# Another paragraph (but no @var: line)
+#
+# Features:
+# @variant1-feat: a feature
##
{ 'struct': 'Variant1',
+ 'features': [ 'variant1-feat' ],
'data': { 'var1': { 'type': 'str', 'if': 'defined(IFSTR)' } } }
##
@@ -104,6 +108,10 @@
#
# @arg2: the second
# argument
+#
+# Features:
+# @cmd-feat1: a feature
+# @cmd-feat2: another feature
# Note: @arg3 is undocumented
# Returns: @Object
# TODO: frobnicate
@@ -123,11 +131,15 @@
##
{ 'command': 'cmd',
'data': { 'arg1': 'int', '*arg2': 'str', 'arg3': 'bool' },
- 'returns': 'Object' }
+ 'returns': 'Object',
+ 'features': [ 'cmd-feat1', 'cmd-feat2' ] }
##
# @cmd-boxed:
# If you're bored enough to read this, go see a video of boxed cats
+# Features:
+# @cmd-feat1: a feature
+# @cmd-feat2: another feature
# Example:
#
# -> in
@@ -135,4 +147,5 @@
# <- out
##
{ 'command': 'cmd-boxed', 'boxed': true,
- 'data': 'Object' }
+ 'data': 'Object',
+ 'features': [ 'cmd-feat1', 'cmd-feat2' ] }
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index d3bca343eb..f78fdef6a9 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -20,6 +20,7 @@ object Base
object Variant1
member var1: str optional=False
if ['defined(IFSTR)']
+ feature variant1-feat
object Variant2
object Object
base Base
@@ -46,9 +47,13 @@ object q_obj_cmd-arg
member arg2: str optional=True
member arg3: bool optional=False
command cmd q_obj_cmd-arg -> Object
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
+ feature cmd-feat1
+ feature cmd-feat2
command cmd-boxed Object -> None
- gen=True success_response=True boxed=True oob=False preconfig=False
+ gen=True success_response=True boxed=True oob=False preconfig=False
+ feature cmd-feat1
+ feature cmd-feat2
doc freeform
body=
= Section
diff --git a/tests/qapi-schema/doc-good.texi b/tests/qapi-schema/doc-good.texi
index 2526abc6d9..2ce8b883c9 100644
--- a/tests/qapi-schema/doc-good.texi
+++ b/tests/qapi-schema/doc-good.texi
@@ -122,6 +122,12 @@ Not documented
@*@b{If:} @code{defined(IFSTR)}
@end table
+@b{Features:}
+@table @asis
+@item @code{variant1-feat}
+a feature
+@end table
+
@end deftp
@@ -182,6 +188,14 @@ argument
Not documented
@end table
+@b{Features:}
+@table @asis
+@item @code{cmd-feat1}
+a feature
+@item @code{cmd-feat2}
+another feature
+@end table
+
@b{Note:}
@code{arg3} is undocumented
@@ -227,6 +241,14 @@ If you're bored enough to read this, go see a video of boxed cats
@b{Arguments:} the members of @code{Object}
+@b{Features:}
+@table @asis
+@item @code{cmd-feat1}
+a feature
+@item @code{cmd-feat2}
+another feature
+@end table
+
@b{Example:}
@example
-> in
diff --git a/tests/qapi-schema/doc-interleaved-section.err b/tests/qapi-schema/doc-interleaved-section.err
index d373eabc55..715d58cd31 100644
--- a/tests/qapi-schema/doc-interleaved-section.err
+++ b/tests/qapi-schema/doc-interleaved-section.err
@@ -1 +1 @@
-tests/qapi-schema/doc-interleaved-section.json:15:1: '@foobar:' can't follow 'Note' section
+doc-interleaved-section.json:15:1: '@foobar:' can't follow 'Note' section
diff --git a/tests/qapi-schema/doc-interleaved-section.exit b/tests/qapi-schema/doc-interleaved-section.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-interleaved-section.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-invalid-end.err b/tests/qapi-schema/doc-invalid-end.err
index 6345aa6a0f..919e4d317e 100644
--- a/tests/qapi-schema/doc-invalid-end.err
+++ b/tests/qapi-schema/doc-invalid-end.err
@@ -1 +1 @@
-tests/qapi-schema/doc-invalid-end.json:5:2: documentation comment must end with '##'
+doc-invalid-end.json:5:2: documentation comment must end with '##'
diff --git a/tests/qapi-schema/doc-invalid-end.exit b/tests/qapi-schema/doc-invalid-end.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-invalid-end.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-invalid-end2.err b/tests/qapi-schema/doc-invalid-end2.err
index 13ead36fd2..d88a15cc8a 100644
--- a/tests/qapi-schema/doc-invalid-end2.err
+++ b/tests/qapi-schema/doc-invalid-end2.err
@@ -1 +1 @@
-tests/qapi-schema/doc-invalid-end2.json:5:1: junk after '##' at end of documentation comment
+doc-invalid-end2.json:5:1: junk after '##' at end of documentation comment
diff --git a/tests/qapi-schema/doc-invalid-end2.exit b/tests/qapi-schema/doc-invalid-end2.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-invalid-end2.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-invalid-return.err b/tests/qapi-schema/doc-invalid-return.err
index 5aaba33bb4..2ad89c5941 100644
--- a/tests/qapi-schema/doc-invalid-return.err
+++ b/tests/qapi-schema/doc-invalid-return.err
@@ -1 +1 @@
-tests/qapi-schema/doc-invalid-return.json:3: 'Returns:' is only valid for commands
+doc-invalid-return.json:3: 'Returns:' is only valid for commands
diff --git a/tests/qapi-schema/doc-invalid-return.exit b/tests/qapi-schema/doc-invalid-return.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-invalid-return.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-invalid-section.err b/tests/qapi-schema/doc-invalid-section.err
index bda93b44fd..d2d40e5704 100644
--- a/tests/qapi-schema/doc-invalid-section.err
+++ b/tests/qapi-schema/doc-invalid-section.err
@@ -1 +1 @@
-tests/qapi-schema/doc-invalid-section.json:5:1: '@note:' not allowed in free-form documentation
+doc-invalid-section.json:5:1: '@note:' not allowed in free-form documentation
diff --git a/tests/qapi-schema/doc-invalid-section.exit b/tests/qapi-schema/doc-invalid-section.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-invalid-section.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-invalid-start.err b/tests/qapi-schema/doc-invalid-start.err
index dcaa9699d6..276c033600 100644
--- a/tests/qapi-schema/doc-invalid-start.err
+++ b/tests/qapi-schema/doc-invalid-start.err
@@ -1 +1 @@
-tests/qapi-schema/doc-invalid-start.json:3:1: junk after '##' at start of documentation comment
+doc-invalid-start.json:3:1: junk after '##' at start of documentation comment
diff --git a/tests/qapi-schema/doc-invalid-start.exit b/tests/qapi-schema/doc-invalid-start.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-invalid-start.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-missing-colon.err b/tests/qapi-schema/doc-missing-colon.err
index 6fb5a380bd..cbcea00715 100644
--- a/tests/qapi-schema/doc-missing-colon.err
+++ b/tests/qapi-schema/doc-missing-colon.err
@@ -1 +1 @@
-tests/qapi-schema/doc-missing-colon.json:4:1: line should end with ':'
+doc-missing-colon.json:4:1: line should end with ':'
diff --git a/tests/qapi-schema/doc-missing-colon.exit b/tests/qapi-schema/doc-missing-colon.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-missing-colon.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-missing-expr.err b/tests/qapi-schema/doc-missing-expr.err
index 622a37cc6c..c9b32a96fa 100644
--- a/tests/qapi-schema/doc-missing-expr.err
+++ b/tests/qapi-schema/doc-missing-expr.err
@@ -1 +1 @@
-tests/qapi-schema/doc-missing-expr.json:3: documentation for 'bar' is not followed by the definition
+doc-missing-expr.json:3: documentation for 'bar' is not followed by the definition
diff --git a/tests/qapi-schema/doc-missing-expr.exit b/tests/qapi-schema/doc-missing-expr.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-missing-expr.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-missing-space.err b/tests/qapi-schema/doc-missing-space.err
index 1187ba12c4..350031d1d6 100644
--- a/tests/qapi-schema/doc-missing-space.err
+++ b/tests/qapi-schema/doc-missing-space.err
@@ -1 +1 @@
-tests/qapi-schema/doc-missing-space.json:5:1: missing space after #
+doc-missing-space.json:5:1: missing space after #
diff --git a/tests/qapi-schema/doc-missing-space.exit b/tests/qapi-schema/doc-missing-space.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-missing-space.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-missing.err b/tests/qapi-schema/doc-missing.err
index 7fbf54ff65..b89d925bcc 100644
--- a/tests/qapi-schema/doc-missing.err
+++ b/tests/qapi-schema/doc-missing.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/doc-missing.json: In command 'undocumented':
-tests/qapi-schema/doc-missing.json:5: documentation comment required
+doc-missing.json: In command 'undocumented':
+doc-missing.json:5: documentation comment required
diff --git a/tests/qapi-schema/doc-missing.exit b/tests/qapi-schema/doc-missing.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-missing.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/doc-no-symbol.err b/tests/qapi-schema/doc-no-symbol.err
index 9a3057730c..1b4fa14a22 100644
--- a/tests/qapi-schema/doc-no-symbol.err
+++ b/tests/qapi-schema/doc-no-symbol.err
@@ -1 +1 @@
-tests/qapi-schema/doc-no-symbol.json:3: definition documentation required
+doc-no-symbol.json:3: definition documentation required
diff --git a/tests/qapi-schema/doc-no-symbol.exit b/tests/qapi-schema/doc-no-symbol.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/doc-no-symbol.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err
index 23f88aae99..71fc4dbb52 100644
--- a/tests/qapi-schema/double-type.err
+++ b/tests/qapi-schema/double-type.err
@@ -1,3 +1,3 @@
-tests/qapi-schema/double-type.json: In struct 'bar':
-tests/qapi-schema/double-type.json:2: struct has unknown key 'command'
+double-type.json: In struct 'bar':
+double-type.json:2: struct has unknown key 'command'
Valid keys are 'base', 'data', 'features', 'if', 'struct'.
diff --git a/tests/qapi-schema/double-type.exit b/tests/qapi-schema/double-type.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/double-type.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/duplicate-key.err b/tests/qapi-schema/duplicate-key.err
index 7f34a34eb6..7ea8e95e8c 100644
--- a/tests/qapi-schema/duplicate-key.err
+++ b/tests/qapi-schema/duplicate-key.err
@@ -1 +1 @@
-tests/qapi-schema/duplicate-key.json:3:10: duplicate key 'key'
+duplicate-key.json:3:10: duplicate key 'key'
diff --git a/tests/qapi-schema/duplicate-key.exit b/tests/qapi-schema/duplicate-key.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/duplicate-key.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/empty.exit b/tests/qapi-schema/empty.exit
deleted file mode 100644
index 573541ac97..0000000000
--- a/tests/qapi-schema/empty.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/enum-bad-member.err b/tests/qapi-schema/enum-bad-member.err
index 2b1b4f98d0..f75d8c56ff 100644
--- a/tests/qapi-schema/enum-bad-member.err
+++ b/tests/qapi-schema/enum-bad-member.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/enum-bad-member.json: In enum 'MyEnum':
-tests/qapi-schema/enum-bad-member.json:2: 'data' member requires a string name
+enum-bad-member.json: In enum 'MyEnum':
+enum-bad-member.json:2: 'data' member requires a string name
diff --git a/tests/qapi-schema/enum-bad-member.exit b/tests/qapi-schema/enum-bad-member.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-bad-member.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/enum-bad-name.err b/tests/qapi-schema/enum-bad-name.err
index 3273a9808a..d26044b8e3 100644
--- a/tests/qapi-schema/enum-bad-name.err
+++ b/tests/qapi-schema/enum-bad-name.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/enum-bad-name.json: In enum 'MyEnum':
-tests/qapi-schema/enum-bad-name.json:3: 'data' member 'not\possible' has an invalid name
+enum-bad-name.json: In enum 'MyEnum':
+enum-bad-name.json:3: 'data' member 'not\possible' has an invalid name
diff --git a/tests/qapi-schema/enum-bad-name.exit b/tests/qapi-schema/enum-bad-name.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-bad-name.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/enum-bad-prefix.err b/tests/qapi-schema/enum-bad-prefix.err
index 933e33aa18..7a2b35787e 100644
--- a/tests/qapi-schema/enum-bad-prefix.err
+++ b/tests/qapi-schema/enum-bad-prefix.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/enum-bad-prefix.json: In enum 'MyEnum':
-tests/qapi-schema/enum-bad-prefix.json:2: 'prefix' must be a string
+enum-bad-prefix.json: In enum 'MyEnum':
+enum-bad-prefix.json:2: 'prefix' must be a string
diff --git a/tests/qapi-schema/enum-bad-prefix.exit b/tests/qapi-schema/enum-bad-prefix.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-bad-prefix.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/enum-clash-member.err b/tests/qapi-schema/enum-clash-member.err
index 84e02db82c..5986571427 100644
--- a/tests/qapi-schema/enum-clash-member.err
+++ b/tests/qapi-schema/enum-clash-member.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/enum-clash-member.json: In enum 'MyEnum':
-tests/qapi-schema/enum-clash-member.json:2: value 'one_two' collides with value 'one-two'
+enum-clash-member.json: In enum 'MyEnum':
+enum-clash-member.json:2: value 'one_two' collides with value 'one-two'
diff --git a/tests/qapi-schema/enum-clash-member.exit b/tests/qapi-schema/enum-clash-member.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-clash-member.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/enum-dict-member-unknown.err b/tests/qapi-schema/enum-dict-member-unknown.err
index 5df0236343..f8617ea179 100644
--- a/tests/qapi-schema/enum-dict-member-unknown.err
+++ b/tests/qapi-schema/enum-dict-member-unknown.err
@@ -1,3 +1,3 @@
-tests/qapi-schema/enum-dict-member-unknown.json: In enum 'MyEnum':
-tests/qapi-schema/enum-dict-member-unknown.json:2: 'data' member has unknown key 'bad-key'
+enum-dict-member-unknown.json: In enum 'MyEnum':
+enum-dict-member-unknown.json:2: 'data' member has unknown key 'bad-key'
Valid keys are 'if', 'name'.
diff --git a/tests/qapi-schema/enum-dict-member-unknown.exit b/tests/qapi-schema/enum-dict-member-unknown.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-dict-member-unknown.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/enum-if-invalid.err b/tests/qapi-schema/enum-if-invalid.err
index 30c1f0e91c..0556dc967b 100644
--- a/tests/qapi-schema/enum-if-invalid.err
+++ b/tests/qapi-schema/enum-if-invalid.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/enum-if-invalid.json: In enum 'TestIfEnum':
-tests/qapi-schema/enum-if-invalid.json:2: 'if' condition of 'data' member 'bar' must be a string or a list of strings
+enum-if-invalid.json: In enum 'TestIfEnum':
+enum-if-invalid.json:2: 'if' condition of 'data' member 'bar' must be a string or a list of strings
diff --git a/tests/qapi-schema/enum-if-invalid.exit b/tests/qapi-schema/enum-if-invalid.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-if-invalid.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/enum-int-member.err b/tests/qapi-schema/enum-int-member.err
index 27f06e334c..3a15294963 100644
--- a/tests/qapi-schema/enum-int-member.err
+++ b/tests/qapi-schema/enum-int-member.err
@@ -1 +1 @@
-tests/qapi-schema/enum-int-member.json:3:31: stray '1'
+enum-int-member.json:3:31: stray '1'
diff --git a/tests/qapi-schema/enum-int-member.exit b/tests/qapi-schema/enum-int-member.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-int-member.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/enum-member-case.err b/tests/qapi-schema/enum-member-case.err
index e6b080c6e5..8b3aefe37a 100644
--- a/tests/qapi-schema/enum-member-case.err
+++ b/tests/qapi-schema/enum-member-case.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/enum-member-case.json: In enum 'NoWayThisWillGetWhitelisted':
-tests/qapi-schema/enum-member-case.json:4: 'data' member 'Value' uses uppercase in name
+enum-member-case.json: In enum 'NoWayThisWillGetWhitelisted':
+enum-member-case.json:4: 'data' member 'Value' uses uppercase in name
diff --git a/tests/qapi-schema/enum-member-case.exit b/tests/qapi-schema/enum-member-case.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-member-case.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/enum-missing-data.err b/tests/qapi-schema/enum-missing-data.err
index 4809b01f34..ec4bdae037 100644
--- a/tests/qapi-schema/enum-missing-data.err
+++ b/tests/qapi-schema/enum-missing-data.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/enum-missing-data.json: In enum 'MyEnum':
-tests/qapi-schema/enum-missing-data.json:2: enum misses key 'data'
+enum-missing-data.json: In enum 'MyEnum':
+enum-missing-data.json:2: enum misses key 'data'
diff --git a/tests/qapi-schema/enum-missing-data.exit b/tests/qapi-schema/enum-missing-data.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-missing-data.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/enum-wrong-data.err b/tests/qapi-schema/enum-wrong-data.err
index ad5f0ce46f..a9f34481ba 100644
--- a/tests/qapi-schema/enum-wrong-data.err
+++ b/tests/qapi-schema/enum-wrong-data.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/enum-wrong-data.json: In enum 'MyEnum':
-tests/qapi-schema/enum-wrong-data.json:2: 'data' must be an array
+enum-wrong-data.json: In enum 'MyEnum':
+enum-wrong-data.json:2: 'data' must be an array
diff --git a/tests/qapi-schema/enum-wrong-data.exit b/tests/qapi-schema/enum-wrong-data.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/enum-wrong-data.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/escape-outside-string.err b/tests/qapi-schema/escape-outside-string.err
index 06f5f2ed55..c49fe0c285 100644
--- a/tests/qapi-schema/escape-outside-string.err
+++ b/tests/qapi-schema/escape-outside-string.err
@@ -1 +1 @@
-tests/qapi-schema/escape-outside-string.json:3:27: stray '\'
+escape-outside-string.json:3:27: stray '\'
diff --git a/tests/qapi-schema/event-boxed-empty.err b/tests/qapi-schema/event-boxed-empty.err
index 931c10b036..b3872cdab5 100644
--- a/tests/qapi-schema/event-boxed-empty.err
+++ b/tests/qapi-schema/event-boxed-empty.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/event-boxed-empty.json: In event 'FOO':
-tests/qapi-schema/event-boxed-empty.json:2: 'boxed': true requires 'data'
+event-boxed-empty.json: In event 'FOO':
+event-boxed-empty.json:2: 'boxed': true requires 'data'
diff --git a/tests/qapi-schema/event-boxed-empty.exit b/tests/qapi-schema/event-boxed-empty.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/event-boxed-empty.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/event-case.exit b/tests/qapi-schema/event-case.exit
deleted file mode 100644
index 573541ac97..0000000000
--- a/tests/qapi-schema/event-case.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/event-case.out b/tests/qapi-schema/event-case.out
index ec8a1406e4..42ae519656 100644
--- a/tests/qapi-schema/event-case.out
+++ b/tests/qapi-schema/event-case.out
@@ -11,4 +11,4 @@ enum QType
member qbool
module event-case.json
event oops None
- boxed=False
+ boxed=False
diff --git a/tests/qapi-schema/event-member-invalid-dict.err b/tests/qapi-schema/event-member-invalid-dict.err
index 8406c43df7..c7a6a24305 100644
--- a/tests/qapi-schema/event-member-invalid-dict.err
+++ b/tests/qapi-schema/event-member-invalid-dict.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/event-member-invalid-dict.json: In event 'EVENT_A':
-tests/qapi-schema/event-member-invalid-dict.json:1: 'data' member 'a' misses key 'type'
+event-member-invalid-dict.json: In event 'EVENT_A':
+event-member-invalid-dict.json:1: 'data' member 'a' misses key 'type'
diff --git a/tests/qapi-schema/event-member-invalid-dict.exit b/tests/qapi-schema/event-member-invalid-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/event-member-invalid-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/event-nest-struct.err b/tests/qapi-schema/event-nest-struct.err
index 1a3254a73c..8c5f6ed311 100644
--- a/tests/qapi-schema/event-nest-struct.err
+++ b/tests/qapi-schema/event-nest-struct.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/event-nest-struct.json: In event 'EVENT_A':
-tests/qapi-schema/event-nest-struct.json:1: 'data' member 'a' should be a type name
+event-nest-struct.json: In event 'EVENT_A':
+event-nest-struct.json:1: 'data' member 'a' should be a type name
diff --git a/tests/qapi-schema/event-nest-struct.exit b/tests/qapi-schema/event-nest-struct.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/event-nest-struct.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/features-bad-type.err b/tests/qapi-schema/features-bad-type.err
index 30deb8b624..3c63591632 100644
--- a/tests/qapi-schema/features-bad-type.err
+++ b/tests/qapi-schema/features-bad-type.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/features-bad-type.json: In struct 'FeatureStruct0':
-tests/qapi-schema/features-bad-type.json:1: 'features' member requires a string name
+features-bad-type.json: In struct 'FeatureStruct0':
+features-bad-type.json:1: 'features' member requires a string name
diff --git a/tests/qapi-schema/features-bad-type.exit b/tests/qapi-schema/features-bad-type.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/features-bad-type.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/features-duplicate-name.err b/tests/qapi-schema/features-duplicate-name.err
index a99bbde737..0adbee6b0a 100644
--- a/tests/qapi-schema/features-duplicate-name.err
+++ b/tests/qapi-schema/features-duplicate-name.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/features-duplicate-name.json: In struct 'FeatureStruct0':
-tests/qapi-schema/features-duplicate-name.json:1: feature 'foo' collides with feature 'foo'
+features-duplicate-name.json: In struct 'FeatureStruct0':
+features-duplicate-name.json:1: feature 'foo' collides with feature 'foo'
diff --git a/tests/qapi-schema/features-duplicate-name.exit b/tests/qapi-schema/features-duplicate-name.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/features-duplicate-name.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/features-if-invalid.err b/tests/qapi-schema/features-if-invalid.err
index ffb39378af..f63b89535e 100644
--- a/tests/qapi-schema/features-if-invalid.err
+++ b/tests/qapi-schema/features-if-invalid.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/features-if-invalid.json: In struct 'Stru':
-tests/qapi-schema/features-if-invalid.json:2: 'if' condition of 'features' member 'f' must be a string or a list of strings
+features-if-invalid.json: In struct 'Stru':
+features-if-invalid.json:2: 'if' condition of 'features' member 'f' must be a string or a list of strings
diff --git a/tests/qapi-schema/features-if-invalid.exit b/tests/qapi-schema/features-if-invalid.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/features-if-invalid.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/features-missing-name.err b/tests/qapi-schema/features-missing-name.err
index b8db328acc..ce02412d2e 100644
--- a/tests/qapi-schema/features-missing-name.err
+++ b/tests/qapi-schema/features-missing-name.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/features-missing-name.json: In struct 'FeatureStruct0':
-tests/qapi-schema/features-missing-name.json:1: 'features' member misses key 'name'
+features-missing-name.json: In struct 'FeatureStruct0':
+features-missing-name.json:1: 'features' member misses key 'name'
diff --git a/tests/qapi-schema/features-missing-name.exit b/tests/qapi-schema/features-missing-name.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/features-missing-name.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/features-name-bad-type.err b/tests/qapi-schema/features-name-bad-type.err
index 86db2c0ea2..4afcd5fdc3 100644
--- a/tests/qapi-schema/features-name-bad-type.err
+++ b/tests/qapi-schema/features-name-bad-type.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/features-name-bad-type.json: In struct 'FeatureStruct0':
-tests/qapi-schema/features-name-bad-type.json:1: 'features' member requires a string name
+features-name-bad-type.json: In struct 'FeatureStruct0':
+features-name-bad-type.json:1: 'features' member requires a string name
diff --git a/tests/qapi-schema/features-name-bad-type.exit b/tests/qapi-schema/features-name-bad-type.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/features-name-bad-type.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/features-no-list.err b/tests/qapi-schema/features-no-list.err
index e493f85057..5cfaa3f216 100644
--- a/tests/qapi-schema/features-no-list.err
+++ b/tests/qapi-schema/features-no-list.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/features-no-list.json: In struct 'FeatureStruct0':
-tests/qapi-schema/features-no-list.json:1: 'features' must be an array
+features-no-list.json: In struct 'FeatureStruct0':
+features-no-list.json:1: 'features' must be an array
diff --git a/tests/qapi-schema/features-no-list.exit b/tests/qapi-schema/features-no-list.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/features-no-list.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/features-unknown-key.err b/tests/qapi-schema/features-unknown-key.err
index 22f5dcf4b0..13e359a216 100644
--- a/tests/qapi-schema/features-unknown-key.err
+++ b/tests/qapi-schema/features-unknown-key.err
@@ -1,3 +1,3 @@
-tests/qapi-schema/features-unknown-key.json: In struct 'FeatureStruct0':
-tests/qapi-schema/features-unknown-key.json:1: 'features' member has unknown key 'colour'
+features-unknown-key.json: In struct 'FeatureStruct0':
+features-unknown-key.json:1: 'features' member has unknown key 'colour'
Valid keys are 'if', 'name'.
diff --git a/tests/qapi-schema/features-unknown-key.exit b/tests/qapi-schema/features-unknown-key.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/features-unknown-key.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-array-branch.err b/tests/qapi-schema/flat-union-array-branch.err
index de07a7b32a..20a8ef1406 100644
--- a/tests/qapi-schema/flat-union-array-branch.err
+++ b/tests/qapi-schema/flat-union-array-branch.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-array-branch.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-array-branch.json:8: 'data' member 'value1' cannot be an array
+flat-union-array-branch.json: In union 'TestUnion':
+flat-union-array-branch.json:8: 'data' member 'value1' cannot be an array
diff --git a/tests/qapi-schema/flat-union-array-branch.exit b/tests/qapi-schema/flat-union-array-branch.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-array-branch.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-bad-base.err b/tests/qapi-schema/flat-union-bad-base.err
index 5da7602c20..e0a205a58c 100644
--- a/tests/qapi-schema/flat-union-bad-base.err
+++ b/tests/qapi-schema/flat-union-bad-base.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-bad-base.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-bad-base.json:8: member 'string' of type 'TestTypeA' collides with base member 'string'
+flat-union-bad-base.json: In union 'TestUnion':
+flat-union-bad-base.json:8: member 'string' of type 'TestTypeA' collides with base member 'string'
diff --git a/tests/qapi-schema/flat-union-bad-base.exit b/tests/qapi-schema/flat-union-bad-base.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-bad-base.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err
index c1b4209ffd..b705439bd9 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.err
+++ b/tests/qapi-schema/flat-union-bad-discriminator.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-bad-discriminator.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-bad-discriminator.json:11: 'discriminator' requires a string name
+flat-union-bad-discriminator.json: In union 'TestUnion':
+flat-union-bad-discriminator.json:11: 'discriminator' requires a string name
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.exit b/tests/qapi-schema/flat-union-bad-discriminator.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-bad-discriminator.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-base-any.err b/tests/qapi-schema/flat-union-base-any.err
index 7ab3402396..c2d4de6a5d 100644
--- a/tests/qapi-schema/flat-union-base-any.err
+++ b/tests/qapi-schema/flat-union-base-any.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-base-any.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-base-any.json:8: 'base' requires a struct type, built-in type 'any' isn't
+flat-union-base-any.json: In union 'TestUnion':
+flat-union-base-any.json:8: 'base' requires a struct type, built-in type 'any' isn't
diff --git a/tests/qapi-schema/flat-union-base-any.exit b/tests/qapi-schema/flat-union-base-any.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-base-any.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-base-union.err b/tests/qapi-schema/flat-union-base-union.err
index 5db7b1e404..3b0087220e 100644
--- a/tests/qapi-schema/flat-union-base-union.err
+++ b/tests/qapi-schema/flat-union-base-union.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-base-union.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-base-union.json:14: 'base' requires a struct type, union type 'UnionBase' isn't
+flat-union-base-union.json: In union 'TestUnion':
+flat-union-base-union.json:14: 'base' requires a struct type, union type 'UnionBase' isn't
diff --git a/tests/qapi-schema/flat-union-base-union.exit b/tests/qapi-schema/flat-union-base-union.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-base-union.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-clash-member.err b/tests/qapi-schema/flat-union-clash-member.err
index 40f10681f8..07551e6ef5 100644
--- a/tests/qapi-schema/flat-union-clash-member.err
+++ b/tests/qapi-schema/flat-union-clash-member.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-clash-member.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-clash-member.json:11: member 'name' of type 'Branch1' collides with member 'name' of type 'Base'
+flat-union-clash-member.json: In union 'TestUnion':
+flat-union-clash-member.json:11: member 'name' of type 'Branch1' collides with member 'name' of type 'Base'
diff --git a/tests/qapi-schema/flat-union-clash-member.exit b/tests/qapi-schema/flat-union-clash-member.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-clash-member.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-discriminator-bad-name.err b/tests/qapi-schema/flat-union-discriminator-bad-name.err
index 2a0deb6a0e..28be49c31a 100644
--- a/tests/qapi-schema/flat-union-discriminator-bad-name.err
+++ b/tests/qapi-schema/flat-union-discriminator-bad-name.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-discriminator-bad-name.json: In union 'MyUnion':
-tests/qapi-schema/flat-union-discriminator-bad-name.json:6: discriminator '*switch' is not a member of 'base'
+flat-union-discriminator-bad-name.json: In union 'MyUnion':
+flat-union-discriminator-bad-name.json:6: discriminator '*switch' is not a member of 'base'
diff --git a/tests/qapi-schema/flat-union-discriminator-bad-name.exit b/tests/qapi-schema/flat-union-discriminator-bad-name.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-discriminator-bad-name.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-empty.err b/tests/qapi-schema/flat-union-empty.err
index 91a5b57f5d..89b0f25cb0 100644
--- a/tests/qapi-schema/flat-union-empty.err
+++ b/tests/qapi-schema/flat-union-empty.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-empty.json: In union 'Union':
-tests/qapi-schema/flat-union-empty.json:4: union has no branches
+flat-union-empty.json: In union 'Union':
+flat-union-empty.json:4: union has no branches
diff --git a/tests/qapi-schema/flat-union-empty.exit b/tests/qapi-schema/flat-union-empty.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-empty.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-inline-invalid-dict.err b/tests/qapi-schema/flat-union-inline-invalid-dict.err
index d353bdd338..53e5416707 100644
--- a/tests/qapi-schema/flat-union-inline-invalid-dict.err
+++ b/tests/qapi-schema/flat-union-inline-invalid-dict.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-inline-invalid-dict.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-inline-invalid-dict.json:7: 'data' member 'value1' misses key 'type'
+flat-union-inline-invalid-dict.json: In union 'TestUnion':
+flat-union-inline-invalid-dict.json:7: 'data' member 'value1' misses key 'type'
diff --git a/tests/qapi-schema/flat-union-inline-invalid-dict.exit b/tests/qapi-schema/flat-union-inline-invalid-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-inline-invalid-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err
index 95b1e8c1b7..538283f5db 100644
--- a/tests/qapi-schema/flat-union-inline.err
+++ b/tests/qapi-schema/flat-union-inline.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-inline.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-inline.json:7: 'data' member 'value1' should be a type name
+flat-union-inline.json: In union 'TestUnion':
+flat-union-inline.json:7: 'data' member 'value1' should be a type name
diff --git a/tests/qapi-schema/flat-union-inline.exit b/tests/qapi-schema/flat-union-inline.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-inline.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-int-branch.err b/tests/qapi-schema/flat-union-int-branch.err
index 416b696c8f..ae7f800603 100644
--- a/tests/qapi-schema/flat-union-int-branch.err
+++ b/tests/qapi-schema/flat-union-int-branch.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-int-branch.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-int-branch.json:8: branch 'value1' cannot use built-in type 'int'
+flat-union-int-branch.json: In union 'TestUnion':
+flat-union-int-branch.json:8: branch 'value1' cannot use built-in type 'int'
diff --git a/tests/qapi-schema/flat-union-int-branch.exit b/tests/qapi-schema/flat-union-int-branch.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-int-branch.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-invalid-branch-key.err b/tests/qapi-schema/flat-union-invalid-branch-key.err
index 6997b3387d..5576a25f9b 100644
--- a/tests/qapi-schema/flat-union-invalid-branch-key.err
+++ b/tests/qapi-schema/flat-union-invalid-branch-key.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-invalid-branch-key.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-invalid-branch-key.json:13: branch 'value_wrong' is not a value of enum type 'TestEnum'
+flat-union-invalid-branch-key.json: In union 'TestUnion':
+flat-union-invalid-branch-key.json:13: branch 'value_wrong' is not a value of enum type 'TestEnum'
diff --git a/tests/qapi-schema/flat-union-invalid-branch-key.exit b/tests/qapi-schema/flat-union-invalid-branch-key.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-invalid-branch-key.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-invalid-discriminator.err b/tests/qapi-schema/flat-union-invalid-discriminator.err
index 3f80de3044..99bca2ddab 100644
--- a/tests/qapi-schema/flat-union-invalid-discriminator.err
+++ b/tests/qapi-schema/flat-union-invalid-discriminator.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-invalid-discriminator.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-invalid-discriminator.json:10: discriminator 'enum_wrong' is not a member of 'base'
+flat-union-invalid-discriminator.json: In union 'TestUnion':
+flat-union-invalid-discriminator.json:10: discriminator 'enum_wrong' is not a member of 'base'
diff --git a/tests/qapi-schema/flat-union-invalid-discriminator.exit b/tests/qapi-schema/flat-union-invalid-discriminator.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-invalid-discriminator.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-invalid-if-discriminator.err b/tests/qapi-schema/flat-union-invalid-if-discriminator.err
index d2b35be9ae..350f28da9d 100644
--- a/tests/qapi-schema/flat-union-invalid-if-discriminator.err
+++ b/tests/qapi-schema/flat-union-invalid-if-discriminator.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-invalid-if-discriminator.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-invalid-if-discriminator.json:10: discriminator member 'enum1' of 'base' must not be conditional
+flat-union-invalid-if-discriminator.json: In union 'TestUnion':
+flat-union-invalid-if-discriminator.json:10: discriminator member 'enum1' of 'base' must not be conditional
diff --git a/tests/qapi-schema/flat-union-invalid-if-discriminator.exit b/tests/qapi-schema/flat-union-invalid-if-discriminator.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-invalid-if-discriminator.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-no-base.err b/tests/qapi-schema/flat-union-no-base.err
index a16f3231f1..9bd595bcfb 100644
--- a/tests/qapi-schema/flat-union-no-base.err
+++ b/tests/qapi-schema/flat-union-no-base.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-no-base.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-no-base.json:9: 'discriminator' requires 'base'
+flat-union-no-base.json: In union 'TestUnion':
+flat-union-no-base.json:9: 'discriminator' requires 'base'
diff --git a/tests/qapi-schema/flat-union-no-base.exit b/tests/qapi-schema/flat-union-no-base.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-no-base.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.err b/tests/qapi-schema/flat-union-optional-discriminator.err
index 49fbf5b04d..3d60a1b496 100644
--- a/tests/qapi-schema/flat-union-optional-discriminator.err
+++ b/tests/qapi-schema/flat-union-optional-discriminator.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-optional-discriminator.json: In union 'MyUnion':
-tests/qapi-schema/flat-union-optional-discriminator.json:6: discriminator member 'switch' of base type 'Base' must not be optional
+flat-union-optional-discriminator.json: In union 'MyUnion':
+flat-union-optional-discriminator.json:6: discriminator member 'switch' of base type 'Base' must not be optional
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.exit b/tests/qapi-schema/flat-union-optional-discriminator.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-optional-discriminator.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-string-discriminator.err b/tests/qapi-schema/flat-union-string-discriminator.err
index fb499188aa..ff42c9728b 100644
--- a/tests/qapi-schema/flat-union-string-discriminator.err
+++ b/tests/qapi-schema/flat-union-string-discriminator.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/flat-union-string-discriminator.json: In union 'TestUnion':
-tests/qapi-schema/flat-union-string-discriminator.json:13: discriminator member 'kind' of base type 'TestBase' must be of enum type
+flat-union-string-discriminator.json: In union 'TestUnion':
+flat-union-string-discriminator.json:13: discriminator member 'kind' of base type 'TestBase' must be of enum type
diff --git a/tests/qapi-schema/flat-union-string-discriminator.exit b/tests/qapi-schema/flat-union-string-discriminator.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/flat-union-string-discriminator.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/funny-char.err b/tests/qapi-schema/funny-char.err
index 132fac93ea..1c8abae2e0 100644
--- a/tests/qapi-schema/funny-char.err
+++ b/tests/qapi-schema/funny-char.err
@@ -1 +1 @@
-tests/qapi-schema/funny-char.json:2:36: stray ';'
+funny-char.json:2:36: stray ';'
diff --git a/tests/qapi-schema/funny-char.exit b/tests/qapi-schema/funny-char.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/funny-char.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/funny-word.err b/tests/qapi-schema/funny-word.err
index d9286c8962..a1d9ade970 100644
--- a/tests/qapi-schema/funny-word.err
+++ b/tests/qapi-schema/funny-word.err
@@ -1 +1 @@
-tests/qapi-schema/funny-word.json:1:3: stray 'command'
+funny-word.json:1:3: stray 'command'
diff --git a/tests/qapi-schema/funny-word.exit b/tests/qapi-schema/funny-word.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/funny-word.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/ident-with-escape.err b/tests/qapi-schema/ident-with-escape.err
index 1117283c81..3f6c1e423c 100644
--- a/tests/qapi-schema/ident-with-escape.err
+++ b/tests/qapi-schema/ident-with-escape.err
@@ -1 +1 @@
-tests/qapi-schema/ident-with-escape.json:3:3: unknown escape \u
+ident-with-escape.json:3:3: unknown escape \u
diff --git a/tests/qapi-schema/ident-with-escape.exit b/tests/qapi-schema/ident-with-escape.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/ident-with-escape.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/include-before-err.err b/tests/qapi-schema/include-before-err.err
index 098314bc49..16da03026f 100644
--- a/tests/qapi-schema/include-before-err.err
+++ b/tests/qapi-schema/include-before-err.err
@@ -1 +1 @@
-tests/qapi-schema/include-before-err.json:2:13: expected ':'
+include-before-err.json:2:13: expected ':'
diff --git a/tests/qapi-schema/include-before-err.exit b/tests/qapi-schema/include-before-err.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/include-before-err.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/include-cycle.err b/tests/qapi-schema/include-cycle.err
index 52028669b5..4c99f5642b 100644
--- a/tests/qapi-schema/include-cycle.err
+++ b/tests/qapi-schema/include-cycle.err
@@ -1,3 +1,3 @@
-In file included from tests/qapi-schema/include-cycle.json:1:
-In file included from tests/qapi-schema/include-cycle-b.json:1:
-tests/qapi-schema/include-cycle-c.json:1: inclusion loop for include-cycle.json
+In file included from include-cycle.json:1:
+In file included from include-cycle-b.json:1:
+include-cycle-c.json:1: inclusion loop for include-cycle.json
diff --git a/tests/qapi-schema/include-cycle.exit b/tests/qapi-schema/include-cycle.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/include-cycle.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/include-extra-junk.err b/tests/qapi-schema/include-extra-junk.err
index 854cec3ce3..1f7d2e1437 100644
--- a/tests/qapi-schema/include-extra-junk.err
+++ b/tests/qapi-schema/include-extra-junk.err
@@ -1 +1 @@
-tests/qapi-schema/include-extra-junk.json:3: invalid 'include' directive
+include-extra-junk.json:3: invalid 'include' directive
diff --git a/tests/qapi-schema/include-extra-junk.exit b/tests/qapi-schema/include-extra-junk.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/include-extra-junk.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/include-nested-err.err b/tests/qapi-schema/include-nested-err.err
index 11928b4f7f..9eac9eaeea 100644
--- a/tests/qapi-schema/include-nested-err.err
+++ b/tests/qapi-schema/include-nested-err.err
@@ -1,2 +1,2 @@
-In file included from tests/qapi-schema/include-nested-err.json:1:
-tests/qapi-schema/missing-colon.json:1:10: expected ':'
+In file included from include-nested-err.json:1:
+missing-colon.json:1:10: expected ':'
diff --git a/tests/qapi-schema/include-nested-err.exit b/tests/qapi-schema/include-nested-err.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/include-nested-err.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/include-no-file.err b/tests/qapi-schema/include-no-file.err
index 0a6c6bb4a9..3115a889fe 100644
--- a/tests/qapi-schema/include-no-file.err
+++ b/tests/qapi-schema/include-no-file.err
@@ -1 +1 @@
-tests/qapi-schema/include-no-file.json:1: can't read include file 'tests/qapi-schema/include-no-file-sub.json': No such file or directory
+include-no-file.json:1: can't read include file 'include-no-file-sub.json': No such file or directory
diff --git a/tests/qapi-schema/include-no-file.exit b/tests/qapi-schema/include-no-file.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/include-no-file.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/include-non-file.err b/tests/qapi-schema/include-non-file.err
index 65dd3c7c2c..ec8a331654 100644
--- a/tests/qapi-schema/include-non-file.err
+++ b/tests/qapi-schema/include-non-file.err
@@ -1 +1 @@
-tests/qapi-schema/include-non-file.json:1: value of 'include' must be a string
+include-non-file.json:1: value of 'include' must be a string
diff --git a/tests/qapi-schema/include-non-file.exit b/tests/qapi-schema/include-non-file.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/include-non-file.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/include-repetition.exit b/tests/qapi-schema/include-repetition.exit
deleted file mode 100644
index 573541ac97..0000000000
--- a/tests/qapi-schema/include-repetition.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/include-self-cycle.err b/tests/qapi-schema/include-self-cycle.err
index c84795d1dc..284b0d6f0e 100644
--- a/tests/qapi-schema/include-self-cycle.err
+++ b/tests/qapi-schema/include-self-cycle.err
@@ -1 +1 @@
-tests/qapi-schema/include-self-cycle.json:1: inclusion loop for include-self-cycle.json
+include-self-cycle.json:1: inclusion loop for include-self-cycle.json
diff --git a/tests/qapi-schema/include-self-cycle.exit b/tests/qapi-schema/include-self-cycle.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/include-self-cycle.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/include-simple.exit b/tests/qapi-schema/include-simple.exit
deleted file mode 100644
index 573541ac97..0000000000
--- a/tests/qapi-schema/include-simple.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/indented-expr.exit b/tests/qapi-schema/indented-expr.exit
deleted file mode 100644
index 573541ac97..0000000000
--- a/tests/qapi-schema/indented-expr.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/indented-expr.out b/tests/qapi-schema/indented-expr.out
index bffdf6756d..04356775cd 100644
--- a/tests/qapi-schema/indented-expr.out
+++ b/tests/qapi-schema/indented-expr.out
@@ -11,6 +11,6 @@ enum QType
member qbool
module indented-expr.json
command eins None -> None
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
command zwei None -> None
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
diff --git a/tests/qapi-schema/leading-comma-list.err b/tests/qapi-schema/leading-comma-list.err
index cddf471f71..76eed2b5b3 100644
--- a/tests/qapi-schema/leading-comma-list.err
+++ b/tests/qapi-schema/leading-comma-list.err
@@ -1 +1 @@
-tests/qapi-schema/leading-comma-list.json:2:13: expected '{', '[', ']', string, boolean or 'null'
+leading-comma-list.json:2:13: expected '{', '[', ']', string, boolean or 'null'
diff --git a/tests/qapi-schema/leading-comma-list.exit b/tests/qapi-schema/leading-comma-list.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/leading-comma-list.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/leading-comma-object.err b/tests/qapi-schema/leading-comma-object.err
index 2f3b193274..25f8b6ffd6 100644
--- a/tests/qapi-schema/leading-comma-object.err
+++ b/tests/qapi-schema/leading-comma-object.err
@@ -1 +1 @@
-tests/qapi-schema/leading-comma-object.json:1:3: expected string or '}'
+leading-comma-object.json:1:3: expected string or '}'
diff --git a/tests/qapi-schema/leading-comma-object.exit b/tests/qapi-schema/leading-comma-object.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/leading-comma-object.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/missing-colon.err b/tests/qapi-schema/missing-colon.err
index e642c7c549..d5fe235cb9 100644
--- a/tests/qapi-schema/missing-colon.err
+++ b/tests/qapi-schema/missing-colon.err
@@ -1 +1 @@
-tests/qapi-schema/missing-colon.json:1:10: expected ':'
+missing-colon.json:1:10: expected ':'
diff --git a/tests/qapi-schema/missing-colon.exit b/tests/qapi-schema/missing-colon.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/missing-colon.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/missing-comma-list.err b/tests/qapi-schema/missing-comma-list.err
index 5359499430..d0a790c2b3 100644
--- a/tests/qapi-schema/missing-comma-list.err
+++ b/tests/qapi-schema/missing-comma-list.err
@@ -1 +1 @@
-tests/qapi-schema/missing-comma-list.json:2:20: expected ',' or ']'
+missing-comma-list.json:2:20: expected ',' or ']'
diff --git a/tests/qapi-schema/missing-comma-list.exit b/tests/qapi-schema/missing-comma-list.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/missing-comma-list.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/missing-comma-object.err b/tests/qapi-schema/missing-comma-object.err
index c9b02b0760..aa8efbc2f3 100644
--- a/tests/qapi-schema/missing-comma-object.err
+++ b/tests/qapi-schema/missing-comma-object.err
@@ -1 +1 @@
-tests/qapi-schema/missing-comma-object.json:2:3: expected ',' or '}'
+missing-comma-object.json:2:3: expected ',' or '}'
diff --git a/tests/qapi-schema/missing-comma-object.exit b/tests/qapi-schema/missing-comma-object.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/missing-comma-object.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/missing-type.err b/tests/qapi-schema/missing-type.err
index 19b7c495e7..5755386a18 100644
--- a/tests/qapi-schema/missing-type.err
+++ b/tests/qapi-schema/missing-type.err
@@ -1 +1 @@
-tests/qapi-schema/missing-type.json:2: expression is missing metatype
+missing-type.json:2: expression is missing metatype
diff --git a/tests/qapi-schema/missing-type.exit b/tests/qapi-schema/missing-type.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/missing-type.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/nested-struct-data-invalid-dict.err b/tests/qapi-schema/nested-struct-data-invalid-dict.err
index ed42d6323e..c044b2b17a 100644
--- a/tests/qapi-schema/nested-struct-data-invalid-dict.err
+++ b/tests/qapi-schema/nested-struct-data-invalid-dict.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/nested-struct-data-invalid-dict.json: In command 'foo':
-tests/qapi-schema/nested-struct-data-invalid-dict.json:2: 'data' member 'a' misses key 'type'
+nested-struct-data-invalid-dict.json: In command 'foo':
+nested-struct-data-invalid-dict.json:2: 'data' member 'a' misses key 'type'
diff --git a/tests/qapi-schema/nested-struct-data-invalid-dict.exit b/tests/qapi-schema/nested-struct-data-invalid-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/nested-struct-data-invalid-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/nested-struct-data.err b/tests/qapi-schema/nested-struct-data.err
index b0ec410eb7..c7258a0182 100644
--- a/tests/qapi-schema/nested-struct-data.err
+++ b/tests/qapi-schema/nested-struct-data.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/nested-struct-data.json: In command 'foo':
-tests/qapi-schema/nested-struct-data.json:2: 'data' member 'a' should be a type name
+nested-struct-data.json: In command 'foo':
+nested-struct-data.json:2: 'data' member 'a' should be a type name
diff --git a/tests/qapi-schema/nested-struct-data.exit b/tests/qapi-schema/nested-struct-data.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/nested-struct-data.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/non-objects.err b/tests/qapi-schema/non-objects.err
index 9237af6939..3a4ea36966 100644
--- a/tests/qapi-schema/non-objects.err
+++ b/tests/qapi-schema/non-objects.err
@@ -1 +1 @@
-tests/qapi-schema/non-objects.json:1:1: expected '{'
+non-objects.json:1:1: expected '{'
diff --git a/tests/qapi-schema/non-objects.exit b/tests/qapi-schema/non-objects.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/non-objects.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/oob-test.err b/tests/qapi-schema/oob-test.err
index 3c2ba6e0fd..7b9a50b3d5 100644
--- a/tests/qapi-schema/oob-test.err
+++ b/tests/qapi-schema/oob-test.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/oob-test.json: In command 'oob-command-1':
-tests/qapi-schema/oob-test.json:2: flag 'allow-oob' may only use true value
+oob-test.json: In command 'oob-command-1':
+oob-test.json:2: flag 'allow-oob' may only use true value
diff --git a/tests/qapi-schema/oob-test.exit b/tests/qapi-schema/oob-test.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/oob-test.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/pragma-doc-required-crap.err b/tests/qapi-schema/pragma-doc-required-crap.err
index bcd981ada8..717062cb14 100644
--- a/tests/qapi-schema/pragma-doc-required-crap.err
+++ b/tests/qapi-schema/pragma-doc-required-crap.err
@@ -1 +1 @@
-tests/qapi-schema/pragma-doc-required-crap.json:3: pragma 'doc-required' must be boolean
+pragma-doc-required-crap.json:3: pragma 'doc-required' must be boolean
diff --git a/tests/qapi-schema/pragma-doc-required-crap.exit b/tests/qapi-schema/pragma-doc-required-crap.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/pragma-doc-required-crap.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/pragma-extra-junk.err b/tests/qapi-schema/pragma-extra-junk.err
index 3ae48d3668..ba5cc23f56 100644
--- a/tests/qapi-schema/pragma-extra-junk.err
+++ b/tests/qapi-schema/pragma-extra-junk.err
@@ -1 +1 @@
-tests/qapi-schema/pragma-extra-junk.json:3: invalid 'pragma' directive
+pragma-extra-junk.json:3: invalid 'pragma' directive
diff --git a/tests/qapi-schema/pragma-extra-junk.exit b/tests/qapi-schema/pragma-extra-junk.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/pragma-extra-junk.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/pragma-name-case-whitelist-crap.err b/tests/qapi-schema/pragma-name-case-whitelist-crap.err
index 81f829ff36..fbea90d6c5 100644
--- a/tests/qapi-schema/pragma-name-case-whitelist-crap.err
+++ b/tests/qapi-schema/pragma-name-case-whitelist-crap.err
@@ -1 +1 @@
-tests/qapi-schema/pragma-name-case-whitelist-crap.json:3: pragma name-case-whitelist must be a list of strings
+pragma-name-case-whitelist-crap.json:3: pragma name-case-whitelist must be a list of strings
diff --git a/tests/qapi-schema/pragma-name-case-whitelist-crap.exit b/tests/qapi-schema/pragma-name-case-whitelist-crap.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/pragma-name-case-whitelist-crap.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/pragma-non-dict.err b/tests/qapi-schema/pragma-non-dict.err
index 8221724b0a..802f1806c5 100644
--- a/tests/qapi-schema/pragma-non-dict.err
+++ b/tests/qapi-schema/pragma-non-dict.err
@@ -1 +1 @@
-tests/qapi-schema/pragma-non-dict.json:3: value of 'pragma' must be an object
+pragma-non-dict.json:3: value of 'pragma' must be an object
diff --git a/tests/qapi-schema/pragma-non-dict.exit b/tests/qapi-schema/pragma-non-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/pragma-non-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/pragma-returns-whitelist-crap.err b/tests/qapi-schema/pragma-returns-whitelist-crap.err
index c0cae5de18..69784259df 100644
--- a/tests/qapi-schema/pragma-returns-whitelist-crap.err
+++ b/tests/qapi-schema/pragma-returns-whitelist-crap.err
@@ -1 +1 @@
-tests/qapi-schema/pragma-returns-whitelist-crap.json:3: pragma returns-whitelist must be a list of strings
+pragma-returns-whitelist-crap.json:3: pragma returns-whitelist must be a list of strings
diff --git a/tests/qapi-schema/pragma-returns-whitelist-crap.exit b/tests/qapi-schema/pragma-returns-whitelist-crap.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/pragma-returns-whitelist-crap.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/pragma-unknown.err b/tests/qapi-schema/pragma-unknown.err
index f1335f0a0a..d99a772503 100644
--- a/tests/qapi-schema/pragma-unknown.err
+++ b/tests/qapi-schema/pragma-unknown.err
@@ -1 +1 @@
-tests/qapi-schema/pragma-unknown.json:1: unknown pragma 'no-such-pragma'
+pragma-unknown.json:1: unknown pragma 'no-such-pragma'
diff --git a/tests/qapi-schema/pragma-unknown.exit b/tests/qapi-schema/pragma-unknown.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/pragma-unknown.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/qapi-schema-test.exit b/tests/qapi-schema/qapi-schema-test.exit
deleted file mode 100644
index 573541ac97..0000000000
--- a/tests/qapi-schema/qapi-schema-test.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 75c42eb0e3..9abf175fe0 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -290,3 +290,21 @@
'cfs1': 'CondFeatureStruct1',
'cfs2': 'CondFeatureStruct2',
'cfs3': 'CondFeatureStruct3' } }
+
+# test 'features' for command
+
+{ 'command': 'test-command-features0',
+ 'features': [] }
+{ 'command': 'test-command-features1',
+ 'features': [ 'feature1' ] }
+{ 'command': 'test-command-features3',
+ 'features': [ 'feature1', 'feature2' ] }
+
+{ 'command': 'test-command-cond-features1',
+ 'features': [ { 'name': 'feature1', 'if': 'defined(TEST_IF_FEATURE_1)'} ] }
+{ 'command': 'test-command-cond-features2',
+ 'features': [ { 'name': 'feature1', 'if': 'defined(TEST_IF_FEATURE_1)'},
+ { 'name': 'feature2', 'if': 'defined(TEST_IF_FEATURE_2)'} ] }
+{ 'command': 'test-command-cond-features3',
+ 'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)',
+ 'defined(TEST_IF_COND_2)'] } ] }
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 98031da96f..3660e75a48 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -33,7 +33,7 @@ object Union
case value3: q_empty
case value4: q_empty
command user_def_cmd0 Empty2 -> Empty2
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
enum QEnumTwo
prefix QENUM_TWO
member value1
@@ -205,35 +205,35 @@ object SecondArrayRef
member s: StatusList optional=False
module qapi-schema-test.json
command user_def_cmd None -> None
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
object q_obj_user_def_cmd1-arg
member ud1a: UserDefOne optional=False
command user_def_cmd1 q_obj_user_def_cmd1-arg -> None
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
object q_obj_user_def_cmd2-arg
member ud1a: UserDefOne optional=False
member ud1b: UserDefOne optional=True
command user_def_cmd2 q_obj_user_def_cmd2-arg -> UserDefTwo
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
command cmd-success-response None -> None
- gen=True success_response=False boxed=False oob=False preconfig=False
+ gen=True success_response=False boxed=False oob=False preconfig=False
object q_obj_guest-get-time-arg
member a: int optional=False
member b: int optional=True
command guest-get-time q_obj_guest-get-time-arg -> int
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
object q_obj_guest-sync-arg
member arg: any optional=False
command guest-sync q_obj_guest-sync-arg -> any
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
command boxed-struct UserDefZero -> None
- gen=True success_response=True boxed=True oob=False preconfig=False
+ gen=True success_response=True boxed=True oob=False preconfig=False
command boxed-union UserDefListUnion -> None
- gen=True success_response=True boxed=True oob=False preconfig=False
+ gen=True success_response=True boxed=True oob=False preconfig=False
command boxed-empty Empty1 -> None
- gen=True success_response=True boxed=True oob=False preconfig=False
+ gen=True success_response=True boxed=True oob=False preconfig=False
command test-flags-command None -> None
- gen=True success_response=True boxed=False oob=True preconfig=True
+ gen=True success_response=True boxed=False oob=True preconfig=True
object UserDefOptions
member i64: intList optional=True
member u64: uint64List optional=True
@@ -245,28 +245,28 @@ object EventStructOne
member string: str optional=False
member enum2: EnumOne optional=True
event EVENT_A None
- boxed=False
+ boxed=False
event EVENT_B None
- boxed=False
+ boxed=False
object q_obj_EVENT_C-arg
member a: int optional=True
member b: UserDefOne optional=True
member c: str optional=False
event EVENT_C q_obj_EVENT_C-arg
- boxed=False
+ boxed=False
object q_obj_EVENT_D-arg
member a: EventStructOne optional=False
member b: str optional=False
member c: str optional=True
member enum3: EnumOne optional=True
event EVENT_D q_obj_EVENT_D-arg
- boxed=False
+ boxed=False
event EVENT_E UserDefZero
- boxed=True
+ boxed=True
event EVENT_F UserDefFlatUnion
- boxed=True
+ boxed=True
event EVENT_G Empty1
- boxed=True
+ boxed=True
enum __org.qemu_x-Enum
member __org.qemu_x-value
object __org.qemu_x-Base
@@ -297,7 +297,7 @@ alternate __org.qemu_x-Alt
tag type
case __org.qemu_x-branch: __org.qemu_x-Base
event __ORG.QEMU_X-EVENT __org.qemu_x-Struct
- boxed=False
+ boxed=False
array __org.qemu_x-EnumList __org.qemu_x-Enum
array __org.qemu_x-StructList __org.qemu_x-Struct
object q_obj___org.qemu_x-command-arg
@@ -306,7 +306,7 @@ object q_obj___org.qemu_x-command-arg
member c: __org.qemu_x-Union2 optional=False
member d: __org.qemu_x-Alt optional=False
command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Union1
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
object TestIfStruct
member foo: int optional=False
member bar: int optional=False
@@ -335,7 +335,7 @@ object q_obj_TestIfUnionCmd-arg
member union_cmd_arg: TestIfUnion optional=False
if ['defined(TEST_IF_UNION)']
command TestIfUnionCmd q_obj_TestIfUnionCmd-arg -> None
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
if ['defined(TEST_IF_UNION)']
alternate TestIfAlternate
tag type
@@ -347,7 +347,7 @@ object q_obj_TestIfAlternateCmd-arg
member alt_cmd_arg: TestIfAlternate optional=False
if ['defined(TEST_IF_ALT)']
command TestIfAlternateCmd q_obj_TestIfAlternateCmd-arg -> None
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
if ['defined(TEST_IF_ALT)']
object q_obj_TestIfCmd-arg
member foo: TestIfStruct optional=False
@@ -355,10 +355,10 @@ object q_obj_TestIfCmd-arg
if ['defined(TEST_IF_CMD_BAR)']
if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
command TestIfCmd q_obj_TestIfCmd-arg -> UserDefThree
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
command TestCmdReturnDefThree None -> UserDefThree
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
array TestIfEnumList TestIfEnum
if ['defined(TEST_IF_ENUM)']
object q_obj_TestIfEvent-arg
@@ -367,7 +367,7 @@ object q_obj_TestIfEvent-arg
if ['defined(TEST_IF_EVT_BAR)']
if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']
event TestIfEvent q_obj_TestIfEvent-arg
- boxed=False
+ boxed=False
if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']
object FeatureStruct0
member foo: int optional=False
@@ -411,4 +411,27 @@ object q_obj_test-features-arg
member cfs2: CondFeatureStruct2 optional=False
member cfs3: CondFeatureStruct3 optional=False
command test-features q_obj_test-features-arg -> None
- gen=True success_response=True boxed=False oob=False preconfig=False
+ gen=True success_response=True boxed=False oob=False preconfig=False
+command test-command-features0 None -> None
+ gen=True success_response=True boxed=False oob=False preconfig=False
+command test-command-features1 None -> None
+ gen=True success_response=True boxed=False oob=False preconfig=False
+ feature feature1
+command test-command-features3 None -> None
+ gen=True success_response=True boxed=False oob=False preconfig=False
+ feature feature1
+ feature feature2
+command test-command-cond-features1 None -> None
+ gen=True success_response=True boxed=False oob=False preconfig=False
+ feature feature1
+ if ['defined(TEST_IF_FEATURE_1)']
+command test-command-cond-features2 None -> None
+ gen=True success_response=True boxed=False oob=False preconfig=False
+ feature feature1
+ if ['defined(TEST_IF_FEATURE_1)']
+ feature feature2
+ if ['defined(TEST_IF_FEATURE_2)']
+command test-command-cond-features3 None -> None
+ gen=True success_response=True boxed=False oob=False preconfig=False
+ feature feature1
+ if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
diff --git a/tests/qapi-schema/quoted-structural-chars.err b/tests/qapi-schema/quoted-structural-chars.err
index d8460a63a7..07d1561d1f 100644
--- a/tests/qapi-schema/quoted-structural-chars.err
+++ b/tests/qapi-schema/quoted-structural-chars.err
@@ -1 +1 @@
-tests/qapi-schema/quoted-structural-chars.json:1:1: expected '{'
+quoted-structural-chars.json:1:1: expected '{'
diff --git a/tests/qapi-schema/quoted-structural-chars.exit b/tests/qapi-schema/quoted-structural-chars.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/quoted-structural-chars.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/redefined-builtin.err b/tests/qapi-schema/redefined-builtin.err
index 47c8933759..58c7e42ffc 100644
--- a/tests/qapi-schema/redefined-builtin.err
+++ b/tests/qapi-schema/redefined-builtin.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/redefined-builtin.json: In struct 'size':
-tests/qapi-schema/redefined-builtin.json:2: built-in type 'size' is already defined
+redefined-builtin.json: In struct 'size':
+redefined-builtin.json:2: built-in type 'size' is already defined
diff --git a/tests/qapi-schema/redefined-builtin.exit b/tests/qapi-schema/redefined-builtin.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/redefined-builtin.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/redefined-command.err b/tests/qapi-schema/redefined-command.err
index 54e366bbf3..888eea87ca 100644
--- a/tests/qapi-schema/redefined-command.err
+++ b/tests/qapi-schema/redefined-command.err
@@ -1,4 +1,4 @@
-tests/qapi-schema/redefined-command.json: In command 'foo':
-tests/qapi-schema/redefined-command.json:3: 'foo' is already defined
-tests/qapi-schema/redefined-command.json: In command 'foo':
-tests/qapi-schema/redefined-command.json:2: previous definition
+redefined-command.json: In command 'foo':
+redefined-command.json:3: 'foo' is already defined
+redefined-command.json: In command 'foo':
+redefined-command.json:2: previous definition
diff --git a/tests/qapi-schema/redefined-command.exit b/tests/qapi-schema/redefined-command.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/redefined-command.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/redefined-event.err b/tests/qapi-schema/redefined-event.err
index 606c6e4497..b1a6d99d2f 100644
--- a/tests/qapi-schema/redefined-event.err
+++ b/tests/qapi-schema/redefined-event.err
@@ -1,4 +1,4 @@
-tests/qapi-schema/redefined-event.json: In event 'EVENT_A':
-tests/qapi-schema/redefined-event.json:3: 'EVENT_A' is already defined
-tests/qapi-schema/redefined-event.json: In event 'EVENT_A':
-tests/qapi-schema/redefined-event.json:2: previous definition
+redefined-event.json: In event 'EVENT_A':
+redefined-event.json:3: 'EVENT_A' is already defined
+redefined-event.json: In event 'EVENT_A':
+redefined-event.json:2: previous definition
diff --git a/tests/qapi-schema/redefined-event.exit b/tests/qapi-schema/redefined-event.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/redefined-event.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/redefined-type.err b/tests/qapi-schema/redefined-type.err
index 77786f98ae..b7103fc15f 100644
--- a/tests/qapi-schema/redefined-type.err
+++ b/tests/qapi-schema/redefined-type.err
@@ -1,4 +1,4 @@
-tests/qapi-schema/redefined-type.json: In enum 'foo':
-tests/qapi-schema/redefined-type.json:3: 'foo' is already defined
-tests/qapi-schema/redefined-type.json: In struct 'foo':
-tests/qapi-schema/redefined-type.json:2: previous definition
+redefined-type.json: In enum 'foo':
+redefined-type.json:3: 'foo' is already defined
+redefined-type.json: In struct 'foo':
+redefined-type.json:2: previous definition
diff --git a/tests/qapi-schema/redefined-type.exit b/tests/qapi-schema/redefined-type.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/redefined-type.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/reserved-command-q.err b/tests/qapi-schema/reserved-command-q.err
index 7f65cda02d..6fecbaa046 100644
--- a/tests/qapi-schema/reserved-command-q.err
+++ b/tests/qapi-schema/reserved-command-q.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/reserved-command-q.json: In command 'q-unix':
-tests/qapi-schema/reserved-command-q.json:5: command has an invalid name
+reserved-command-q.json: In command 'q-unix':
+reserved-command-q.json:5: command has an invalid name
diff --git a/tests/qapi-schema/reserved-command-q.exit b/tests/qapi-schema/reserved-command-q.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/reserved-command-q.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/reserved-enum-q.err b/tests/qapi-schema/reserved-enum-q.err
index e202f9ff7b..fc3c757874 100644
--- a/tests/qapi-schema/reserved-enum-q.err
+++ b/tests/qapi-schema/reserved-enum-q.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/reserved-enum-q.json: In enum 'Foo':
-tests/qapi-schema/reserved-enum-q.json:4: 'data' member 'q-Unix' has an invalid name
+reserved-enum-q.json: In enum 'Foo':
+reserved-enum-q.json:4: 'data' member 'q-Unix' has an invalid name
diff --git a/tests/qapi-schema/reserved-enum-q.exit b/tests/qapi-schema/reserved-enum-q.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/reserved-enum-q.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/reserved-member-has.err b/tests/qapi-schema/reserved-member-has.err
index c7ad721ad1..436e1749f2 100644
--- a/tests/qapi-schema/reserved-member-has.err
+++ b/tests/qapi-schema/reserved-member-has.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/reserved-member-has.json: In command 'oops':
-tests/qapi-schema/reserved-member-has.json:5: 'data' member 'has-a' uses reserved name
+reserved-member-has.json: In command 'oops':
+reserved-member-has.json:5: 'data' member 'has-a' uses reserved name
diff --git a/tests/qapi-schema/reserved-member-has.exit b/tests/qapi-schema/reserved-member-has.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/reserved-member-has.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/reserved-member-q.err b/tests/qapi-schema/reserved-member-q.err
index 04078604fa..9ac8654a8b 100644
--- a/tests/qapi-schema/reserved-member-q.err
+++ b/tests/qapi-schema/reserved-member-q.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/reserved-member-q.json: In struct 'Foo':
-tests/qapi-schema/reserved-member-q.json:4: 'data' member 'q-unix' has an invalid name
+reserved-member-q.json: In struct 'Foo':
+reserved-member-q.json:4: 'data' member 'q-unix' has an invalid name
diff --git a/tests/qapi-schema/reserved-member-q.exit b/tests/qapi-schema/reserved-member-q.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/reserved-member-q.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/reserved-member-u.err b/tests/qapi-schema/reserved-member-u.err
index 2e92c11ba5..231d552494 100644
--- a/tests/qapi-schema/reserved-member-u.err
+++ b/tests/qapi-schema/reserved-member-u.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/reserved-member-u.json: In struct 'Oops':
-tests/qapi-schema/reserved-member-u.json:7: 'data' member 'u' uses reserved name
+reserved-member-u.json: In struct 'Oops':
+reserved-member-u.json:7: 'data' member 'u' uses reserved name
diff --git a/tests/qapi-schema/reserved-member-u.exit b/tests/qapi-schema/reserved-member-u.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/reserved-member-u.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/reserved-member-underscore.err b/tests/qapi-schema/reserved-member-underscore.err
index da62b48222..df3ab8a11a 100644
--- a/tests/qapi-schema/reserved-member-underscore.err
+++ b/tests/qapi-schema/reserved-member-underscore.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/reserved-member-underscore.json: In struct 'Oops':
-tests/qapi-schema/reserved-member-underscore.json:4: 'data' member '_oops' has an invalid name
+reserved-member-underscore.json: In struct 'Oops':
+reserved-member-underscore.json:4: 'data' member '_oops' has an invalid name
diff --git a/tests/qapi-schema/reserved-member-underscore.exit b/tests/qapi-schema/reserved-member-underscore.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/reserved-member-underscore.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/reserved-type-kind.err b/tests/qapi-schema/reserved-type-kind.err
index f8112cf92e..d8fb769f9d 100644
--- a/tests/qapi-schema/reserved-type-kind.err
+++ b/tests/qapi-schema/reserved-type-kind.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/reserved-type-kind.json: In enum 'UnionKind':
-tests/qapi-schema/reserved-type-kind.json:2: enum name should not end in 'Kind'
+reserved-type-kind.json: In enum 'UnionKind':
+reserved-type-kind.json:2: enum name should not end in 'Kind'
diff --git a/tests/qapi-schema/reserved-type-kind.exit b/tests/qapi-schema/reserved-type-kind.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/reserved-type-kind.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/reserved-type-list.err b/tests/qapi-schema/reserved-type-list.err
index c6eee0585c..e09f5352f8 100644
--- a/tests/qapi-schema/reserved-type-list.err
+++ b/tests/qapi-schema/reserved-type-list.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/reserved-type-list.json: In struct 'FooList':
-tests/qapi-schema/reserved-type-list.json:5: struct name should not end in 'List'
+reserved-type-list.json: In struct 'FooList':
+reserved-type-list.json:5: struct name should not end in 'List'
diff --git a/tests/qapi-schema/reserved-type-list.exit b/tests/qapi-schema/reserved-type-list.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/reserved-type-list.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/returns-alternate.err b/tests/qapi-schema/returns-alternate.err
index c1caf98c47..b6421a8917 100644
--- a/tests/qapi-schema/returns-alternate.err
+++ b/tests/qapi-schema/returns-alternate.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/returns-alternate.json: In command 'oops':
-tests/qapi-schema/returns-alternate.json:3: command's 'returns' cannot take alternate type 'Alt'
+returns-alternate.json: In command 'oops':
+returns-alternate.json:3: command's 'returns' cannot take alternate type 'Alt'
diff --git a/tests/qapi-schema/returns-alternate.exit b/tests/qapi-schema/returns-alternate.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/returns-alternate.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/returns-array-bad.err b/tests/qapi-schema/returns-array-bad.err
index 1b86777d8f..52712d139d 100644
--- a/tests/qapi-schema/returns-array-bad.err
+++ b/tests/qapi-schema/returns-array-bad.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/returns-array-bad.json: In command 'oops':
-tests/qapi-schema/returns-array-bad.json:2: 'returns': array type must contain single type name
+returns-array-bad.json: In command 'oops':
+returns-array-bad.json:2: 'returns': array type must contain single type name
diff --git a/tests/qapi-schema/returns-array-bad.exit b/tests/qapi-schema/returns-array-bad.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/returns-array-bad.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/returns-dict.err b/tests/qapi-schema/returns-dict.err
index 52e4f3ad71..9b2d90c010 100644
--- a/tests/qapi-schema/returns-dict.err
+++ b/tests/qapi-schema/returns-dict.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/returns-dict.json: In command 'oops':
-tests/qapi-schema/returns-dict.json:2: 'returns' should be a type name
+returns-dict.json: In command 'oops':
+returns-dict.json:2: 'returns' should be a type name
diff --git a/tests/qapi-schema/returns-dict.exit b/tests/qapi-schema/returns-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/returns-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/returns-unknown.err b/tests/qapi-schema/returns-unknown.err
index f0a989a175..bf59086d0c 100644
--- a/tests/qapi-schema/returns-unknown.err
+++ b/tests/qapi-schema/returns-unknown.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/returns-unknown.json: In command 'oops':
-tests/qapi-schema/returns-unknown.json:2: command's 'returns' uses unknown type 'NoSuchType'
+returns-unknown.json: In command 'oops':
+returns-unknown.json:2: command's 'returns' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/returns-unknown.exit b/tests/qapi-schema/returns-unknown.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/returns-unknown.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/returns-whitelist.err b/tests/qapi-schema/returns-whitelist.err
index 5b0285220f..c6e46b9b86 100644
--- a/tests/qapi-schema/returns-whitelist.err
+++ b/tests/qapi-schema/returns-whitelist.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/returns-whitelist.json: In command 'no-way-this-will-get-whitelisted':
-tests/qapi-schema/returns-whitelist.json:14: command's 'returns' cannot take array type ['int']
+returns-whitelist.json: In command 'no-way-this-will-get-whitelisted':
+returns-whitelist.json:14: command's 'returns' cannot take array type ['int']
diff --git a/tests/qapi-schema/returns-whitelist.exit b/tests/qapi-schema/returns-whitelist.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/returns-whitelist.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/string-code-point-127.err b/tests/qapi-schema/string-code-point-127.err
index b4fa2610a9..eb0d1c71a6 100644
--- a/tests/qapi-schema/string-code-point-127.err
+++ b/tests/qapi-schema/string-code-point-127.err
@@ -1 +1 @@
-tests/qapi-schema/string-code-point-127.json:2:14: funny character in string
+string-code-point-127.json:2:14: funny character in string
diff --git a/tests/qapi-schema/string-code-point-127.exit b/tests/qapi-schema/string-code-point-127.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/string-code-point-127.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/string-code-point-31.err b/tests/qapi-schema/string-code-point-31.err
index 0bb5ce37b8..ae969d6e10 100644
--- a/tests/qapi-schema/string-code-point-31.err
+++ b/tests/qapi-schema/string-code-point-31.err
@@ -1 +1 @@
-tests/qapi-schema/string-code-point-31.json:2:14: funny character in string
+string-code-point-31.json:2:14: funny character in string
diff --git a/tests/qapi-schema/string-code-point-31.exit b/tests/qapi-schema/string-code-point-31.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/string-code-point-31.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/struct-base-clash-deep.err b/tests/qapi-schema/struct-base-clash-deep.err
index 2b12b3c07f..79879681d9 100644
--- a/tests/qapi-schema/struct-base-clash-deep.err
+++ b/tests/qapi-schema/struct-base-clash-deep.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/struct-base-clash-deep.json: In struct 'Sub':
-tests/qapi-schema/struct-base-clash-deep.json:10: member 'name' collides with member 'name' of type 'Base'
+struct-base-clash-deep.json: In struct 'Sub':
+struct-base-clash-deep.json:10: member 'name' collides with member 'name' of type 'Base'
diff --git a/tests/qapi-schema/struct-base-clash-deep.exit b/tests/qapi-schema/struct-base-clash-deep.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/struct-base-clash-deep.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/struct-base-clash.err b/tests/qapi-schema/struct-base-clash.err
index 8c3ee1c435..46473947e6 100644
--- a/tests/qapi-schema/struct-base-clash.err
+++ b/tests/qapi-schema/struct-base-clash.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/struct-base-clash.json: In struct 'Sub':
-tests/qapi-schema/struct-base-clash.json:5: member 'name' collides with member 'name' of type 'Base'
+struct-base-clash.json: In struct 'Sub':
+struct-base-clash.json:5: member 'name' collides with member 'name' of type 'Base'
diff --git a/tests/qapi-schema/struct-base-clash.exit b/tests/qapi-schema/struct-base-clash.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/struct-base-clash.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/struct-data-invalid.err b/tests/qapi-schema/struct-data-invalid.err
index aa868bf974..5ed4bec573 100644
--- a/tests/qapi-schema/struct-data-invalid.err
+++ b/tests/qapi-schema/struct-data-invalid.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/struct-data-invalid.json: In struct 'foo':
-tests/qapi-schema/struct-data-invalid.json:1: 'data' should be an object or type name
+struct-data-invalid.json: In struct 'foo':
+struct-data-invalid.json:1: 'data' should be an object or type name
diff --git a/tests/qapi-schema/struct-data-invalid.exit b/tests/qapi-schema/struct-data-invalid.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/struct-data-invalid.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/struct-member-if-invalid.err b/tests/qapi-schema/struct-member-if-invalid.err
index 4c5983674b..42e7fdae3c 100644
--- a/tests/qapi-schema/struct-member-if-invalid.err
+++ b/tests/qapi-schema/struct-member-if-invalid.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/struct-member-if-invalid.json: In struct 'Stru':
-tests/qapi-schema/struct-member-if-invalid.json:2: 'if' condition of 'data' member 'member' must be a string or a list of strings
+struct-member-if-invalid.json: In struct 'Stru':
+struct-member-if-invalid.json:2: 'if' condition of 'data' member 'member' must be a string or a list of strings
diff --git a/tests/qapi-schema/struct-member-if-invalid.exit b/tests/qapi-schema/struct-member-if-invalid.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/struct-member-if-invalid.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/struct-member-invalid-dict.err b/tests/qapi-schema/struct-member-invalid-dict.err
index 46ec991c28..0621aecfbd 100644
--- a/tests/qapi-schema/struct-member-invalid-dict.err
+++ b/tests/qapi-schema/struct-member-invalid-dict.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/struct-member-invalid-dict.json: In struct 'foo':
-tests/qapi-schema/struct-member-invalid-dict.json:2: 'data' member '*a' misses key 'type'
+struct-member-invalid-dict.json: In struct 'foo':
+struct-member-invalid-dict.json:2: 'data' member '*a' misses key 'type'
diff --git a/tests/qapi-schema/struct-member-invalid-dict.exit b/tests/qapi-schema/struct-member-invalid-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/struct-member-invalid-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/struct-member-invalid.err b/tests/qapi-schema/struct-member-invalid.err
index 92d4973832..9a2c934538 100644
--- a/tests/qapi-schema/struct-member-invalid.err
+++ b/tests/qapi-schema/struct-member-invalid.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/struct-member-invalid.json: In struct 'foo':
-tests/qapi-schema/struct-member-invalid.json:1: 'data' member 'a' should be a type name
+struct-member-invalid.json: In struct 'foo':
+struct-member-invalid.json:1: 'data' member 'a' should be a type name
diff --git a/tests/qapi-schema/struct-member-invalid.exit b/tests/qapi-schema/struct-member-invalid.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/struct-member-invalid.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index b0f770b9bd..2bd9fd8742 100644..100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
#
# QAPI parser test harness
#
@@ -11,8 +12,19 @@
#
from __future__ import print_function
+
+import argparse
+import difflib
+import os
import sys
-from qapi.common import QAPIError, QAPISchema, QAPISchemaVisitor
+
+from qapi.error import QAPIError
+from qapi.schema import QAPISchema, QAPISchemaVisitor
+
+if sys.version_info[0] < 3:
+ from cStringIO import StringIO
+else:
+ from io import StringIO
class QAPISchemaTestVisitor(QAPISchemaVisitor):
@@ -49,10 +61,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
self._print_if(m.ifcond, 8)
self._print_variants(variants)
self._print_if(ifcond)
- if features:
- for f in features:
- print(' feature %s' % f.name)
- self._print_if(f.ifcond, 8)
+ self._print_features(features)
def visit_alternate_type(self, name, info, ifcond, variants):
print('alternate %s' % name)
@@ -60,17 +69,19 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
self._print_if(ifcond)
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
- success_response, boxed, allow_oob, allow_preconfig):
+ success_response, boxed, allow_oob, allow_preconfig,
+ features):
print('command %s %s -> %s'
% (name, arg_type and arg_type.name,
ret_type and ret_type.name))
- print(' gen=%s success_response=%s boxed=%s oob=%s preconfig=%s'
+ print(' gen=%s success_response=%s boxed=%s oob=%s preconfig=%s'
% (gen, success_response, boxed, allow_oob, allow_preconfig))
self._print_if(ifcond)
+ self._print_features(features)
def visit_event(self, name, info, ifcond, arg_type, boxed):
print('event %s %s' % (name, arg_type and arg_type.name))
- print(' boxed=%s' % boxed)
+ print(' boxed=%s' % boxed)
self._print_if(ifcond)
@staticmethod
@@ -86,22 +97,110 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
if ifcond:
print('%sif %s' % (' ' * indent, ifcond))
-
-try:
- schema = QAPISchema(sys.argv[1])
-except QAPIError as err:
- print(err, file=sys.stderr)
- exit(1)
-
-schema.visit(QAPISchemaTestVisitor())
-
-for doc in schema.docs:
- if doc.symbol:
- print('doc symbol=%s' % doc.symbol)
+ @classmethod
+ def _print_features(cls, features):
+ if features:
+ for f in features:
+ print(' feature %s' % f.name)
+ cls._print_if(f.ifcond, 8)
+
+
+def test_frontend(fname):
+ schema = QAPISchema(fname)
+ schema.visit(QAPISchemaTestVisitor())
+
+ for doc in schema.docs:
+ if doc.symbol:
+ print('doc symbol=%s' % doc.symbol)
+ else:
+ print('doc freeform')
+ print(' body=\n%s' % doc.body.text)
+ for arg, section in doc.args.items():
+ print(' arg=%s\n%s' % (arg, section.text))
+ for section in doc.sections:
+ print(' section=%s\n%s' % (section.name, section.text))
+
+
+def test_and_diff(test_name, dir_name, update):
+ sys.stdout = StringIO()
+ try:
+ test_frontend(os.path.join(dir_name, test_name + '.json'))
+ except QAPIError as err:
+ if err.info.fname is None:
+ print("%s" % err, file=sys.stderr)
+ return 2
+ errstr = str(err) + '\n'
+ if dir_name:
+ errstr = errstr.replace(dir_name + '/', '')
+ actual_err = errstr.splitlines(True)
else:
- print('doc freeform')
- print(' body=\n%s' % doc.body.text)
- for arg, section in doc.args.items():
- print(' arg=%s\n%s' % (arg, section.text))
- for section in doc.sections:
- print(' section=%s\n%s' % (section.name, section.text))
+ actual_err = []
+ finally:
+ actual_out = sys.stdout.getvalue().splitlines(True)
+ sys.stdout.close()
+ sys.stdout = sys.__stdout__
+
+ mode = 'r+' if update else 'r'
+ try:
+ outfp = open(os.path.join(dir_name, test_name + '.out'), mode)
+ errfp = open(os.path.join(dir_name, test_name + '.err'), mode)
+ expected_out = outfp.readlines()
+ expected_err = errfp.readlines()
+ except IOError as err:
+ print("%s: can't open '%s': %s"
+ % (sys.argv[0], err.filename, err.strerror),
+ file=sys.stderr)
+ return 2
+
+ if actual_out == expected_out and actual_err == expected_err:
+ return 0
+
+ print("%s %s" % (test_name, 'UPDATE' if update else 'FAIL'),
+ file=sys.stderr)
+ out_diff = difflib.unified_diff(expected_out, actual_out, outfp.name)
+ err_diff = difflib.unified_diff(expected_err, actual_err, errfp.name)
+ sys.stdout.writelines(out_diff)
+ sys.stdout.writelines(err_diff)
+
+ if not update:
+ return 1
+
+ try:
+ outfp.truncate(0)
+ outfp.seek(0)
+ outfp.writelines(actual_out)
+ errfp.truncate(0)
+ errfp.seek(0)
+ errfp.writelines(actual_err)
+ except IOError as err:
+ print("%s: can't write '%s': %s"
+ % (sys.argv[0], err.filename, err.strerror),
+ file=sys.stderr)
+ return 2
+
+ return 0
+
+
+def main(argv):
+ parser = argparse.ArgumentParser(
+ description='QAPI schema tester')
+ parser.add_argument('-d', '--dir', action='store', default='',
+ help="directory containing tests")
+ parser.add_argument('-u', '--update', action='store_true',
+ help="update expected test results")
+ parser.add_argument('tests', nargs='*', metavar='TEST', action='store')
+ args = parser.parse_args()
+
+ status = 0
+ for t in args.tests:
+ (dir_name, base_name) = os.path.split(t)
+ dir_name = dir_name or args.dir
+ test_name = os.path.splitext(base_name)[0]
+ status |= test_and_diff(test_name, dir_name, args.update)
+
+ exit(status)
+
+
+if __name__ == '__main__':
+ main(sys.argv)
+ exit(0)
diff --git a/tests/qapi-schema/trailing-comma-list.err b/tests/qapi-schema/trailing-comma-list.err
index 167d688beb..ad2f2d7c97 100644
--- a/tests/qapi-schema/trailing-comma-list.err
+++ b/tests/qapi-schema/trailing-comma-list.err
@@ -1 +1 @@
-tests/qapi-schema/trailing-comma-list.json:2:36: expected '{', '[', string, boolean or 'null'
+trailing-comma-list.json:2:36: expected '{', '[', string, boolean or 'null'
diff --git a/tests/qapi-schema/trailing-comma-list.exit b/tests/qapi-schema/trailing-comma-list.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/trailing-comma-list.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/trailing-comma-object.err b/tests/qapi-schema/trailing-comma-object.err
index 186df0fa45..29aafc69cd 100644
--- a/tests/qapi-schema/trailing-comma-object.err
+++ b/tests/qapi-schema/trailing-comma-object.err
@@ -1 +1 @@
-tests/qapi-schema/trailing-comma-object.json:2:38: expected string
+trailing-comma-object.json:2:38: expected string
diff --git a/tests/qapi-schema/trailing-comma-object.exit b/tests/qapi-schema/trailing-comma-object.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/trailing-comma-object.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/type-bypass-bad-gen.err b/tests/qapi-schema/type-bypass-bad-gen.err
index 1077651896..2ca95cd86d 100644
--- a/tests/qapi-schema/type-bypass-bad-gen.err
+++ b/tests/qapi-schema/type-bypass-bad-gen.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/type-bypass-bad-gen.json: In command 'foo':
-tests/qapi-schema/type-bypass-bad-gen.json:2: flag 'gen' may only use false value
+type-bypass-bad-gen.json: In command 'foo':
+type-bypass-bad-gen.json:2: flag 'gen' may only use false value
diff --git a/tests/qapi-schema/type-bypass-bad-gen.exit b/tests/qapi-schema/type-bypass-bad-gen.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/type-bypass-bad-gen.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/unclosed-list.err b/tests/qapi-schema/unclosed-list.err
index 6648a8e30b..aa2c430b22 100644
--- a/tests/qapi-schema/unclosed-list.err
+++ b/tests/qapi-schema/unclosed-list.err
@@ -1 +1 @@
-tests/qapi-schema/unclosed-list.json:1:20: expected ',' or ']'
+unclosed-list.json:1:20: expected ',' or ']'
diff --git a/tests/qapi-schema/unclosed-list.exit b/tests/qapi-schema/unclosed-list.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/unclosed-list.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/unclosed-object.err b/tests/qapi-schema/unclosed-object.err
index 54d221e3a9..c08499b341 100644
--- a/tests/qapi-schema/unclosed-object.err
+++ b/tests/qapi-schema/unclosed-object.err
@@ -1 +1 @@
-tests/qapi-schema/unclosed-object.json:1:21: expected ',' or '}'
+unclosed-object.json:1:21: expected ',' or '}'
diff --git a/tests/qapi-schema/unclosed-object.exit b/tests/qapi-schema/unclosed-object.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/unclosed-object.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/unclosed-string.err b/tests/qapi-schema/unclosed-string.err
index 9439698fe0..175e192b24 100644
--- a/tests/qapi-schema/unclosed-string.err
+++ b/tests/qapi-schema/unclosed-string.err
@@ -1 +1 @@
-tests/qapi-schema/unclosed-string.json:1:11: missing terminating "'"
+unclosed-string.json:1:11: missing terminating "'"
diff --git a/tests/qapi-schema/unclosed-string.exit b/tests/qapi-schema/unclosed-string.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/unclosed-string.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-base-empty.err b/tests/qapi-schema/union-base-empty.err
index b76542d47a..3630b341a6 100644
--- a/tests/qapi-schema/union-base-empty.err
+++ b/tests/qapi-schema/union-base-empty.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-base-empty.json: In union 'TestUnion':
-tests/qapi-schema/union-base-empty.json:5: discriminator 'type' is not a member of 'base'
+union-base-empty.json: In union 'TestUnion':
+union-base-empty.json:5: discriminator 'type' is not a member of 'base'
diff --git a/tests/qapi-schema/union-base-empty.exit b/tests/qapi-schema/union-base-empty.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-base-empty.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-base-no-discriminator.err b/tests/qapi-schema/union-base-no-discriminator.err
index f4c16a2c14..9cd5d11b0b 100644
--- a/tests/qapi-schema/union-base-no-discriminator.err
+++ b/tests/qapi-schema/union-base-no-discriminator.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-base-no-discriminator.json: In union 'TestUnion':
-tests/qapi-schema/union-base-no-discriminator.json:11: 'base' requires 'discriminator'
+union-base-no-discriminator.json: In union 'TestUnion':
+union-base-no-discriminator.json:11: 'base' requires 'discriminator'
diff --git a/tests/qapi-schema/union-base-no-discriminator.exit b/tests/qapi-schema/union-base-no-discriminator.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-base-no-discriminator.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-branch-case.err b/tests/qapi-schema/union-branch-case.err
index a0684ae637..b1e9417303 100644
--- a/tests/qapi-schema/union-branch-case.err
+++ b/tests/qapi-schema/union-branch-case.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-branch-case.json: In union 'Uni':
-tests/qapi-schema/union-branch-case.json:2: 'data' member 'Branch' uses uppercase in name
+union-branch-case.json: In union 'Uni':
+union-branch-case.json:2: 'data' member 'Branch' uses uppercase in name
diff --git a/tests/qapi-schema/union-branch-case.exit b/tests/qapi-schema/union-branch-case.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-branch-case.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-branch-if-invalid.err b/tests/qapi-schema/union-branch-if-invalid.err
index 14819bf8b8..dd4518233e 100644
--- a/tests/qapi-schema/union-branch-if-invalid.err
+++ b/tests/qapi-schema/union-branch-if-invalid.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-branch-if-invalid.json: In union 'Uni':
-tests/qapi-schema/union-branch-if-invalid.json:4: 'if' condition '' of 'data' member 'branch1' makes no sense
+union-branch-if-invalid.json: In union 'Uni':
+union-branch-if-invalid.json:4: 'if' condition '' of 'data' member 'branch1' makes no sense
diff --git a/tests/qapi-schema/union-branch-if-invalid.exit b/tests/qapi-schema/union-branch-if-invalid.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-branch-if-invalid.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-branch-invalid-dict.err b/tests/qapi-schema/union-branch-invalid-dict.err
index 2967cd6260..8137c5a767 100644
--- a/tests/qapi-schema/union-branch-invalid-dict.err
+++ b/tests/qapi-schema/union-branch-invalid-dict.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-branch-invalid-dict.json: In union 'UnionInvalidBranch':
-tests/qapi-schema/union-branch-invalid-dict.json:2: 'data' member 'integer' misses key 'type'
+union-branch-invalid-dict.json: In union 'UnionInvalidBranch':
+union-branch-invalid-dict.json:2: 'data' member 'integer' misses key 'type'
diff --git a/tests/qapi-schema/union-branch-invalid-dict.exit b/tests/qapi-schema/union-branch-invalid-dict.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-branch-invalid-dict.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-clash-branches.err b/tests/qapi-schema/union-clash-branches.err
index 931399f076..73bbc2cabd 100644
--- a/tests/qapi-schema/union-clash-branches.err
+++ b/tests/qapi-schema/union-clash-branches.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-clash-branches.json: In union 'TestUnion':
-tests/qapi-schema/union-clash-branches.json:4: branch 'a_b' collides with branch 'a-b'
+union-clash-branches.json: In union 'TestUnion':
+union-clash-branches.json:4: branch 'a_b' collides with branch 'a-b'
diff --git a/tests/qapi-schema/union-clash-branches.exit b/tests/qapi-schema/union-clash-branches.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-clash-branches.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-empty.err b/tests/qapi-schema/union-empty.err
index 35c0d62eb0..59788c94ce 100644
--- a/tests/qapi-schema/union-empty.err
+++ b/tests/qapi-schema/union-empty.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-empty.json: In union 'Union':
-tests/qapi-schema/union-empty.json:2: union has no branches
+union-empty.json: In union 'Union':
+union-empty.json:2: union has no branches
diff --git a/tests/qapi-schema/union-empty.exit b/tests/qapi-schema/union-empty.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-empty.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-invalid-base.err b/tests/qapi-schema/union-invalid-base.err
index 10fecf0b56..a804028438 100644
--- a/tests/qapi-schema/union-invalid-base.err
+++ b/tests/qapi-schema/union-invalid-base.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-invalid-base.json: In union 'TestUnion':
-tests/qapi-schema/union-invalid-base.json:8: 'base' requires a struct type, built-in type 'int' isn't
+union-invalid-base.json: In union 'TestUnion':
+union-invalid-base.json:8: 'base' requires a struct type, built-in type 'int' isn't
diff --git a/tests/qapi-schema/union-invalid-base.exit b/tests/qapi-schema/union-invalid-base.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-invalid-base.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-optional-branch.err b/tests/qapi-schema/union-optional-branch.err
index 9f24274923..b33f111de4 100644
--- a/tests/qapi-schema/union-optional-branch.err
+++ b/tests/qapi-schema/union-optional-branch.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-optional-branch.json: In union 'Union':
-tests/qapi-schema/union-optional-branch.json:2: 'data' member '*a' has an invalid name
+union-optional-branch.json: In union 'Union':
+union-optional-branch.json:2: 'data' member '*a' has an invalid name
diff --git a/tests/qapi-schema/union-optional-branch.exit b/tests/qapi-schema/union-optional-branch.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-optional-branch.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/union-unknown.err b/tests/qapi-schema/union-unknown.err
index a7f340ddca..7aba9f94da 100644
--- a/tests/qapi-schema/union-unknown.err
+++ b/tests/qapi-schema/union-unknown.err
@@ -1,2 +1,2 @@
-tests/qapi-schema/union-unknown.json: In union 'Union':
-tests/qapi-schema/union-unknown.json:2: union uses unknown type 'MissingType'
+union-unknown.json: In union 'Union':
+union-unknown.json:2: union uses unknown type 'MissingType'
diff --git a/tests/qapi-schema/union-unknown.exit b/tests/qapi-schema/union-unknown.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/union-unknown.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/unknown-escape.err b/tests/qapi-schema/unknown-escape.err
index e24bbaf046..759a5990d8 100644
--- a/tests/qapi-schema/unknown-escape.err
+++ b/tests/qapi-schema/unknown-escape.err
@@ -1 +1 @@
-tests/qapi-schema/unknown-escape.json:3:21: unknown escape \x
+unknown-escape.json:3:21: unknown escape \x
diff --git a/tests/qapi-schema/unknown-escape.exit b/tests/qapi-schema/unknown-escape.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/unknown-escape.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err
index be9f99c4ef..c5f395bf79 100644
--- a/tests/qapi-schema/unknown-expr-key.err
+++ b/tests/qapi-schema/unknown-expr-key.err
@@ -1,3 +1,3 @@
-tests/qapi-schema/unknown-expr-key.json: In struct 'bar':
-tests/qapi-schema/unknown-expr-key.json:2: struct has unknown keys 'bogus', 'phony'
+unknown-expr-key.json: In struct 'bar':
+unknown-expr-key.json:2: struct has unknown keys 'bogus', 'phony'
Valid keys are 'base', 'data', 'features', 'if', 'struct'.
diff --git a/tests/qapi-schema/unknown-expr-key.exit b/tests/qapi-schema/unknown-expr-key.exit
deleted file mode 100644
index d00491fd7e..0000000000
--- a/tests/qapi-schema/unknown-expr-key.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/test-qmp-cmds.c b/tests/test-qmp-cmds.c
index 36fdf5b115..27b0afe55a 100644
--- a/tests/test-qmp-cmds.c
+++ b/tests/test-qmp-cmds.c
@@ -51,6 +51,30 @@ void qmp_test_features(FeatureStruct0 *fs0, FeatureStruct1 *fs1,
{
}
+void qmp_test_command_features0(Error **errp)
+{
+}
+
+void qmp_test_command_features1(Error **errp)
+{
+}
+
+void qmp_test_command_features3(Error **errp)
+{
+}
+
+void qmp_test_command_cond_features1(Error **errp)
+{
+}
+
+void qmp_test_command_cond_features2(Error **errp)
+{
+}
+
+void qmp_test_command_cond_features3(Error **errp)
+{
+}
+
UserDefTwo *qmp_user_def_cmd2(UserDefOne *ud1a,
bool has_udb1, UserDefOne *ud1b,
Error **errp)