All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/7] qapi: Cleanups and test speedup
@ 2019-10-18  7:43 Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 1/7] qapi: Don't suppress doc generation without pragma doc-required Markus Armbruster
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Markus Armbruster @ 2019-10-18  7:43 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, marcandre.lureau, mdroth

v2:
* PATCH 4,6
  - Commit message improved [Eric]
* PATCH 6
  - Makefiles updated [Kevin]
  - Import statements fixed

Markus Armbruster (7):
  qapi: Don't suppress doc generation without pragma doc-required
  qapi: Store pragma state in QAPISourceInfo, not global state
  qapi: Eliminate accidental global frontend state
  qapi: Speed up frontend tests
  qapi: Move gen_enum(), gen_enum_lookup() back to qapi/types.py
  qapi: Split up scripts/qapi/common.py
  qapi: Clear scripts/qapi/doc.py executable bits again

 Makefile                                      |   13 +-
 scripts/qapi-gen.py                           |   10 +-
 scripts/qapi/commands.py                      |    1 +
 scripts/qapi/common.py                        | 2377 -----------------
 scripts/qapi/doc.py                           |   10 +-
 scripts/qapi/error.py                         |   42 +
 scripts/qapi/events.py                        |    3 +
 scripts/qapi/expr.py                          |  377 +++
 scripts/qapi/gen.py                           |  290 ++
 scripts/qapi/introspect.py                    |    5 +
 scripts/qapi/parser.py                        |  569 ++++
 scripts/qapi/schema.py                        | 1042 ++++++++
 scripts/qapi/source.py                        |   67 +
 scripts/qapi/types.py                         |   61 +
 scripts/qapi/visit.py                         |    2 +
 tests/Makefile.include                        |   30 +-
 tests/qapi-schema/allow-preconfig-test.err    |    4 +-
 tests/qapi-schema/allow-preconfig-test.exit   |    1 -
 tests/qapi-schema/alternate-any.err           |    4 +-
 tests/qapi-schema/alternate-any.exit          |    1 -
 tests/qapi-schema/alternate-array.err         |    4 +-
 tests/qapi-schema/alternate-array.exit        |    1 -
 tests/qapi-schema/alternate-base.err          |    4 +-
 tests/qapi-schema/alternate-base.exit         |    1 -
 .../alternate-branch-if-invalid.err           |    4 +-
 .../alternate-branch-if-invalid.exit          |    1 -
 tests/qapi-schema/alternate-clash.err         |    4 +-
 tests/qapi-schema/alternate-clash.exit        |    1 -
 .../alternate-conflict-bool-string.err        |    4 +-
 .../alternate-conflict-bool-string.exit       |    1 -
 tests/qapi-schema/alternate-conflict-dict.err |    4 +-
 .../qapi-schema/alternate-conflict-dict.exit  |    1 -
 .../alternate-conflict-enum-bool.err          |    4 +-
 .../alternate-conflict-enum-bool.exit         |    1 -
 .../alternate-conflict-enum-int.err           |    4 +-
 .../alternate-conflict-enum-int.exit          |    1 -
 .../alternate-conflict-num-string.err         |    4 +-
 .../alternate-conflict-num-string.exit        |    1 -
 .../qapi-schema/alternate-conflict-string.err |    4 +-
 .../alternate-conflict-string.exit            |    1 -
 tests/qapi-schema/alternate-empty.err         |    4 +-
 tests/qapi-schema/alternate-empty.exit        |    1 -
 tests/qapi-schema/alternate-invalid-dict.err  |    4 +-
 tests/qapi-schema/alternate-invalid-dict.exit |    1 -
 tests/qapi-schema/alternate-nested.err        |    4 +-
 tests/qapi-schema/alternate-nested.exit       |    1 -
 tests/qapi-schema/alternate-unknown.err       |    4 +-
 tests/qapi-schema/alternate-unknown.exit      |    1 -
 tests/qapi-schema/args-alternate.err          |    4 +-
 tests/qapi-schema/args-alternate.exit         |    1 -
 tests/qapi-schema/args-any.err                |    4 +-
 tests/qapi-schema/args-any.exit               |    1 -
 tests/qapi-schema/args-array-empty.err        |    4 +-
 tests/qapi-schema/args-array-empty.exit       |    1 -
 tests/qapi-schema/args-array-unknown.err      |    4 +-
 tests/qapi-schema/args-array-unknown.exit     |    1 -
 tests/qapi-schema/args-bad-boxed.err          |    4 +-
 tests/qapi-schema/args-bad-boxed.exit         |    1 -
 tests/qapi-schema/args-boxed-anon.err         |    4 +-
 tests/qapi-schema/args-boxed-anon.exit        |    1 -
 tests/qapi-schema/args-boxed-string.err       |    4 +-
 tests/qapi-schema/args-boxed-string.exit      |    1 -
 tests/qapi-schema/args-int.err                |    4 +-
 tests/qapi-schema/args-int.exit               |    1 -
 tests/qapi-schema/args-invalid.err            |    4 +-
 tests/qapi-schema/args-invalid.exit           |    1 -
 tests/qapi-schema/args-member-array-bad.err   |    4 +-
 tests/qapi-schema/args-member-array-bad.exit  |    1 -
 tests/qapi-schema/args-member-case.err        |    4 +-
 tests/qapi-schema/args-member-case.exit       |    1 -
 tests/qapi-schema/args-member-unknown.err     |    4 +-
 tests/qapi-schema/args-member-unknown.exit    |    1 -
 tests/qapi-schema/args-name-clash.err         |    4 +-
 tests/qapi-schema/args-name-clash.exit        |    1 -
 tests/qapi-schema/args-union.err              |    4 +-
 tests/qapi-schema/args-union.exit             |    1 -
 tests/qapi-schema/args-unknown.err            |    4 +-
 tests/qapi-schema/args-unknown.exit           |    1 -
 tests/qapi-schema/bad-base.err                |    4 +-
 tests/qapi-schema/bad-base.exit               |    1 -
 tests/qapi-schema/bad-data.err                |    4 +-
 tests/qapi-schema/bad-data.exit               |    1 -
 tests/qapi-schema/bad-ident.err               |    4 +-
 tests/qapi-schema/bad-ident.exit              |    1 -
 tests/qapi-schema/bad-if-empty-list.err       |    4 +-
 tests/qapi-schema/bad-if-empty-list.exit      |    1 -
 tests/qapi-schema/bad-if-empty.err            |    4 +-
 tests/qapi-schema/bad-if-empty.exit           |    1 -
 tests/qapi-schema/bad-if-list.err             |    4 +-
 tests/qapi-schema/bad-if-list.exit            |    1 -
 tests/qapi-schema/bad-if.err                  |    4 +-
 tests/qapi-schema/bad-if.exit                 |    1 -
 tests/qapi-schema/bad-type-bool.err           |    2 +-
 tests/qapi-schema/bad-type-bool.exit          |    1 -
 tests/qapi-schema/bad-type-dict.err           |    2 +-
 tests/qapi-schema/bad-type-dict.exit          |    1 -
 tests/qapi-schema/bad-type-int.err            |    2 +-
 tests/qapi-schema/bad-type-int.exit           |    1 -
 tests/qapi-schema/base-cycle-direct.err       |    4 +-
 tests/qapi-schema/base-cycle-direct.exit      |    1 -
 tests/qapi-schema/base-cycle-indirect.err     |    4 +-
 tests/qapi-schema/base-cycle-indirect.exit    |    1 -
 tests/qapi-schema/command-int.err             |    4 +-
 tests/qapi-schema/command-int.exit            |    1 -
 tests/qapi-schema/comments.exit               |    1 -
 .../qapi-schema/doc-bad-alternate-member.err  |    2 +-
 .../qapi-schema/doc-bad-alternate-member.exit |    1 -
 tests/qapi-schema/doc-bad-command-arg.err     |    2 +-
 tests/qapi-schema/doc-bad-command-arg.exit    |    1 -
 tests/qapi-schema/doc-bad-section.exit        |    1 -
 tests/qapi-schema/doc-bad-symbol.err          |    4 +-
 tests/qapi-schema/doc-bad-symbol.exit         |    1 -
 tests/qapi-schema/doc-bad-union-member.err    |    2 +-
 tests/qapi-schema/doc-bad-union-member.exit   |    1 -
 tests/qapi-schema/doc-before-include.err      |    2 +-
 tests/qapi-schema/doc-before-include.exit     |    1 -
 tests/qapi-schema/doc-before-pragma.err       |    2 +-
 tests/qapi-schema/doc-before-pragma.exit      |    1 -
 tests/qapi-schema/doc-duplicated-arg.err      |    2 +-
 tests/qapi-schema/doc-duplicated-arg.exit     |    1 -
 tests/qapi-schema/doc-duplicated-return.err   |    2 +-
 tests/qapi-schema/doc-duplicated-return.exit  |    1 -
 tests/qapi-schema/doc-duplicated-since.err    |    2 +-
 tests/qapi-schema/doc-duplicated-since.exit   |    1 -
 tests/qapi-schema/doc-empty-arg.err           |    2 +-
 tests/qapi-schema/doc-empty-arg.exit          |    1 -
 tests/qapi-schema/doc-empty-section.err       |    2 +-
 tests/qapi-schema/doc-empty-section.exit      |    1 -
 tests/qapi-schema/doc-empty-symbol.err        |    2 +-
 tests/qapi-schema/doc-empty-symbol.exit       |    1 -
 tests/qapi-schema/doc-good.exit               |    1 -
 tests/qapi-schema/doc-interleaved-section.err |    2 +-
 .../qapi-schema/doc-interleaved-section.exit  |    1 -
 tests/qapi-schema/doc-invalid-end.err         |    2 +-
 tests/qapi-schema/doc-invalid-end.exit        |    1 -
 tests/qapi-schema/doc-invalid-end2.err        |    2 +-
 tests/qapi-schema/doc-invalid-end2.exit       |    1 -
 tests/qapi-schema/doc-invalid-return.err      |    2 +-
 tests/qapi-schema/doc-invalid-return.exit     |    1 -
 tests/qapi-schema/doc-invalid-section.err     |    2 +-
 tests/qapi-schema/doc-invalid-section.exit    |    1 -
 tests/qapi-schema/doc-invalid-start.err       |    2 +-
 tests/qapi-schema/doc-invalid-start.exit      |    1 -
 tests/qapi-schema/doc-missing-colon.err       |    2 +-
 tests/qapi-schema/doc-missing-colon.exit      |    1 -
 tests/qapi-schema/doc-missing-expr.err        |    2 +-
 tests/qapi-schema/doc-missing-expr.exit       |    1 -
 tests/qapi-schema/doc-missing-space.err       |    2 +-
 tests/qapi-schema/doc-missing-space.exit      |    1 -
 tests/qapi-schema/doc-missing.err             |    4 +-
 tests/qapi-schema/doc-missing.exit            |    1 -
 tests/qapi-schema/doc-no-symbol.err           |    2 +-
 tests/qapi-schema/doc-no-symbol.exit          |    1 -
 tests/qapi-schema/double-type.err             |    4 +-
 tests/qapi-schema/double-type.exit            |    1 -
 tests/qapi-schema/duplicate-key.err           |    2 +-
 tests/qapi-schema/duplicate-key.exit          |    1 -
 tests/qapi-schema/empty.exit                  |    1 -
 tests/qapi-schema/enum-bad-member.err         |    4 +-
 tests/qapi-schema/enum-bad-member.exit        |    1 -
 tests/qapi-schema/enum-bad-name.err           |    4 +-
 tests/qapi-schema/enum-bad-name.exit          |    1 -
 tests/qapi-schema/enum-bad-prefix.err         |    4 +-
 tests/qapi-schema/enum-bad-prefix.exit        |    1 -
 tests/qapi-schema/enum-clash-member.err       |    4 +-
 tests/qapi-schema/enum-clash-member.exit      |    1 -
 .../qapi-schema/enum-dict-member-unknown.err  |    4 +-
 .../qapi-schema/enum-dict-member-unknown.exit |    1 -
 tests/qapi-schema/enum-if-invalid.err         |    4 +-
 tests/qapi-schema/enum-if-invalid.exit        |    1 -
 tests/qapi-schema/enum-int-member.err         |    2 +-
 tests/qapi-schema/enum-int-member.exit        |    1 -
 tests/qapi-schema/enum-member-case.err        |    4 +-
 tests/qapi-schema/enum-member-case.exit       |    1 -
 tests/qapi-schema/enum-missing-data.err       |    4 +-
 tests/qapi-schema/enum-missing-data.exit      |    1 -
 tests/qapi-schema/enum-wrong-data.err         |    4 +-
 tests/qapi-schema/enum-wrong-data.exit        |    1 -
 tests/qapi-schema/escape-outside-string.err   |    2 +-
 tests/qapi-schema/event-boxed-empty.err       |    4 +-
 tests/qapi-schema/event-boxed-empty.exit      |    1 -
 tests/qapi-schema/event-case.exit             |    1 -
 .../qapi-schema/event-member-invalid-dict.err |    4 +-
 .../event-member-invalid-dict.exit            |    1 -
 tests/qapi-schema/event-nest-struct.err       |    4 +-
 tests/qapi-schema/event-nest-struct.exit      |    1 -
 tests/qapi-schema/features-bad-type.err       |    4 +-
 tests/qapi-schema/features-bad-type.exit      |    1 -
 tests/qapi-schema/features-duplicate-name.err |    4 +-
 .../qapi-schema/features-duplicate-name.exit  |    1 -
 tests/qapi-schema/features-if-invalid.err     |    4 +-
 tests/qapi-schema/features-if-invalid.exit    |    1 -
 tests/qapi-schema/features-missing-name.err   |    4 +-
 tests/qapi-schema/features-missing-name.exit  |    1 -
 tests/qapi-schema/features-name-bad-type.err  |    4 +-
 tests/qapi-schema/features-name-bad-type.exit |    1 -
 tests/qapi-schema/features-no-list.err        |    4 +-
 tests/qapi-schema/features-no-list.exit       |    1 -
 tests/qapi-schema/features-unknown-key.err    |    4 +-
 tests/qapi-schema/features-unknown-key.exit   |    1 -
 tests/qapi-schema/flat-union-array-branch.err |    4 +-
 .../qapi-schema/flat-union-array-branch.exit  |    1 -
 tests/qapi-schema/flat-union-bad-base.err     |    4 +-
 tests/qapi-schema/flat-union-bad-base.exit    |    1 -
 .../flat-union-bad-discriminator.err          |    4 +-
 .../flat-union-bad-discriminator.exit         |    1 -
 tests/qapi-schema/flat-union-base-any.err     |    4 +-
 tests/qapi-schema/flat-union-base-any.exit    |    1 -
 tests/qapi-schema/flat-union-base-union.err   |    4 +-
 tests/qapi-schema/flat-union-base-union.exit  |    1 -
 tests/qapi-schema/flat-union-clash-member.err |    4 +-
 .../qapi-schema/flat-union-clash-member.exit  |    1 -
 .../flat-union-discriminator-bad-name.err     |    4 +-
 .../flat-union-discriminator-bad-name.exit    |    1 -
 tests/qapi-schema/flat-union-empty.err        |    4 +-
 tests/qapi-schema/flat-union-empty.exit       |    1 -
 .../flat-union-inline-invalid-dict.err        |    4 +-
 .../flat-union-inline-invalid-dict.exit       |    1 -
 tests/qapi-schema/flat-union-inline.err       |    4 +-
 tests/qapi-schema/flat-union-inline.exit      |    1 -
 tests/qapi-schema/flat-union-int-branch.err   |    4 +-
 tests/qapi-schema/flat-union-int-branch.exit  |    1 -
 .../flat-union-invalid-branch-key.err         |    4 +-
 .../flat-union-invalid-branch-key.exit        |    1 -
 .../flat-union-invalid-discriminator.err      |    4 +-
 .../flat-union-invalid-discriminator.exit     |    1 -
 .../flat-union-invalid-if-discriminator.err   |    4 +-
 .../flat-union-invalid-if-discriminator.exit  |    1 -
 tests/qapi-schema/flat-union-no-base.err      |    4 +-
 tests/qapi-schema/flat-union-no-base.exit     |    1 -
 .../flat-union-optional-discriminator.err     |    4 +-
 .../flat-union-optional-discriminator.exit    |    1 -
 .../flat-union-string-discriminator.err       |    4 +-
 .../flat-union-string-discriminator.exit      |    1 -
 tests/qapi-schema/funny-char.err              |    2 +-
 tests/qapi-schema/funny-char.exit             |    1 -
 tests/qapi-schema/funny-word.err              |    2 +-
 tests/qapi-schema/funny-word.exit             |    1 -
 tests/qapi-schema/ident-with-escape.err       |    2 +-
 tests/qapi-schema/ident-with-escape.exit      |    1 -
 tests/qapi-schema/include-before-err.err      |    2 +-
 tests/qapi-schema/include-before-err.exit     |    1 -
 tests/qapi-schema/include-cycle.err           |    6 +-
 tests/qapi-schema/include-cycle.exit          |    1 -
 tests/qapi-schema/include-extra-junk.err      |    2 +-
 tests/qapi-schema/include-extra-junk.exit     |    1 -
 tests/qapi-schema/include-nested-err.err      |    4 +-
 tests/qapi-schema/include-nested-err.exit     |    1 -
 tests/qapi-schema/include-no-file.err         |    2 +-
 tests/qapi-schema/include-no-file.exit        |    1 -
 tests/qapi-schema/include-non-file.err        |    2 +-
 tests/qapi-schema/include-non-file.exit       |    1 -
 tests/qapi-schema/include-repetition.exit     |    1 -
 tests/qapi-schema/include-self-cycle.err      |    2 +-
 tests/qapi-schema/include-self-cycle.exit     |    1 -
 tests/qapi-schema/include-simple.exit         |    1 -
 tests/qapi-schema/indented-expr.exit          |    1 -
 tests/qapi-schema/leading-comma-list.err      |    2 +-
 tests/qapi-schema/leading-comma-list.exit     |    1 -
 tests/qapi-schema/leading-comma-object.err    |    2 +-
 tests/qapi-schema/leading-comma-object.exit   |    1 -
 tests/qapi-schema/missing-colon.err           |    2 +-
 tests/qapi-schema/missing-colon.exit          |    1 -
 tests/qapi-schema/missing-comma-list.err      |    2 +-
 tests/qapi-schema/missing-comma-list.exit     |    1 -
 tests/qapi-schema/missing-comma-object.err    |    2 +-
 tests/qapi-schema/missing-comma-object.exit   |    1 -
 tests/qapi-schema/missing-type.err            |    2 +-
 tests/qapi-schema/missing-type.exit           |    1 -
 .../nested-struct-data-invalid-dict.err       |    4 +-
 .../nested-struct-data-invalid-dict.exit      |    1 -
 tests/qapi-schema/nested-struct-data.err      |    4 +-
 tests/qapi-schema/nested-struct-data.exit     |    1 -
 tests/qapi-schema/non-objects.err             |    2 +-
 tests/qapi-schema/non-objects.exit            |    1 -
 tests/qapi-schema/oob-test.err                |    4 +-
 tests/qapi-schema/oob-test.exit               |    1 -
 .../qapi-schema/pragma-doc-required-crap.err  |    2 +-
 .../qapi-schema/pragma-doc-required-crap.exit |    1 -
 tests/qapi-schema/pragma-extra-junk.err       |    2 +-
 tests/qapi-schema/pragma-extra-junk.exit      |    1 -
 .../pragma-name-case-whitelist-crap.err       |    2 +-
 .../pragma-name-case-whitelist-crap.exit      |    1 -
 tests/qapi-schema/pragma-non-dict.err         |    2 +-
 tests/qapi-schema/pragma-non-dict.exit        |    1 -
 .../pragma-returns-whitelist-crap.err         |    2 +-
 .../pragma-returns-whitelist-crap.exit        |    1 -
 tests/qapi-schema/pragma-unknown.err          |    2 +-
 tests/qapi-schema/pragma-unknown.exit         |    1 -
 tests/qapi-schema/qapi-schema-test.exit       |    1 -
 tests/qapi-schema/quoted-structural-chars.err |    2 +-
 .../qapi-schema/quoted-structural-chars.exit  |    1 -
 tests/qapi-schema/redefined-builtin.err       |    4 +-
 tests/qapi-schema/redefined-builtin.exit      |    1 -
 tests/qapi-schema/redefined-command.err       |    8 +-
 tests/qapi-schema/redefined-command.exit      |    1 -
 tests/qapi-schema/redefined-event.err         |    8 +-
 tests/qapi-schema/redefined-event.exit        |    1 -
 tests/qapi-schema/redefined-type.err          |    8 +-
 tests/qapi-schema/redefined-type.exit         |    1 -
 tests/qapi-schema/reserved-command-q.err      |    4 +-
 tests/qapi-schema/reserved-command-q.exit     |    1 -
 tests/qapi-schema/reserved-enum-q.err         |    4 +-
 tests/qapi-schema/reserved-enum-q.exit        |    1 -
 tests/qapi-schema/reserved-member-has.err     |    4 +-
 tests/qapi-schema/reserved-member-has.exit    |    1 -
 tests/qapi-schema/reserved-member-q.err       |    4 +-
 tests/qapi-schema/reserved-member-q.exit      |    1 -
 tests/qapi-schema/reserved-member-u.err       |    4 +-
 tests/qapi-schema/reserved-member-u.exit      |    1 -
 .../reserved-member-underscore.err            |    4 +-
 .../reserved-member-underscore.exit           |    1 -
 tests/qapi-schema/reserved-type-kind.err      |    4 +-
 tests/qapi-schema/reserved-type-kind.exit     |    1 -
 tests/qapi-schema/reserved-type-list.err      |    4 +-
 tests/qapi-schema/reserved-type-list.exit     |    1 -
 tests/qapi-schema/returns-alternate.err       |    4 +-
 tests/qapi-schema/returns-alternate.exit      |    1 -
 tests/qapi-schema/returns-array-bad.err       |    4 +-
 tests/qapi-schema/returns-array-bad.exit      |    1 -
 tests/qapi-schema/returns-dict.err            |    4 +-
 tests/qapi-schema/returns-dict.exit           |    1 -
 tests/qapi-schema/returns-unknown.err         |    4 +-
 tests/qapi-schema/returns-unknown.exit        |    1 -
 tests/qapi-schema/returns-whitelist.err       |    4 +-
 tests/qapi-schema/returns-whitelist.exit      |    1 -
 tests/qapi-schema/string-code-point-127.err   |    2 +-
 tests/qapi-schema/string-code-point-127.exit  |    1 -
 tests/qapi-schema/string-code-point-31.err    |    2 +-
 tests/qapi-schema/string-code-point-31.exit   |    1 -
 tests/qapi-schema/struct-base-clash-deep.err  |    4 +-
 tests/qapi-schema/struct-base-clash-deep.exit |    1 -
 tests/qapi-schema/struct-base-clash.err       |    4 +-
 tests/qapi-schema/struct-base-clash.exit      |    1 -
 tests/qapi-schema/struct-data-invalid.err     |    4 +-
 tests/qapi-schema/struct-data-invalid.exit    |    1 -
 .../qapi-schema/struct-member-if-invalid.err  |    4 +-
 .../qapi-schema/struct-member-if-invalid.exit |    1 -
 .../struct-member-invalid-dict.err            |    4 +-
 .../struct-member-invalid-dict.exit           |    1 -
 tests/qapi-schema/struct-member-invalid.err   |    4 +-
 tests/qapi-schema/struct-member-invalid.exit  |    1 -
 tests/qapi-schema/test-qapi.py                |  125 +-
 tests/qapi-schema/trailing-comma-list.err     |    2 +-
 tests/qapi-schema/trailing-comma-list.exit    |    1 -
 tests/qapi-schema/trailing-comma-object.err   |    2 +-
 tests/qapi-schema/trailing-comma-object.exit  |    1 -
 tests/qapi-schema/type-bypass-bad-gen.err     |    4 +-
 tests/qapi-schema/type-bypass-bad-gen.exit    |    1 -
 tests/qapi-schema/unclosed-list.err           |    2 +-
 tests/qapi-schema/unclosed-list.exit          |    1 -
 tests/qapi-schema/unclosed-object.err         |    2 +-
 tests/qapi-schema/unclosed-object.exit        |    1 -
 tests/qapi-schema/unclosed-string.err         |    2 +-
 tests/qapi-schema/unclosed-string.exit        |    1 -
 tests/qapi-schema/union-base-empty.err        |    4 +-
 tests/qapi-schema/union-base-empty.exit       |    1 -
 .../union-base-no-discriminator.err           |    4 +-
 .../union-base-no-discriminator.exit          |    1 -
 tests/qapi-schema/union-branch-case.err       |    4 +-
 tests/qapi-schema/union-branch-case.exit      |    1 -
 tests/qapi-schema/union-branch-if-invalid.err |    4 +-
 .../qapi-schema/union-branch-if-invalid.exit  |    1 -
 .../qapi-schema/union-branch-invalid-dict.err |    4 +-
 .../union-branch-invalid-dict.exit            |    1 -
 tests/qapi-schema/union-clash-branches.err    |    4 +-
 tests/qapi-schema/union-clash-branches.exit   |    1 -
 tests/qapi-schema/union-empty.err             |    4 +-
 tests/qapi-schema/union-empty.exit            |    1 -
 tests/qapi-schema/union-invalid-base.err      |    4 +-
 tests/qapi-schema/union-invalid-base.exit     |    1 -
 tests/qapi-schema/union-optional-branch.err   |    4 +-
 tests/qapi-schema/union-optional-branch.exit  |    1 -
 tests/qapi-schema/union-unknown.err           |    4 +-
 tests/qapi-schema/union-unknown.exit          |    1 -
 tests/qapi-schema/unknown-escape.err          |    2 +-
 tests/qapi-schema/unknown-escape.exit         |    1 -
 tests/qapi-schema/unknown-expr-key.err        |    4 +-
 tests/qapi-schema/unknown-expr-key.exit       |    1 -
 379 files changed, 2908 insertions(+), 2909 deletions(-)
 mode change 100755 => 100644 scripts/qapi/doc.py
 create mode 100644 scripts/qapi/error.py
 create mode 100644 scripts/qapi/expr.py
 create mode 100644 scripts/qapi/gen.py
 create mode 100644 scripts/qapi/parser.py
 create mode 100644 scripts/qapi/schema.py
 create mode 100644 scripts/qapi/source.py
 delete mode 100644 tests/qapi-schema/allow-preconfig-test.exit
 delete mode 100644 tests/qapi-schema/alternate-any.exit
 delete mode 100644 tests/qapi-schema/alternate-array.exit
 delete mode 100644 tests/qapi-schema/alternate-base.exit
 delete mode 100644 tests/qapi-schema/alternate-branch-if-invalid.exit
 delete mode 100644 tests/qapi-schema/alternate-clash.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-bool-string.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-dict.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-enum-bool.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-enum-int.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-num-string.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-string.exit
 delete mode 100644 tests/qapi-schema/alternate-empty.exit
 delete mode 100644 tests/qapi-schema/alternate-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/alternate-nested.exit
 delete mode 100644 tests/qapi-schema/alternate-unknown.exit
 delete mode 100644 tests/qapi-schema/args-alternate.exit
 delete mode 100644 tests/qapi-schema/args-any.exit
 delete mode 100644 tests/qapi-schema/args-array-empty.exit
 delete mode 100644 tests/qapi-schema/args-array-unknown.exit
 delete mode 100644 tests/qapi-schema/args-bad-boxed.exit
 delete mode 100644 tests/qapi-schema/args-boxed-anon.exit
 delete mode 100644 tests/qapi-schema/args-boxed-string.exit
 delete mode 100644 tests/qapi-schema/args-int.exit
 delete mode 100644 tests/qapi-schema/args-invalid.exit
 delete mode 100644 tests/qapi-schema/args-member-array-bad.exit
 delete mode 100644 tests/qapi-schema/args-member-case.exit
 delete mode 100644 tests/qapi-schema/args-member-unknown.exit
 delete mode 100644 tests/qapi-schema/args-name-clash.exit
 delete mode 100644 tests/qapi-schema/args-union.exit
 delete mode 100644 tests/qapi-schema/args-unknown.exit
 delete mode 100644 tests/qapi-schema/bad-base.exit
 delete mode 100644 tests/qapi-schema/bad-data.exit
 delete mode 100644 tests/qapi-schema/bad-ident.exit
 delete mode 100644 tests/qapi-schema/bad-if-empty-list.exit
 delete mode 100644 tests/qapi-schema/bad-if-empty.exit
 delete mode 100644 tests/qapi-schema/bad-if-list.exit
 delete mode 100644 tests/qapi-schema/bad-if.exit
 delete mode 100644 tests/qapi-schema/bad-type-bool.exit
 delete mode 100644 tests/qapi-schema/bad-type-dict.exit
 delete mode 100644 tests/qapi-schema/bad-type-int.exit
 delete mode 100644 tests/qapi-schema/base-cycle-direct.exit
 delete mode 100644 tests/qapi-schema/base-cycle-indirect.exit
 delete mode 100644 tests/qapi-schema/command-int.exit
 delete mode 100644 tests/qapi-schema/comments.exit
 delete mode 100644 tests/qapi-schema/doc-bad-alternate-member.exit
 delete mode 100644 tests/qapi-schema/doc-bad-command-arg.exit
 delete mode 100644 tests/qapi-schema/doc-bad-section.exit
 delete mode 100644 tests/qapi-schema/doc-bad-symbol.exit
 delete mode 100644 tests/qapi-schema/doc-bad-union-member.exit
 delete mode 100644 tests/qapi-schema/doc-before-include.exit
 delete mode 100644 tests/qapi-schema/doc-before-pragma.exit
 delete mode 100644 tests/qapi-schema/doc-duplicated-arg.exit
 delete mode 100644 tests/qapi-schema/doc-duplicated-return.exit
 delete mode 100644 tests/qapi-schema/doc-duplicated-since.exit
 delete mode 100644 tests/qapi-schema/doc-empty-arg.exit
 delete mode 100644 tests/qapi-schema/doc-empty-section.exit
 delete mode 100644 tests/qapi-schema/doc-empty-symbol.exit
 delete mode 100644 tests/qapi-schema/doc-good.exit
 delete mode 100644 tests/qapi-schema/doc-interleaved-section.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-end.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-end2.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-return.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-section.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-start.exit
 delete mode 100644 tests/qapi-schema/doc-missing-colon.exit
 delete mode 100644 tests/qapi-schema/doc-missing-expr.exit
 delete mode 100644 tests/qapi-schema/doc-missing-space.exit
 delete mode 100644 tests/qapi-schema/doc-missing.exit
 delete mode 100644 tests/qapi-schema/doc-no-symbol.exit
 delete mode 100644 tests/qapi-schema/double-type.exit
 delete mode 100644 tests/qapi-schema/duplicate-key.exit
 delete mode 100644 tests/qapi-schema/empty.exit
 delete mode 100644 tests/qapi-schema/enum-bad-member.exit
 delete mode 100644 tests/qapi-schema/enum-bad-name.exit
 delete mode 100644 tests/qapi-schema/enum-bad-prefix.exit
 delete mode 100644 tests/qapi-schema/enum-clash-member.exit
 delete mode 100644 tests/qapi-schema/enum-dict-member-unknown.exit
 delete mode 100644 tests/qapi-schema/enum-if-invalid.exit
 delete mode 100644 tests/qapi-schema/enum-int-member.exit
 delete mode 100644 tests/qapi-schema/enum-member-case.exit
 delete mode 100644 tests/qapi-schema/enum-missing-data.exit
 delete mode 100644 tests/qapi-schema/enum-wrong-data.exit
 delete mode 100644 tests/qapi-schema/event-boxed-empty.exit
 delete mode 100644 tests/qapi-schema/event-case.exit
 delete mode 100644 tests/qapi-schema/event-member-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/event-nest-struct.exit
 delete mode 100644 tests/qapi-schema/features-bad-type.exit
 delete mode 100644 tests/qapi-schema/features-duplicate-name.exit
 delete mode 100644 tests/qapi-schema/features-if-invalid.exit
 delete mode 100644 tests/qapi-schema/features-missing-name.exit
 delete mode 100644 tests/qapi-schema/features-name-bad-type.exit
 delete mode 100644 tests/qapi-schema/features-no-list.exit
 delete mode 100644 tests/qapi-schema/features-unknown-key.exit
 delete mode 100644 tests/qapi-schema/flat-union-array-branch.exit
 delete mode 100644 tests/qapi-schema/flat-union-bad-base.exit
 delete mode 100644 tests/qapi-schema/flat-union-bad-discriminator.exit
 delete mode 100644 tests/qapi-schema/flat-union-base-any.exit
 delete mode 100644 tests/qapi-schema/flat-union-base-union.exit
 delete mode 100644 tests/qapi-schema/flat-union-clash-member.exit
 delete mode 100644 tests/qapi-schema/flat-union-discriminator-bad-name.exit
 delete mode 100644 tests/qapi-schema/flat-union-empty.exit
 delete mode 100644 tests/qapi-schema/flat-union-inline-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/flat-union-inline.exit
 delete mode 100644 tests/qapi-schema/flat-union-int-branch.exit
 delete mode 100644 tests/qapi-schema/flat-union-invalid-branch-key.exit
 delete mode 100644 tests/qapi-schema/flat-union-invalid-discriminator.exit
 delete mode 100644 tests/qapi-schema/flat-union-invalid-if-discriminator.exit
 delete mode 100644 tests/qapi-schema/flat-union-no-base.exit
 delete mode 100644 tests/qapi-schema/flat-union-optional-discriminator.exit
 delete mode 100644 tests/qapi-schema/flat-union-string-discriminator.exit
 delete mode 100644 tests/qapi-schema/funny-char.exit
 delete mode 100644 tests/qapi-schema/funny-word.exit
 delete mode 100644 tests/qapi-schema/ident-with-escape.exit
 delete mode 100644 tests/qapi-schema/include-before-err.exit
 delete mode 100644 tests/qapi-schema/include-cycle.exit
 delete mode 100644 tests/qapi-schema/include-extra-junk.exit
 delete mode 100644 tests/qapi-schema/include-nested-err.exit
 delete mode 100644 tests/qapi-schema/include-no-file.exit
 delete mode 100644 tests/qapi-schema/include-non-file.exit
 delete mode 100644 tests/qapi-schema/include-repetition.exit
 delete mode 100644 tests/qapi-schema/include-self-cycle.exit
 delete mode 100644 tests/qapi-schema/include-simple.exit
 delete mode 100644 tests/qapi-schema/indented-expr.exit
 delete mode 100644 tests/qapi-schema/leading-comma-list.exit
 delete mode 100644 tests/qapi-schema/leading-comma-object.exit
 delete mode 100644 tests/qapi-schema/missing-colon.exit
 delete mode 100644 tests/qapi-schema/missing-comma-list.exit
 delete mode 100644 tests/qapi-schema/missing-comma-object.exit
 delete mode 100644 tests/qapi-schema/missing-type.exit
 delete mode 100644 tests/qapi-schema/nested-struct-data-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/nested-struct-data.exit
 delete mode 100644 tests/qapi-schema/non-objects.exit
 delete mode 100644 tests/qapi-schema/oob-test.exit
 delete mode 100644 tests/qapi-schema/pragma-doc-required-crap.exit
 delete mode 100644 tests/qapi-schema/pragma-extra-junk.exit
 delete mode 100644 tests/qapi-schema/pragma-name-case-whitelist-crap.exit
 delete mode 100644 tests/qapi-schema/pragma-non-dict.exit
 delete mode 100644 tests/qapi-schema/pragma-returns-whitelist-crap.exit
 delete mode 100644 tests/qapi-schema/pragma-unknown.exit
 delete mode 100644 tests/qapi-schema/qapi-schema-test.exit
 delete mode 100644 tests/qapi-schema/quoted-structural-chars.exit
 delete mode 100644 tests/qapi-schema/redefined-builtin.exit
 delete mode 100644 tests/qapi-schema/redefined-command.exit
 delete mode 100644 tests/qapi-schema/redefined-event.exit
 delete mode 100644 tests/qapi-schema/redefined-type.exit
 delete mode 100644 tests/qapi-schema/reserved-command-q.exit
 delete mode 100644 tests/qapi-schema/reserved-enum-q.exit
 delete mode 100644 tests/qapi-schema/reserved-member-has.exit
 delete mode 100644 tests/qapi-schema/reserved-member-q.exit
 delete mode 100644 tests/qapi-schema/reserved-member-u.exit
 delete mode 100644 tests/qapi-schema/reserved-member-underscore.exit
 delete mode 100644 tests/qapi-schema/reserved-type-kind.exit
 delete mode 100644 tests/qapi-schema/reserved-type-list.exit
 delete mode 100644 tests/qapi-schema/returns-alternate.exit
 delete mode 100644 tests/qapi-schema/returns-array-bad.exit
 delete mode 100644 tests/qapi-schema/returns-dict.exit
 delete mode 100644 tests/qapi-schema/returns-unknown.exit
 delete mode 100644 tests/qapi-schema/returns-whitelist.exit
 delete mode 100644 tests/qapi-schema/string-code-point-127.exit
 delete mode 100644 tests/qapi-schema/string-code-point-31.exit
 delete mode 100644 tests/qapi-schema/struct-base-clash-deep.exit
 delete mode 100644 tests/qapi-schema/struct-base-clash.exit
 delete mode 100644 tests/qapi-schema/struct-data-invalid.exit
 delete mode 100644 tests/qapi-schema/struct-member-if-invalid.exit
 delete mode 100644 tests/qapi-schema/struct-member-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/struct-member-invalid.exit
 mode change 100644 => 100755 tests/qapi-schema/test-qapi.py
 delete mode 100644 tests/qapi-schema/trailing-comma-list.exit
 delete mode 100644 tests/qapi-schema/trailing-comma-object.exit
 delete mode 100644 tests/qapi-schema/type-bypass-bad-gen.exit
 delete mode 100644 tests/qapi-schema/unclosed-list.exit
 delete mode 100644 tests/qapi-schema/unclosed-object.exit
 delete mode 100644 tests/qapi-schema/unclosed-string.exit
 delete mode 100644 tests/qapi-schema/union-base-empty.exit
 delete mode 100644 tests/qapi-schema/union-base-no-discriminator.exit
 delete mode 100644 tests/qapi-schema/union-branch-case.exit
 delete mode 100644 tests/qapi-schema/union-branch-if-invalid.exit
 delete mode 100644 tests/qapi-schema/union-branch-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/union-clash-branches.exit
 delete mode 100644 tests/qapi-schema/union-empty.exit
 delete mode 100644 tests/qapi-schema/union-invalid-base.exit
 delete mode 100644 tests/qapi-schema/union-optional-branch.exit
 delete mode 100644 tests/qapi-schema/union-unknown.exit
 delete mode 100644 tests/qapi-schema/unknown-escape.exit
 delete mode 100644 tests/qapi-schema/unknown-expr-key.exit

-- 
2.21.0



^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 1/7] qapi: Don't suppress doc generation without pragma doc-required
  2019-10-18  7:43 [PATCH v2 0/7] qapi: Cleanups and test speedup Markus Armbruster
@ 2019-10-18  7:43 ` Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 2/7] qapi: Store pragma state in QAPISourceInfo, not global state Markus Armbruster
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Markus Armbruster @ 2019-10-18  7:43 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, marcandre.lureau, mdroth

Commit bc52d03ff5 "qapi: Make doc comments optional where we don't
need them" made scripts/qapi2texi.py fail[*] unless the schema had
pragma 'doc-required': true.  The stated reason was inability to cope
with incomplete documentation.

When commit fb0bc835e5 "qapi-gen: New common driver for code and doc
generators" folded scripts/qapi2texi.py into scripts/qapi-gen.py, it
turned the failure into silent suppression.

The doc generator can cope with incomplete documentation now.  I don't
know since when, or what the problem was, or even whether it ever
existed.

Drop the silent suppression.

[*] The fail part was broken, fixed in commit e8ba07ea9a.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi/doc.py    | 2 --
 tests/Makefile.include | 1 +
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 5fc0fc7e06..693cc4486b 100755
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -283,8 +283,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/tests/Makefile.include b/tests/Makefile.include
index 3543451ed3..214fbd941c 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -609,6 +609,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)
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 2/7] qapi: Store pragma state in QAPISourceInfo, not global state
  2019-10-18  7:43 [PATCH v2 0/7] qapi: Cleanups and test speedup Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 1/7] qapi: Don't suppress doc generation without pragma doc-required Markus Armbruster
@ 2019-10-18  7:43 ` Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 3/7] qapi: Eliminate accidental global frontend state Markus Armbruster
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Markus Armbruster @ 2019-10-18  7:43 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, marcandre.lureau, mdroth

The frontend can't be run more than once due to its global state.
A future commit will want to do that.

Recent commit "qapi: Move context-sensitive checking to the proper
place" got rid of many global variables already, but pragma state is
still stored in global variables (that's why a pragma directive's
scope is the complete schema).

Move the pragma state to QAPISourceInfo.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi/common.py | 36 +++++++++++++++++++-----------------
 1 file changed, 19 insertions(+), 17 deletions(-)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index d6e00c80ea..5abab44302 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -21,25 +21,28 @@ 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 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
 
@@ -486,26 +489,25 @@ class QAPISchemaParser(object):
         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
+            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")
-            returns_whitelist = value
+            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")
-            name_case_whitelist = value
+            info.pragma.name_case_whitelist = value
         else:
             raise QAPISemError(info, "unknown pragma '%s'" % name)
 
@@ -757,7 +759,7 @@ def check_type(value, info, source,
         raise QAPISemError(info,
                            "%s should be an object or type name" % source)
 
-    permit_upper = allow_dict in name_case_whitelist
+    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():
@@ -840,7 +842,7 @@ def check_enum(expr, info):
     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
+    permit_upper = name in info.pragma.name_case_whitelist
 
     for member in members:
         source = "'data' member"
@@ -968,7 +970,7 @@ def check_exprs(exprs):
                 raise QAPISemError(
                     info, "documentation comment is for '%s'" % doc.symbol)
             doc.check_expr(expr)
-        elif doc_required:
+        elif info.pragma.doc_required:
             raise QAPISemError(info,
                                "documentation comment required")
 
@@ -1690,7 +1692,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
         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 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,
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 3/7] qapi: Eliminate accidental global frontend state
  2019-10-18  7:43 [PATCH v2 0/7] qapi: Cleanups and test speedup Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 1/7] qapi: Don't suppress doc generation without pragma doc-required Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 2/7] qapi: Store pragma state in QAPISourceInfo, not global state Markus Armbruster
@ 2019-10-18  7:43 ` Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 4/7] qapi: Speed up frontend tests Markus Armbruster
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Markus Armbruster @ 2019-10-18  7:43 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, marcandre.lureau, mdroth

The frontend can't be run more than once due to its global state.
A future commit will want to do that.

The only global frontend state remaining is accidental:
QAPISchemaParser.__init__()'s parameter previously_included=[].
Python evaluates the default once, at definition time.  Any
modifications to it are visible in subsequent calls.  Well-known
Python trap.  Change the default to None and replace it by the real
default in the function body.  Use the opportunity to convert
previously_included to a set.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi/common.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 5abab44302..9d5c05f6a1 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -391,8 +391,9 @@ class QAPIDoc(object):
 
 class QAPISchemaParser(object):
 
-    def __init__(self, fname, previously_included=[], incl_info=None):
-        previously_included.append(os.path.abspath(fname))
+    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:
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 4/7] qapi: Speed up frontend tests
  2019-10-18  7:43 [PATCH v2 0/7] qapi: Cleanups and test speedup Markus Armbruster
                   ` (2 preceding siblings ...)
  2019-10-18  7:43 ` [PATCH v2 3/7] qapi: Eliminate accidental global frontend state Markus Armbruster
@ 2019-10-18  7:43 ` Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 5/7] qapi: Move gen_enum(), gen_enum_lookup() back to qapi/types.py Markus Armbruster
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Markus Armbruster @ 2019-10-18  7:43 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, marcandre.lureau, mdroth

"make check-qapi-schema" takes around 10s user + system time for me.
With -j, it takes a bit over 3s real time.  We have worse tests.  It's
still annoying when you work on the QAPI generator.

Some 1.4s user + system time is consumed by make figuring out what to
do, measured by making a target that does nothing.  There's nothing I
can do about that right now.  But let's see what we can do about the
other 8s.

Almost 7s are spent running test-qapi.py for every test case, the rest
normalizing and diffing test-qapi.py output.  We have 190 test cases.

If I downgrade to python2, it's 4.5s, but python2 is a goner.

Hacking up test-qapi.py to exit(0) without doing anything makes it
only marginally faster.  The problem is Python startup overhead.

Our configure puts -B into $(PYTHON).  Running without -B is faster:
4.4s.

We could improve the Makefile to run test cases only when the test
case or the generator changed.  But I'm after improvement in the case
where the generator changed.

test-qapi.py is designed to be the simplest possible building block
for a shell script to do the complete job (it's actually a Makefile,
not a shell script; no real difference).  Python is just not meant for
that.  It's for bigger blocks.

Move the post-processing and diffing into test-qapi.py, and make it
capable of testing multiple schema files.  Set executable bits while
there.

Running it once per test case now takes slightly longer than 8s.  But
running it once for all of them takes under 0.2s.

Messing with the Makefile to run it only on the tests that need
retesting is clearly not worth the bother.

Expected error output changes because the new normalization strips off
$(SRCDIR)/tests/qapi-schema/ instead of just $(SRCDIR)/.

The .exit files go away, because there is no exit status to test
anymore.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 tests/Makefile.include                        |  16 +--
 tests/qapi-schema/allow-preconfig-test.err    |   4 +-
 tests/qapi-schema/allow-preconfig-test.exit   |   1 -
 tests/qapi-schema/alternate-any.err           |   4 +-
 tests/qapi-schema/alternate-any.exit          |   1 -
 tests/qapi-schema/alternate-array.err         |   4 +-
 tests/qapi-schema/alternate-array.exit        |   1 -
 tests/qapi-schema/alternate-base.err          |   4 +-
 tests/qapi-schema/alternate-base.exit         |   1 -
 .../alternate-branch-if-invalid.err           |   4 +-
 .../alternate-branch-if-invalid.exit          |   1 -
 tests/qapi-schema/alternate-clash.err         |   4 +-
 tests/qapi-schema/alternate-clash.exit        |   1 -
 .../alternate-conflict-bool-string.err        |   4 +-
 .../alternate-conflict-bool-string.exit       |   1 -
 tests/qapi-schema/alternate-conflict-dict.err |   4 +-
 .../qapi-schema/alternate-conflict-dict.exit  |   1 -
 .../alternate-conflict-enum-bool.err          |   4 +-
 .../alternate-conflict-enum-bool.exit         |   1 -
 .../alternate-conflict-enum-int.err           |   4 +-
 .../alternate-conflict-enum-int.exit          |   1 -
 .../alternate-conflict-num-string.err         |   4 +-
 .../alternate-conflict-num-string.exit        |   1 -
 .../qapi-schema/alternate-conflict-string.err |   4 +-
 .../alternate-conflict-string.exit            |   1 -
 tests/qapi-schema/alternate-empty.err         |   4 +-
 tests/qapi-schema/alternate-empty.exit        |   1 -
 tests/qapi-schema/alternate-invalid-dict.err  |   4 +-
 tests/qapi-schema/alternate-invalid-dict.exit |   1 -
 tests/qapi-schema/alternate-nested.err        |   4 +-
 tests/qapi-schema/alternate-nested.exit       |   1 -
 tests/qapi-schema/alternate-unknown.err       |   4 +-
 tests/qapi-schema/alternate-unknown.exit      |   1 -
 tests/qapi-schema/args-alternate.err          |   4 +-
 tests/qapi-schema/args-alternate.exit         |   1 -
 tests/qapi-schema/args-any.err                |   4 +-
 tests/qapi-schema/args-any.exit               |   1 -
 tests/qapi-schema/args-array-empty.err        |   4 +-
 tests/qapi-schema/args-array-empty.exit       |   1 -
 tests/qapi-schema/args-array-unknown.err      |   4 +-
 tests/qapi-schema/args-array-unknown.exit     |   1 -
 tests/qapi-schema/args-bad-boxed.err          |   4 +-
 tests/qapi-schema/args-bad-boxed.exit         |   1 -
 tests/qapi-schema/args-boxed-anon.err         |   4 +-
 tests/qapi-schema/args-boxed-anon.exit        |   1 -
 tests/qapi-schema/args-boxed-string.err       |   4 +-
 tests/qapi-schema/args-boxed-string.exit      |   1 -
 tests/qapi-schema/args-int.err                |   4 +-
 tests/qapi-schema/args-int.exit               |   1 -
 tests/qapi-schema/args-invalid.err            |   4 +-
 tests/qapi-schema/args-invalid.exit           |   1 -
 tests/qapi-schema/args-member-array-bad.err   |   4 +-
 tests/qapi-schema/args-member-array-bad.exit  |   1 -
 tests/qapi-schema/args-member-case.err        |   4 +-
 tests/qapi-schema/args-member-case.exit       |   1 -
 tests/qapi-schema/args-member-unknown.err     |   4 +-
 tests/qapi-schema/args-member-unknown.exit    |   1 -
 tests/qapi-schema/args-name-clash.err         |   4 +-
 tests/qapi-schema/args-name-clash.exit        |   1 -
 tests/qapi-schema/args-union.err              |   4 +-
 tests/qapi-schema/args-union.exit             |   1 -
 tests/qapi-schema/args-unknown.err            |   4 +-
 tests/qapi-schema/args-unknown.exit           |   1 -
 tests/qapi-schema/bad-base.err                |   4 +-
 tests/qapi-schema/bad-base.exit               |   1 -
 tests/qapi-schema/bad-data.err                |   4 +-
 tests/qapi-schema/bad-data.exit               |   1 -
 tests/qapi-schema/bad-ident.err               |   4 +-
 tests/qapi-schema/bad-ident.exit              |   1 -
 tests/qapi-schema/bad-if-empty-list.err       |   4 +-
 tests/qapi-schema/bad-if-empty-list.exit      |   1 -
 tests/qapi-schema/bad-if-empty.err            |   4 +-
 tests/qapi-schema/bad-if-empty.exit           |   1 -
 tests/qapi-schema/bad-if-list.err             |   4 +-
 tests/qapi-schema/bad-if-list.exit            |   1 -
 tests/qapi-schema/bad-if.err                  |   4 +-
 tests/qapi-schema/bad-if.exit                 |   1 -
 tests/qapi-schema/bad-type-bool.err           |   2 +-
 tests/qapi-schema/bad-type-bool.exit          |   1 -
 tests/qapi-schema/bad-type-dict.err           |   2 +-
 tests/qapi-schema/bad-type-dict.exit          |   1 -
 tests/qapi-schema/bad-type-int.err            |   2 +-
 tests/qapi-schema/bad-type-int.exit           |   1 -
 tests/qapi-schema/base-cycle-direct.err       |   4 +-
 tests/qapi-schema/base-cycle-direct.exit      |   1 -
 tests/qapi-schema/base-cycle-indirect.err     |   4 +-
 tests/qapi-schema/base-cycle-indirect.exit    |   1 -
 tests/qapi-schema/command-int.err             |   4 +-
 tests/qapi-schema/command-int.exit            |   1 -
 tests/qapi-schema/comments.exit               |   1 -
 .../qapi-schema/doc-bad-alternate-member.err  |   2 +-
 .../qapi-schema/doc-bad-alternate-member.exit |   1 -
 tests/qapi-schema/doc-bad-command-arg.err     |   2 +-
 tests/qapi-schema/doc-bad-command-arg.exit    |   1 -
 tests/qapi-schema/doc-bad-section.exit        |   1 -
 tests/qapi-schema/doc-bad-symbol.err          |   4 +-
 tests/qapi-schema/doc-bad-symbol.exit         |   1 -
 tests/qapi-schema/doc-bad-union-member.err    |   2 +-
 tests/qapi-schema/doc-bad-union-member.exit   |   1 -
 tests/qapi-schema/doc-before-include.err      |   2 +-
 tests/qapi-schema/doc-before-include.exit     |   1 -
 tests/qapi-schema/doc-before-pragma.err       |   2 +-
 tests/qapi-schema/doc-before-pragma.exit      |   1 -
 tests/qapi-schema/doc-duplicated-arg.err      |   2 +-
 tests/qapi-schema/doc-duplicated-arg.exit     |   1 -
 tests/qapi-schema/doc-duplicated-return.err   |   2 +-
 tests/qapi-schema/doc-duplicated-return.exit  |   1 -
 tests/qapi-schema/doc-duplicated-since.err    |   2 +-
 tests/qapi-schema/doc-duplicated-since.exit   |   1 -
 tests/qapi-schema/doc-empty-arg.err           |   2 +-
 tests/qapi-schema/doc-empty-arg.exit          |   1 -
 tests/qapi-schema/doc-empty-section.err       |   2 +-
 tests/qapi-schema/doc-empty-section.exit      |   1 -
 tests/qapi-schema/doc-empty-symbol.err        |   2 +-
 tests/qapi-schema/doc-empty-symbol.exit       |   1 -
 tests/qapi-schema/doc-good.exit               |   1 -
 tests/qapi-schema/doc-interleaved-section.err |   2 +-
 .../qapi-schema/doc-interleaved-section.exit  |   1 -
 tests/qapi-schema/doc-invalid-end.err         |   2 +-
 tests/qapi-schema/doc-invalid-end.exit        |   1 -
 tests/qapi-schema/doc-invalid-end2.err        |   2 +-
 tests/qapi-schema/doc-invalid-end2.exit       |   1 -
 tests/qapi-schema/doc-invalid-return.err      |   2 +-
 tests/qapi-schema/doc-invalid-return.exit     |   1 -
 tests/qapi-schema/doc-invalid-section.err     |   2 +-
 tests/qapi-schema/doc-invalid-section.exit    |   1 -
 tests/qapi-schema/doc-invalid-start.err       |   2 +-
 tests/qapi-schema/doc-invalid-start.exit      |   1 -
 tests/qapi-schema/doc-missing-colon.err       |   2 +-
 tests/qapi-schema/doc-missing-colon.exit      |   1 -
 tests/qapi-schema/doc-missing-expr.err        |   2 +-
 tests/qapi-schema/doc-missing-expr.exit       |   1 -
 tests/qapi-schema/doc-missing-space.err       |   2 +-
 tests/qapi-schema/doc-missing-space.exit      |   1 -
 tests/qapi-schema/doc-missing.err             |   4 +-
 tests/qapi-schema/doc-missing.exit            |   1 -
 tests/qapi-schema/doc-no-symbol.err           |   2 +-
 tests/qapi-schema/doc-no-symbol.exit          |   1 -
 tests/qapi-schema/double-type.err             |   4 +-
 tests/qapi-schema/double-type.exit            |   1 -
 tests/qapi-schema/duplicate-key.err           |   2 +-
 tests/qapi-schema/duplicate-key.exit          |   1 -
 tests/qapi-schema/empty.exit                  |   1 -
 tests/qapi-schema/enum-bad-member.err         |   4 +-
 tests/qapi-schema/enum-bad-member.exit        |   1 -
 tests/qapi-schema/enum-bad-name.err           |   4 +-
 tests/qapi-schema/enum-bad-name.exit          |   1 -
 tests/qapi-schema/enum-bad-prefix.err         |   4 +-
 tests/qapi-schema/enum-bad-prefix.exit        |   1 -
 tests/qapi-schema/enum-clash-member.err       |   4 +-
 tests/qapi-schema/enum-clash-member.exit      |   1 -
 .../qapi-schema/enum-dict-member-unknown.err  |   4 +-
 .../qapi-schema/enum-dict-member-unknown.exit |   1 -
 tests/qapi-schema/enum-if-invalid.err         |   4 +-
 tests/qapi-schema/enum-if-invalid.exit        |   1 -
 tests/qapi-schema/enum-int-member.err         |   2 +-
 tests/qapi-schema/enum-int-member.exit        |   1 -
 tests/qapi-schema/enum-member-case.err        |   4 +-
 tests/qapi-schema/enum-member-case.exit       |   1 -
 tests/qapi-schema/enum-missing-data.err       |   4 +-
 tests/qapi-schema/enum-missing-data.exit      |   1 -
 tests/qapi-schema/enum-wrong-data.err         |   4 +-
 tests/qapi-schema/enum-wrong-data.exit        |   1 -
 tests/qapi-schema/escape-outside-string.err   |   2 +-
 tests/qapi-schema/event-boxed-empty.err       |   4 +-
 tests/qapi-schema/event-boxed-empty.exit      |   1 -
 tests/qapi-schema/event-case.exit             |   1 -
 .../qapi-schema/event-member-invalid-dict.err |   4 +-
 .../event-member-invalid-dict.exit            |   1 -
 tests/qapi-schema/event-nest-struct.err       |   4 +-
 tests/qapi-schema/event-nest-struct.exit      |   1 -
 tests/qapi-schema/features-bad-type.err       |   4 +-
 tests/qapi-schema/features-bad-type.exit      |   1 -
 tests/qapi-schema/features-duplicate-name.err |   4 +-
 .../qapi-schema/features-duplicate-name.exit  |   1 -
 tests/qapi-schema/features-if-invalid.err     |   4 +-
 tests/qapi-schema/features-if-invalid.exit    |   1 -
 tests/qapi-schema/features-missing-name.err   |   4 +-
 tests/qapi-schema/features-missing-name.exit  |   1 -
 tests/qapi-schema/features-name-bad-type.err  |   4 +-
 tests/qapi-schema/features-name-bad-type.exit |   1 -
 tests/qapi-schema/features-no-list.err        |   4 +-
 tests/qapi-schema/features-no-list.exit       |   1 -
 tests/qapi-schema/features-unknown-key.err    |   4 +-
 tests/qapi-schema/features-unknown-key.exit   |   1 -
 tests/qapi-schema/flat-union-array-branch.err |   4 +-
 .../qapi-schema/flat-union-array-branch.exit  |   1 -
 tests/qapi-schema/flat-union-bad-base.err     |   4 +-
 tests/qapi-schema/flat-union-bad-base.exit    |   1 -
 .../flat-union-bad-discriminator.err          |   4 +-
 .../flat-union-bad-discriminator.exit         |   1 -
 tests/qapi-schema/flat-union-base-any.err     |   4 +-
 tests/qapi-schema/flat-union-base-any.exit    |   1 -
 tests/qapi-schema/flat-union-base-union.err   |   4 +-
 tests/qapi-schema/flat-union-base-union.exit  |   1 -
 tests/qapi-schema/flat-union-clash-member.err |   4 +-
 .../qapi-schema/flat-union-clash-member.exit  |   1 -
 .../flat-union-discriminator-bad-name.err     |   4 +-
 .../flat-union-discriminator-bad-name.exit    |   1 -
 tests/qapi-schema/flat-union-empty.err        |   4 +-
 tests/qapi-schema/flat-union-empty.exit       |   1 -
 .../flat-union-inline-invalid-dict.err        |   4 +-
 .../flat-union-inline-invalid-dict.exit       |   1 -
 tests/qapi-schema/flat-union-inline.err       |   4 +-
 tests/qapi-schema/flat-union-inline.exit      |   1 -
 tests/qapi-schema/flat-union-int-branch.err   |   4 +-
 tests/qapi-schema/flat-union-int-branch.exit  |   1 -
 .../flat-union-invalid-branch-key.err         |   4 +-
 .../flat-union-invalid-branch-key.exit        |   1 -
 .../flat-union-invalid-discriminator.err      |   4 +-
 .../flat-union-invalid-discriminator.exit     |   1 -
 .../flat-union-invalid-if-discriminator.err   |   4 +-
 .../flat-union-invalid-if-discriminator.exit  |   1 -
 tests/qapi-schema/flat-union-no-base.err      |   4 +-
 tests/qapi-schema/flat-union-no-base.exit     |   1 -
 .../flat-union-optional-discriminator.err     |   4 +-
 .../flat-union-optional-discriminator.exit    |   1 -
 .../flat-union-string-discriminator.err       |   4 +-
 .../flat-union-string-discriminator.exit      |   1 -
 tests/qapi-schema/funny-char.err              |   2 +-
 tests/qapi-schema/funny-char.exit             |   1 -
 tests/qapi-schema/funny-word.err              |   2 +-
 tests/qapi-schema/funny-word.exit             |   1 -
 tests/qapi-schema/ident-with-escape.err       |   2 +-
 tests/qapi-schema/ident-with-escape.exit      |   1 -
 tests/qapi-schema/include-before-err.err      |   2 +-
 tests/qapi-schema/include-before-err.exit     |   1 -
 tests/qapi-schema/include-cycle.err           |   6 +-
 tests/qapi-schema/include-cycle.exit          |   1 -
 tests/qapi-schema/include-extra-junk.err      |   2 +-
 tests/qapi-schema/include-extra-junk.exit     |   1 -
 tests/qapi-schema/include-nested-err.err      |   4 +-
 tests/qapi-schema/include-nested-err.exit     |   1 -
 tests/qapi-schema/include-no-file.err         |   2 +-
 tests/qapi-schema/include-no-file.exit        |   1 -
 tests/qapi-schema/include-non-file.err        |   2 +-
 tests/qapi-schema/include-non-file.exit       |   1 -
 tests/qapi-schema/include-repetition.exit     |   1 -
 tests/qapi-schema/include-self-cycle.err      |   2 +-
 tests/qapi-schema/include-self-cycle.exit     |   1 -
 tests/qapi-schema/include-simple.exit         |   1 -
 tests/qapi-schema/indented-expr.exit          |   1 -
 tests/qapi-schema/leading-comma-list.err      |   2 +-
 tests/qapi-schema/leading-comma-list.exit     |   1 -
 tests/qapi-schema/leading-comma-object.err    |   2 +-
 tests/qapi-schema/leading-comma-object.exit   |   1 -
 tests/qapi-schema/missing-colon.err           |   2 +-
 tests/qapi-schema/missing-colon.exit          |   1 -
 tests/qapi-schema/missing-comma-list.err      |   2 +-
 tests/qapi-schema/missing-comma-list.exit     |   1 -
 tests/qapi-schema/missing-comma-object.err    |   2 +-
 tests/qapi-schema/missing-comma-object.exit   |   1 -
 tests/qapi-schema/missing-type.err            |   2 +-
 tests/qapi-schema/missing-type.exit           |   1 -
 .../nested-struct-data-invalid-dict.err       |   4 +-
 .../nested-struct-data-invalid-dict.exit      |   1 -
 tests/qapi-schema/nested-struct-data.err      |   4 +-
 tests/qapi-schema/nested-struct-data.exit     |   1 -
 tests/qapi-schema/non-objects.err             |   2 +-
 tests/qapi-schema/non-objects.exit            |   1 -
 tests/qapi-schema/oob-test.err                |   4 +-
 tests/qapi-schema/oob-test.exit               |   1 -
 .../qapi-schema/pragma-doc-required-crap.err  |   2 +-
 .../qapi-schema/pragma-doc-required-crap.exit |   1 -
 tests/qapi-schema/pragma-extra-junk.err       |   2 +-
 tests/qapi-schema/pragma-extra-junk.exit      |   1 -
 .../pragma-name-case-whitelist-crap.err       |   2 +-
 .../pragma-name-case-whitelist-crap.exit      |   1 -
 tests/qapi-schema/pragma-non-dict.err         |   2 +-
 tests/qapi-schema/pragma-non-dict.exit        |   1 -
 .../pragma-returns-whitelist-crap.err         |   2 +-
 .../pragma-returns-whitelist-crap.exit        |   1 -
 tests/qapi-schema/pragma-unknown.err          |   2 +-
 tests/qapi-schema/pragma-unknown.exit         |   1 -
 tests/qapi-schema/qapi-schema-test.exit       |   1 -
 tests/qapi-schema/quoted-structural-chars.err |   2 +-
 .../qapi-schema/quoted-structural-chars.exit  |   1 -
 tests/qapi-schema/redefined-builtin.err       |   4 +-
 tests/qapi-schema/redefined-builtin.exit      |   1 -
 tests/qapi-schema/redefined-command.err       |   8 +-
 tests/qapi-schema/redefined-command.exit      |   1 -
 tests/qapi-schema/redefined-event.err         |   8 +-
 tests/qapi-schema/redefined-event.exit        |   1 -
 tests/qapi-schema/redefined-type.err          |   8 +-
 tests/qapi-schema/redefined-type.exit         |   1 -
 tests/qapi-schema/reserved-command-q.err      |   4 +-
 tests/qapi-schema/reserved-command-q.exit     |   1 -
 tests/qapi-schema/reserved-enum-q.err         |   4 +-
 tests/qapi-schema/reserved-enum-q.exit        |   1 -
 tests/qapi-schema/reserved-member-has.err     |   4 +-
 tests/qapi-schema/reserved-member-has.exit    |   1 -
 tests/qapi-schema/reserved-member-q.err       |   4 +-
 tests/qapi-schema/reserved-member-q.exit      |   1 -
 tests/qapi-schema/reserved-member-u.err       |   4 +-
 tests/qapi-schema/reserved-member-u.exit      |   1 -
 .../reserved-member-underscore.err            |   4 +-
 .../reserved-member-underscore.exit           |   1 -
 tests/qapi-schema/reserved-type-kind.err      |   4 +-
 tests/qapi-schema/reserved-type-kind.exit     |   1 -
 tests/qapi-schema/reserved-type-list.err      |   4 +-
 tests/qapi-schema/reserved-type-list.exit     |   1 -
 tests/qapi-schema/returns-alternate.err       |   4 +-
 tests/qapi-schema/returns-alternate.exit      |   1 -
 tests/qapi-schema/returns-array-bad.err       |   4 +-
 tests/qapi-schema/returns-array-bad.exit      |   1 -
 tests/qapi-schema/returns-dict.err            |   4 +-
 tests/qapi-schema/returns-dict.exit           |   1 -
 tests/qapi-schema/returns-unknown.err         |   4 +-
 tests/qapi-schema/returns-unknown.exit        |   1 -
 tests/qapi-schema/returns-whitelist.err       |   4 +-
 tests/qapi-schema/returns-whitelist.exit      |   1 -
 tests/qapi-schema/string-code-point-127.err   |   2 +-
 tests/qapi-schema/string-code-point-127.exit  |   1 -
 tests/qapi-schema/string-code-point-31.err    |   2 +-
 tests/qapi-schema/string-code-point-31.exit   |   1 -
 tests/qapi-schema/struct-base-clash-deep.err  |   4 +-
 tests/qapi-schema/struct-base-clash-deep.exit |   1 -
 tests/qapi-schema/struct-base-clash.err       |   4 +-
 tests/qapi-schema/struct-base-clash.exit      |   1 -
 tests/qapi-schema/struct-data-invalid.err     |   4 +-
 tests/qapi-schema/struct-data-invalid.exit    |   1 -
 .../qapi-schema/struct-member-if-invalid.err  |   4 +-
 .../qapi-schema/struct-member-if-invalid.exit |   1 -
 .../struct-member-invalid-dict.err            |   4 +-
 .../struct-member-invalid-dict.exit           |   1 -
 tests/qapi-schema/struct-member-invalid.err   |   4 +-
 tests/qapi-schema/struct-member-invalid.exit  |   1 -
 tests/qapi-schema/test-qapi.py                | 119 +++++++++++++++---
 tests/qapi-schema/trailing-comma-list.err     |   2 +-
 tests/qapi-schema/trailing-comma-list.exit    |   1 -
 tests/qapi-schema/trailing-comma-object.err   |   2 +-
 tests/qapi-schema/trailing-comma-object.exit  |   1 -
 tests/qapi-schema/type-bypass-bad-gen.err     |   4 +-
 tests/qapi-schema/type-bypass-bad-gen.exit    |   1 -
 tests/qapi-schema/unclosed-list.err           |   2 +-
 tests/qapi-schema/unclosed-list.exit          |   1 -
 tests/qapi-schema/unclosed-object.err         |   2 +-
 tests/qapi-schema/unclosed-object.exit        |   1 -
 tests/qapi-schema/unclosed-string.err         |   2 +-
 tests/qapi-schema/unclosed-string.exit        |   1 -
 tests/qapi-schema/union-base-empty.err        |   4 +-
 tests/qapi-schema/union-base-empty.exit       |   1 -
 .../union-base-no-discriminator.err           |   4 +-
 .../union-base-no-discriminator.exit          |   1 -
 tests/qapi-schema/union-branch-case.err       |   4 +-
 tests/qapi-schema/union-branch-case.exit      |   1 -
 tests/qapi-schema/union-branch-if-invalid.err |   4 +-
 .../qapi-schema/union-branch-if-invalid.exit  |   1 -
 .../qapi-schema/union-branch-invalid-dict.err |   4 +-
 .../union-branch-invalid-dict.exit            |   1 -
 tests/qapi-schema/union-clash-branches.err    |   4 +-
 tests/qapi-schema/union-clash-branches.exit   |   1 -
 tests/qapi-schema/union-empty.err             |   4 +-
 tests/qapi-schema/union-empty.exit            |   1 -
 tests/qapi-schema/union-invalid-base.err      |   4 +-
 tests/qapi-schema/union-invalid-base.exit     |   1 -
 tests/qapi-schema/union-optional-branch.err   |   4 +-
 tests/qapi-schema/union-optional-branch.exit  |   1 -
 tests/qapi-schema/union-unknown.err           |   4 +-
 tests/qapi-schema/union-unknown.exit          |   1 -
 tests/qapi-schema/unknown-escape.err          |   2 +-
 tests/qapi-schema/unknown-escape.exit         |   1 -
 tests/qapi-schema/unknown-expr-key.err        |   4 +-
 tests/qapi-schema/unknown-expr-key.exit       |   1 -
 364 files changed, 413 insertions(+), 515 deletions(-)
 delete mode 100644 tests/qapi-schema/allow-preconfig-test.exit
 delete mode 100644 tests/qapi-schema/alternate-any.exit
 delete mode 100644 tests/qapi-schema/alternate-array.exit
 delete mode 100644 tests/qapi-schema/alternate-base.exit
 delete mode 100644 tests/qapi-schema/alternate-branch-if-invalid.exit
 delete mode 100644 tests/qapi-schema/alternate-clash.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-bool-string.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-dict.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-enum-bool.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-enum-int.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-num-string.exit
 delete mode 100644 tests/qapi-schema/alternate-conflict-string.exit
 delete mode 100644 tests/qapi-schema/alternate-empty.exit
 delete mode 100644 tests/qapi-schema/alternate-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/alternate-nested.exit
 delete mode 100644 tests/qapi-schema/alternate-unknown.exit
 delete mode 100644 tests/qapi-schema/args-alternate.exit
 delete mode 100644 tests/qapi-schema/args-any.exit
 delete mode 100644 tests/qapi-schema/args-array-empty.exit
 delete mode 100644 tests/qapi-schema/args-array-unknown.exit
 delete mode 100644 tests/qapi-schema/args-bad-boxed.exit
 delete mode 100644 tests/qapi-schema/args-boxed-anon.exit
 delete mode 100644 tests/qapi-schema/args-boxed-string.exit
 delete mode 100644 tests/qapi-schema/args-int.exit
 delete mode 100644 tests/qapi-schema/args-invalid.exit
 delete mode 100644 tests/qapi-schema/args-member-array-bad.exit
 delete mode 100644 tests/qapi-schema/args-member-case.exit
 delete mode 100644 tests/qapi-schema/args-member-unknown.exit
 delete mode 100644 tests/qapi-schema/args-name-clash.exit
 delete mode 100644 tests/qapi-schema/args-union.exit
 delete mode 100644 tests/qapi-schema/args-unknown.exit
 delete mode 100644 tests/qapi-schema/bad-base.exit
 delete mode 100644 tests/qapi-schema/bad-data.exit
 delete mode 100644 tests/qapi-schema/bad-ident.exit
 delete mode 100644 tests/qapi-schema/bad-if-empty-list.exit
 delete mode 100644 tests/qapi-schema/bad-if-empty.exit
 delete mode 100644 tests/qapi-schema/bad-if-list.exit
 delete mode 100644 tests/qapi-schema/bad-if.exit
 delete mode 100644 tests/qapi-schema/bad-type-bool.exit
 delete mode 100644 tests/qapi-schema/bad-type-dict.exit
 delete mode 100644 tests/qapi-schema/bad-type-int.exit
 delete mode 100644 tests/qapi-schema/base-cycle-direct.exit
 delete mode 100644 tests/qapi-schema/base-cycle-indirect.exit
 delete mode 100644 tests/qapi-schema/command-int.exit
 delete mode 100644 tests/qapi-schema/comments.exit
 delete mode 100644 tests/qapi-schema/doc-bad-alternate-member.exit
 delete mode 100644 tests/qapi-schema/doc-bad-command-arg.exit
 delete mode 100644 tests/qapi-schema/doc-bad-section.exit
 delete mode 100644 tests/qapi-schema/doc-bad-symbol.exit
 delete mode 100644 tests/qapi-schema/doc-bad-union-member.exit
 delete mode 100644 tests/qapi-schema/doc-before-include.exit
 delete mode 100644 tests/qapi-schema/doc-before-pragma.exit
 delete mode 100644 tests/qapi-schema/doc-duplicated-arg.exit
 delete mode 100644 tests/qapi-schema/doc-duplicated-return.exit
 delete mode 100644 tests/qapi-schema/doc-duplicated-since.exit
 delete mode 100644 tests/qapi-schema/doc-empty-arg.exit
 delete mode 100644 tests/qapi-schema/doc-empty-section.exit
 delete mode 100644 tests/qapi-schema/doc-empty-symbol.exit
 delete mode 100644 tests/qapi-schema/doc-good.exit
 delete mode 100644 tests/qapi-schema/doc-interleaved-section.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-end.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-end2.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-return.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-section.exit
 delete mode 100644 tests/qapi-schema/doc-invalid-start.exit
 delete mode 100644 tests/qapi-schema/doc-missing-colon.exit
 delete mode 100644 tests/qapi-schema/doc-missing-expr.exit
 delete mode 100644 tests/qapi-schema/doc-missing-space.exit
 delete mode 100644 tests/qapi-schema/doc-missing.exit
 delete mode 100644 tests/qapi-schema/doc-no-symbol.exit
 delete mode 100644 tests/qapi-schema/double-type.exit
 delete mode 100644 tests/qapi-schema/duplicate-key.exit
 delete mode 100644 tests/qapi-schema/empty.exit
 delete mode 100644 tests/qapi-schema/enum-bad-member.exit
 delete mode 100644 tests/qapi-schema/enum-bad-name.exit
 delete mode 100644 tests/qapi-schema/enum-bad-prefix.exit
 delete mode 100644 tests/qapi-schema/enum-clash-member.exit
 delete mode 100644 tests/qapi-schema/enum-dict-member-unknown.exit
 delete mode 100644 tests/qapi-schema/enum-if-invalid.exit
 delete mode 100644 tests/qapi-schema/enum-int-member.exit
 delete mode 100644 tests/qapi-schema/enum-member-case.exit
 delete mode 100644 tests/qapi-schema/enum-missing-data.exit
 delete mode 100644 tests/qapi-schema/enum-wrong-data.exit
 delete mode 100644 tests/qapi-schema/event-boxed-empty.exit
 delete mode 100644 tests/qapi-schema/event-case.exit
 delete mode 100644 tests/qapi-schema/event-member-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/event-nest-struct.exit
 delete mode 100644 tests/qapi-schema/features-bad-type.exit
 delete mode 100644 tests/qapi-schema/features-duplicate-name.exit
 delete mode 100644 tests/qapi-schema/features-if-invalid.exit
 delete mode 100644 tests/qapi-schema/features-missing-name.exit
 delete mode 100644 tests/qapi-schema/features-name-bad-type.exit
 delete mode 100644 tests/qapi-schema/features-no-list.exit
 delete mode 100644 tests/qapi-schema/features-unknown-key.exit
 delete mode 100644 tests/qapi-schema/flat-union-array-branch.exit
 delete mode 100644 tests/qapi-schema/flat-union-bad-base.exit
 delete mode 100644 tests/qapi-schema/flat-union-bad-discriminator.exit
 delete mode 100644 tests/qapi-schema/flat-union-base-any.exit
 delete mode 100644 tests/qapi-schema/flat-union-base-union.exit
 delete mode 100644 tests/qapi-schema/flat-union-clash-member.exit
 delete mode 100644 tests/qapi-schema/flat-union-discriminator-bad-name.exit
 delete mode 100644 tests/qapi-schema/flat-union-empty.exit
 delete mode 100644 tests/qapi-schema/flat-union-inline-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/flat-union-inline.exit
 delete mode 100644 tests/qapi-schema/flat-union-int-branch.exit
 delete mode 100644 tests/qapi-schema/flat-union-invalid-branch-key.exit
 delete mode 100644 tests/qapi-schema/flat-union-invalid-discriminator.exit
 delete mode 100644 tests/qapi-schema/flat-union-invalid-if-discriminator.exit
 delete mode 100644 tests/qapi-schema/flat-union-no-base.exit
 delete mode 100644 tests/qapi-schema/flat-union-optional-discriminator.exit
 delete mode 100644 tests/qapi-schema/flat-union-string-discriminator.exit
 delete mode 100644 tests/qapi-schema/funny-char.exit
 delete mode 100644 tests/qapi-schema/funny-word.exit
 delete mode 100644 tests/qapi-schema/ident-with-escape.exit
 delete mode 100644 tests/qapi-schema/include-before-err.exit
 delete mode 100644 tests/qapi-schema/include-cycle.exit
 delete mode 100644 tests/qapi-schema/include-extra-junk.exit
 delete mode 100644 tests/qapi-schema/include-nested-err.exit
 delete mode 100644 tests/qapi-schema/include-no-file.exit
 delete mode 100644 tests/qapi-schema/include-non-file.exit
 delete mode 100644 tests/qapi-schema/include-repetition.exit
 delete mode 100644 tests/qapi-schema/include-self-cycle.exit
 delete mode 100644 tests/qapi-schema/include-simple.exit
 delete mode 100644 tests/qapi-schema/indented-expr.exit
 delete mode 100644 tests/qapi-schema/leading-comma-list.exit
 delete mode 100644 tests/qapi-schema/leading-comma-object.exit
 delete mode 100644 tests/qapi-schema/missing-colon.exit
 delete mode 100644 tests/qapi-schema/missing-comma-list.exit
 delete mode 100644 tests/qapi-schema/missing-comma-object.exit
 delete mode 100644 tests/qapi-schema/missing-type.exit
 delete mode 100644 tests/qapi-schema/nested-struct-data-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/nested-struct-data.exit
 delete mode 100644 tests/qapi-schema/non-objects.exit
 delete mode 100644 tests/qapi-schema/oob-test.exit
 delete mode 100644 tests/qapi-schema/pragma-doc-required-crap.exit
 delete mode 100644 tests/qapi-schema/pragma-extra-junk.exit
 delete mode 100644 tests/qapi-schema/pragma-name-case-whitelist-crap.exit
 delete mode 100644 tests/qapi-schema/pragma-non-dict.exit
 delete mode 100644 tests/qapi-schema/pragma-returns-whitelist-crap.exit
 delete mode 100644 tests/qapi-schema/pragma-unknown.exit
 delete mode 100644 tests/qapi-schema/qapi-schema-test.exit
 delete mode 100644 tests/qapi-schema/quoted-structural-chars.exit
 delete mode 100644 tests/qapi-schema/redefined-builtin.exit
 delete mode 100644 tests/qapi-schema/redefined-command.exit
 delete mode 100644 tests/qapi-schema/redefined-event.exit
 delete mode 100644 tests/qapi-schema/redefined-type.exit
 delete mode 100644 tests/qapi-schema/reserved-command-q.exit
 delete mode 100644 tests/qapi-schema/reserved-enum-q.exit
 delete mode 100644 tests/qapi-schema/reserved-member-has.exit
 delete mode 100644 tests/qapi-schema/reserved-member-q.exit
 delete mode 100644 tests/qapi-schema/reserved-member-u.exit
 delete mode 100644 tests/qapi-schema/reserved-member-underscore.exit
 delete mode 100644 tests/qapi-schema/reserved-type-kind.exit
 delete mode 100644 tests/qapi-schema/reserved-type-list.exit
 delete mode 100644 tests/qapi-schema/returns-alternate.exit
 delete mode 100644 tests/qapi-schema/returns-array-bad.exit
 delete mode 100644 tests/qapi-schema/returns-dict.exit
 delete mode 100644 tests/qapi-schema/returns-unknown.exit
 delete mode 100644 tests/qapi-schema/returns-whitelist.exit
 delete mode 100644 tests/qapi-schema/string-code-point-127.exit
 delete mode 100644 tests/qapi-schema/string-code-point-31.exit
 delete mode 100644 tests/qapi-schema/struct-base-clash-deep.exit
 delete mode 100644 tests/qapi-schema/struct-base-clash.exit
 delete mode 100644 tests/qapi-schema/struct-data-invalid.exit
 delete mode 100644 tests/qapi-schema/struct-member-if-invalid.exit
 delete mode 100644 tests/qapi-schema/struct-member-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/struct-member-invalid.exit
 mode change 100644 => 100755 tests/qapi-schema/test-qapi.py
 delete mode 100644 tests/qapi-schema/trailing-comma-list.exit
 delete mode 100644 tests/qapi-schema/trailing-comma-object.exit
 delete mode 100644 tests/qapi-schema/type-bypass-bad-gen.exit
 delete mode 100644 tests/qapi-schema/unclosed-list.exit
 delete mode 100644 tests/qapi-schema/unclosed-object.exit
 delete mode 100644 tests/qapi-schema/unclosed-string.exit
 delete mode 100644 tests/qapi-schema/union-base-empty.exit
 delete mode 100644 tests/qapi-schema/union-base-no-discriminator.exit
 delete mode 100644 tests/qapi-schema/union-branch-case.exit
 delete mode 100644 tests/qapi-schema/union-branch-if-invalid.exit
 delete mode 100644 tests/qapi-schema/union-branch-invalid-dict.exit
 delete mode 100644 tests/qapi-schema/union-clash-branches.exit
 delete mode 100644 tests/qapi-schema/union-empty.exit
 delete mode 100644 tests/qapi-schema/union-invalid-base.exit
 delete mode 100644 tests/qapi-schema/union-optional-branch.exit
 delete mode 100644 tests/qapi-schema/union-unknown.exit
 delete mode 100644 tests/qapi-schema/unknown-escape.exit
 delete mode 100644 tests/qapi-schema/unknown-expr-key.exit

diff --git a/tests/Makefile.include b/tests/Makefile.include
index 214fbd941c..1b24b8ba10 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -1102,17 +1102,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
@@ -1170,7 +1164,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-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-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/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/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
old mode 100644
new mode 100755
index b0f770b9bd..42baa702b6
--- 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,7 +12,14 @@
 #
 
 from __future__ import print_function
+import argparse
+import difflib
+import os
 import sys
+if sys.version_info[0] < 3:
+    from cStringIO import StringIO
+else:
+    from io import StringIO
 from qapi.common import QAPIError, QAPISchema, QAPISchemaVisitor
 
 
@@ -87,21 +95,102 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
             print('%sif %s' % (' ' * indent, ifcond))
 
 
-try:
-    schema = QAPISchema(sys.argv[1])
-except QAPIError as err:
-    print(err, file=sys.stderr)
-    exit(1)
+def test_frontend(fname):
+    schema = QAPISchema(fname)
+    schema.visit(QAPISchemaTestVisitor())
 
-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))
 
-for doc in schema.docs:
-    if doc.symbol:
-        print('doc symbol=%s' % doc.symbol)
+
+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
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 5/7] qapi: Move gen_enum(), gen_enum_lookup() back to qapi/types.py
  2019-10-18  7:43 [PATCH v2 0/7] qapi: Cleanups and test speedup Markus Armbruster
                   ` (3 preceding siblings ...)
  2019-10-18  7:43 ` [PATCH v2 4/7] qapi: Speed up frontend tests Markus Armbruster
@ 2019-10-18  7:43 ` Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 6/7] qapi: Split up scripts/qapi/common.py Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 7/7] qapi: Clear scripts/qapi/doc.py executable bits again Markus Armbruster
  6 siblings, 0 replies; 8+ messages in thread
From: Markus Armbruster @ 2019-10-18  7:43 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, marcandre.lureau, mdroth

The next commit will split up qapi/common.py.  gen_enum() needs
QAPISchemaEnumMember, and that's in the way.  Move it to qapi/types.py
along with its buddy gen_enum_lookup().

Permit me a short a digression on history: how did gen_enum() end up
in qapi/common.py?  Commit 21cd70dfc1 "qapi script: add event support"
duplicated qapi-types.py's gen_enum() and gen_enum_lookup() in
qapi-event.py.  Simply importing them would have been cleaner, but
wasn't possible as qapi-types.py was a program, not a module.  Commit
efd2eaa6c2 "qapi: De-duplicate enum code generation" de-duplicated by
moving them to qapi.py, which was a module.

Since then, program qapi-types.py has morphed into module types.py.
It's where gen_enum() and gen_enum_lookup() started, and where they
belong.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi/common.py | 59 ------------------------------------------
 scripts/qapi/events.py |  1 +
 scripts/qapi/types.py  | 59 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 59 deletions(-)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 9d5c05f6a1..306857f0c0 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -2239,65 +2239,6 @@ def _wrap_ifcond(ifcond, before, after):
     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 = ''
diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py
index 7308e8e589..a716a1d27f 100644
--- a/scripts/qapi/events.py
+++ b/scripts/qapi/events.py
@@ -13,6 +13,7 @@ See the COPYING file in the top-level directory.
 """
 
 from qapi.common import *
+from qapi.types import gen_enum, gen_enum_lookup
 
 
 def build_event_send_proto(name, arg_type, boxed):
diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
index 3edd9374aa..711543147d 100644
--- a/scripts/qapi/types.py
+++ b/scripts/qapi/types.py
@@ -21,6 +21,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('''
 
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 6/7] qapi: Split up scripts/qapi/common.py
  2019-10-18  7:43 [PATCH v2 0/7] qapi: Cleanups and test speedup Markus Armbruster
                   ` (4 preceding siblings ...)
  2019-10-18  7:43 ` [PATCH v2 5/7] qapi: Move gen_enum(), gen_enum_lookup() back to qapi/types.py Markus Armbruster
@ 2019-10-18  7:43 ` Markus Armbruster
  2019-10-18  7:43 ` [PATCH v2 7/7] qapi: Clear scripts/qapi/doc.py executable bits again Markus Armbruster
  6 siblings, 0 replies; 8+ messages in thread
From: Markus Armbruster @ 2019-10-18  7:43 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, marcandre.lureau, mdroth

The QAPI code generator clocks in at some 3100 SLOC in 8 source files.
Almost 60% of the code is in qapi/common.py.  Split it into more
focused modules:

* Move QAPISchemaPragma and QAPISourceInfo to qapi/source.py.

* Move QAPIError and its sub-classes to qapi/error.py.

* Move QAPISchemaParser and QAPIDoc to parser.py.  Use the opportunity
  to put QAPISchemaParser first.

* Move check_expr() & friends to qapi/expr.py.  Use the opportunity to
  put the code into a more sensible order.

* Move QAPISchema & friends to qapi/schema.py

* Move QAPIGen and its sub-classes, ifcontext,
  QAPISchemaModularCVisitor, and QAPISchemaModularCVisitor to qapi/gen.py

* Delete camel_case(), it's unused since commit e98859a9b9 "qapi:
  Clean up after recent conversions to QAPISchemaVisitor"

A number of helper functions remain in qapi/common.py.  I considered
moving the code generator helpers to qapi/gen.py, but decided not to.
Perhaps we should rewrite them as methods of QAPIGen some day.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 Makefile                       |   13 +-
 scripts/qapi-gen.py            |   10 +-
 scripts/qapi/commands.py       |    1 +
 scripts/qapi/common.py         | 2321 --------------------------------
 scripts/qapi/doc.py            |    7 +-
 scripts/qapi/error.py          |   42 +
 scripts/qapi/events.py         |    2 +
 scripts/qapi/expr.py           |  377 ++++++
 scripts/qapi/gen.py            |  290 ++++
 scripts/qapi/introspect.py     |    5 +
 scripts/qapi/parser.py         |  569 ++++++++
 scripts/qapi/schema.py         | 1042 ++++++++++++++
 scripts/qapi/source.py         |   67 +
 scripts/qapi/types.py          |    2 +
 scripts/qapi/visit.py          |    2 +
 tests/Makefile.include         |   13 +-
 tests/qapi-schema/test-qapi.py |    6 +-
 17 files changed, 2434 insertions(+), 2335 deletions(-)
 create mode 100644 scripts/qapi/error.py
 create mode 100644 scripts/qapi/expr.py
 create mode 100644 scripts/qapi/gen.py
 create mode 100644 scripts/qapi/parser.py
 create mode 100644 scripts/qapi/schema.py
 create mode 100644 scripts/qapi/source.py

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/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..898516b086 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):
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 306857f0c0..e00dcafce7 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -11,2056 +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
-
-
-#
-# Parsing the schema into expressions
-#
-
-
-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()
-
-
-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=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 '##'")
-
-
-#
-# 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 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 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 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']
-    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 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',
-                        '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 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())
-
-    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
@@ -2223,22 +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 build_params(arg_type, boxed, extra=None):
     ret = ''
     sep = ''
@@ -2258,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 693cc4486b..1c5125249f 100755
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -7,7 +7,8 @@
 
 from __future__ import print_function
 import re
-import qapi.common
+from qapi.gen import QAPIGenDoc, QAPISchemaVisitor
+
 
 MSG_FMT = """
 @deftypefn {type} {{}} {name}
@@ -216,10 +217,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):
diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
new file mode 100644
index 0000000000..1db6634044
--- /dev/null
+++ b/scripts/qapi/error.py
@@ -0,0 +1,42 @@
+#
+# 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 a716a1d27f..10fc509fa9 100644
--- a/scripts/qapi/events.py
+++ b/scripts/qapi/events.py
@@ -13,6 +13,8 @@ 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
 
 
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
new file mode 100644
index 0000000000..da23063f57
--- /dev/null
+++ b/scripts/qapi/expr.py
@@ -0,0 +1,377 @@
+#
+# 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 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']
+    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_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)
+
+
+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',
+                        '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
diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
new file mode 100644
index 0000000000..796c17c38a
--- /dev/null
+++ b/scripts/qapi/gen.py
@@ -0,0 +1,290 @@
+#
+# 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..4f257591de 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):
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
new file mode 100644
index 0000000000..cd7f88ebed
--- /dev/null
+++ b/scripts/qapi/parser.py
@@ -0,0 +1,569 @@
+#
+# 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..38041098bd
--- /dev/null
+++ b/scripts/qapi/schema.py
@@ -0,0 +1,1042 @@
+#
+# 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):
+        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 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())
+
+    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()
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 711543147d..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
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 1b24b8ba10..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
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 42baa702b6..664254618a 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -12,15 +12,19 @@
 #
 
 from __future__ import print_function
+
 import argparse
 import difflib
 import os
 import sys
+
+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
-from qapi.common import QAPIError, QAPISchema, QAPISchemaVisitor
 
 
 class QAPISchemaTestVisitor(QAPISchemaVisitor):
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 7/7] qapi: Clear scripts/qapi/doc.py executable bits again
  2019-10-18  7:43 [PATCH v2 0/7] qapi: Cleanups and test speedup Markus Armbruster
                   ` (5 preceding siblings ...)
  2019-10-18  7:43 ` [PATCH v2 6/7] qapi: Split up scripts/qapi/common.py Markus Armbruster
@ 2019-10-18  7:43 ` Markus Armbruster
  6 siblings, 0 replies; 8+ messages in thread
From: Markus Armbruster @ 2019-10-18  7:43 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, marcandre.lureau, mdroth

Commit fbf09a2fa4 "qapi: add 'ifcond' to visitor methods" brought back
the executable bits.  Fix that.  Drop the #! line for good measure.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi/doc.py | 1 -
 1 file changed, 1 deletion(-)
 mode change 100755 => 100644 scripts/qapi/doc.py

diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
old mode 100755
new mode 100644
index 1c5125249f..dc8919bab7
--- 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+.
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2019-10-18  8:03 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-10-18  7:43 [PATCH v2 0/7] qapi: Cleanups and test speedup Markus Armbruster
2019-10-18  7:43 ` [PATCH v2 1/7] qapi: Don't suppress doc generation without pragma doc-required Markus Armbruster
2019-10-18  7:43 ` [PATCH v2 2/7] qapi: Store pragma state in QAPISourceInfo, not global state Markus Armbruster
2019-10-18  7:43 ` [PATCH v2 3/7] qapi: Eliminate accidental global frontend state Markus Armbruster
2019-10-18  7:43 ` [PATCH v2 4/7] qapi: Speed up frontend tests Markus Armbruster
2019-10-18  7:43 ` [PATCH v2 5/7] qapi: Move gen_enum(), gen_enum_lookup() back to qapi/types.py Markus Armbruster
2019-10-18  7:43 ` [PATCH v2 6/7] qapi: Split up scripts/qapi/common.py Markus Armbruster
2019-10-18  7:43 ` [PATCH v2 7/7] qapi: Clear scripts/qapi/doc.py executable bits again Markus Armbruster

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.