summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Maydell2019-10-29 21:06:08 +0100
committerPeter Maydell2019-10-29 21:06:08 +0100
commit16884391c750d0c5e863f55ad7aaaa146fc5181e (patch)
tree7909241a593eea7f9cafaea658e9b101a7682626
parentMerge remote-tracking branch 'remotes/jasowang/tags/net-pull-request' into st... (diff)
parentqapi: Check feature documentation against the schema (diff)
downloadqemu-16884391c750d0c5e863f55ad7aaaa146fc5181e.tar.gz
qemu-16884391c750d0c5e863f55ad7aaaa146fc5181e.tar.xz
qemu-16884391c750d0c5e863f55ad7aaaa146fc5181e.zip
Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2019-10-29' into staging
QAPI patches for 2019-10-29 # gpg: Signature made Tue 29 Oct 2019 06:40:56 GMT # gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653 # gpg: issuer "armbru@redhat.com" # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full] # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full] # Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653 * remotes/armbru/tags/pull-qapi-2019-10-29: qapi: Check feature documentation against the schema qapi: Polish reporting of bogus member documentation qapi: Lift features into QAPISchemaEntity qapi: Fold normalize_enum() into check_enum() qapi: Fold normalize_features() into check_features() qapi: Fold normalize_if() into check_if() qapi: Eliminate .check_doc() overrides qapi: Simplify ._make_implicit_object_type() qapi: Fix doc comment checking for commands and events qapi: Clean up doc comment checking for implicit union base qapi: Fix enum doc comment checking qapi: Split .connect_doc(), .check_doc() off .check() qapi: De-duplicate entity documentation generation code qapi: Implement boxed event argument documentation qemu-doc: Belatedly document QMP command deprecation tests/qapi-schema: Fix feature documentation testing tests/qapi-schema: Cover alternate documentation comments tests/qapi-schema: Demonstrate command and event doc comment bugs tests/qapi-schema: Demonstrate feature and enum doc comment bugs Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--qapi/net.json2
-rw-r--r--qemu-deprecated.texi12
-rw-r--r--scripts/qapi/doc.py75
-rw-r--r--scripts/qapi/expr.py32
-rw-r--r--scripts/qapi/parser.py29
-rw-r--r--scripts/qapi/schema.py97
-rw-r--r--tests/Makefile.include5
-rw-r--r--tests/qapi-schema/doc-bad-alternate-member.err2
-rw-r--r--tests/qapi-schema/doc-bad-boxed-command-arg.err1
-rw-r--r--tests/qapi-schema/doc-bad-boxed-command-arg.json14
-rw-r--r--tests/qapi-schema/doc-bad-boxed-command-arg.out0
-rw-r--r--tests/qapi-schema/doc-bad-command-arg.err2
-rw-r--r--tests/qapi-schema/doc-bad-enum-member.err1
-rw-r--r--tests/qapi-schema/doc-bad-enum-member.json8
-rw-r--r--tests/qapi-schema/doc-bad-enum-member.out0
-rw-r--r--tests/qapi-schema/doc-bad-event-arg.err1
-rw-r--r--tests/qapi-schema/doc-bad-event-arg.json7
-rw-r--r--tests/qapi-schema/doc-bad-event-arg.out0
-rw-r--r--tests/qapi-schema/doc-bad-feature.err1
-rw-r--r--tests/qapi-schema/doc-bad-feature.json9
-rw-r--r--tests/qapi-schema/doc-bad-feature.out0
-rw-r--r--tests/qapi-schema/doc-bad-union-member.err2
-rw-r--r--tests/qapi-schema/doc-good.json14
-rw-r--r--tests/qapi-schema/doc-good.out27
-rw-r--r--tests/qapi-schema/doc-good.texi27
-rw-r--r--tests/qapi-schema/doc-undoc-feature.err2
-rw-r--r--tests/qapi-schema/doc-undoc-feature.json9
-rw-r--r--tests/qapi-schema/doc-undoc-feature.out0
-rwxr-xr-xtests/qapi-schema/test-qapi.py2
29 files changed, 270 insertions, 111 deletions
diff --git a/qapi/net.json b/qapi/net.json
index 728990f4fb..4c96137811 100644
--- a/qapi/net.json
+++ b/qapi/net.json
@@ -723,8 +723,6 @@
# Trigger generation of broadcast RARP frames to update network switches.
# This can be useful when network bonds fail-over the active slave.
#
-# @params: AnnounceParameters giving timing and repetition count of announce
-#
# Example:
#
# -> { "execute": "announce-self",
diff --git a/qemu-deprecated.texi b/qemu-deprecated.texi
index 7239e0959d..f727bd3932 100644
--- a/qemu-deprecated.texi
+++ b/qemu-deprecated.texi
@@ -149,6 +149,18 @@ QEMU 4.1 has three options, please migrate to one of these three:
@section QEMU Machine Protocol (QMP) commands
+@subsection change (since 2.5.0)
+
+Use ``blockdev-change-medium'' or ``change-vnc-password'' instead.
+
+@subsection migrate_set_downtime and migrate_set_speed (since 2.8.0)
+
+Use ``migrate-set-parameters'' instead.
+
+@subsection migrate-set-cache-size and query-migrate-cache-size (since 2.11.0)
+
+Use ``migrate-set-parameters'' and ``query-migrate-parameters'' instead.
+
@subsection query-block result field dirty-bitmaps[i].status (since 4.0)
The ``status'' field of the ``BlockDirtyInfo'' structure, returned by
diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 6d5726cf6e..6f1c17f71f 100644
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -12,7 +12,7 @@ from qapi.gen import QAPIGenDoc, QAPISchemaVisitor
MSG_FMT = """
@deftypefn {type} {{}} {name}
-{body}
+{body}{members}{features}{sections}
@end deftypefn
""".format
@@ -20,7 +20,7 @@ MSG_FMT = """
TYPE_FMT = """
@deftp {{{type}}} {name}
-{body}
+{body}{members}{features}{sections}
@end deftp
""".format
@@ -149,7 +149,8 @@ def texi_member(member, desc, suffix):
suffix, desc, texi_if(member.ifcond, prefix='@*'))
-def texi_members(doc, what, base, variants, member_func):
+def texi_members(doc, what, base=None, variants=None,
+ member_func=texi_member):
"""Format the table of members"""
items = ''
for section in doc.args.values():
@@ -182,6 +183,14 @@ def texi_members(doc, what, base, variants, member_func):
return '\n@b{%s:}\n@table @asis\n%s@end table\n' % (what, items)
+def texi_arguments(doc, boxed_arg_type):
+ if boxed_arg_type:
+ assert not doc.args
+ return ('\n@b{Arguments:} the members of @code{%s}\n'
+ % boxed_arg_type.name)
+ return texi_members(doc, 'Arguments')
+
+
def texi_features(doc):
"""Format the table of features"""
items = ''
@@ -208,12 +217,22 @@ def texi_sections(doc, ifcond):
return body
-def texi_entity(doc, what, ifcond, base=None, variants=None,
- member_func=texi_member):
- return (texi_body(doc)
- + texi_members(doc, what, base, variants, member_func)
- + texi_features(doc)
- + texi_sections(doc, ifcond))
+def texi_type(typ, doc, ifcond, members):
+ return TYPE_FMT(type=typ,
+ name=doc.symbol,
+ body=texi_body(doc),
+ members=members,
+ features=texi_features(doc),
+ sections=texi_sections(doc, ifcond))
+
+
+def texi_msg(typ, doc, ifcond, members):
+ return MSG_FMT(type=typ,
+ name=doc.symbol,
+ body=texi_body(doc),
+ members=members,
+ features=texi_features(doc),
+ sections=texi_sections(doc, ifcond))
class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
@@ -227,48 +246,36 @@ class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
def visit_enum_type(self, name, info, ifcond, members, prefix):
doc = self.cur_doc
- self._gen.add(TYPE_FMT(type='Enum',
- name=doc.symbol,
- body=texi_entity(doc, 'Values', ifcond,
- member_func=texi_enum_value)))
+ self._gen.add(texi_type('Enum', doc, ifcond,
+ texi_members(doc, 'Values',
+ member_func=texi_enum_value)))
def visit_object_type(self, name, info, ifcond, base, members, variants,
features):
doc = self.cur_doc
if base and base.is_implicit():
base = None
- self._gen.add(TYPE_FMT(type='Object',
- name=doc.symbol,
- body=texi_entity(doc, 'Members', ifcond,
- base, variants)))
+ self._gen.add(texi_type('Object', doc, ifcond,
+ texi_members(doc, 'Members', base, variants)))
def visit_alternate_type(self, name, info, ifcond, variants):
doc = self.cur_doc
- self._gen.add(TYPE_FMT(type='Alternate',
- name=doc.symbol,
- body=texi_entity(doc, 'Members', ifcond)))
+ self._gen.add(texi_type('Alternate', doc, ifcond,
+ texi_members(doc, 'Members')))
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig,
features):
doc = self.cur_doc
- if boxed:
- body = texi_body(doc)
- body += ('\n@b{Arguments:} the members of @code{%s}\n'
- % arg_type.name)
- body += texi_features(doc)
- body += texi_sections(doc, ifcond)
- else:
- body = texi_entity(doc, 'Arguments', ifcond)
- self._gen.add(MSG_FMT(type='Command',
- name=doc.symbol,
- body=body))
+ self._gen.add(texi_msg('Command', doc, ifcond,
+ texi_arguments(doc,
+ arg_type if boxed else None)))
def visit_event(self, name, info, ifcond, arg_type, boxed):
doc = self.cur_doc
- self._gen.add(MSG_FMT(type='Event',
- name=doc.symbol,
- body=texi_entity(doc, 'Arguments', ifcond)))
+ self._gen.add(texi_msg('Event', doc, ifcond,
+ texi_arguments(doc,
+ arg_type if boxed else None)))
def symbol(self, doc, entity):
if self._gen._body:
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index 7c7394f835..d7a289eded 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -95,12 +95,6 @@ def check_flags(expr, info):
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):
@@ -126,6 +120,7 @@ def check_if(expr, info, source):
check_if_str(elt, info)
else:
check_if_str(ifcond, info)
+ expr['if'] = [ifcond]
def normalize_members(members):
@@ -175,21 +170,16 @@ def check_type(value, info, source,
raise QAPISemError(info, "%s uses reserved name" % key_source)
check_keys(arg, info, key_source, ['type'], ['if'])
check_if(arg, info, key_source)
- normalize_if(arg)
check_type(arg['type'], info, key_source, allow_array=True)
-def normalize_features(features):
- if isinstance(features, list):
- features[:] = [f if isinstance(f, dict) else {'name': f}
- for f in features]
-
-
def check_features(features, info):
if features is None:
return
if not isinstance(features, list):
raise QAPISemError(info, "'features' must be an array")
+ features[:] = [f if isinstance(f, dict) else {'name': f}
+ for f in features]
for f in features:
source = "'features' member"
assert isinstance(f, dict)
@@ -198,13 +188,6 @@ def check_features(features, info):
source = "%s '%s'" % (source, f['name'])
check_name_str(f['name'], info, source)
check_if(f, info, source)
- normalize_if(f)
-
-
-def normalize_enum(expr):
- if isinstance(expr['data'], list):
- expr['data'] = [m if isinstance(m, dict) else {'name': m}
- for m in expr['data']]
def check_enum(expr, info):
@@ -219,6 +202,8 @@ def check_enum(expr, info):
permit_upper = name in info.pragma.name_case_whitelist
+ members[:] = [m if isinstance(m, dict) else {'name': m}
+ for m in members]
for member in members:
source = "'data' member"
check_keys(member, info, source, ['name'], ['if'])
@@ -227,7 +212,6 @@ def check_enum(expr, info):
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):
@@ -259,7 +243,6 @@ def check_union(expr, info):
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)
@@ -273,7 +256,6 @@ def check_alternate(expr, info):
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)
@@ -339,7 +321,6 @@ def check_exprs(exprs):
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,
@@ -357,7 +338,6 @@ def check_exprs(exprs):
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,
@@ -366,7 +346,6 @@ def check_exprs(exprs):
'gen', 'success-response', 'allow-oob',
'allow-preconfig'])
normalize_members(expr.get('data'))
- normalize_features(expr.get('features'))
check_command(expr, info)
elif meta == 'event':
check_keys(expr, info, meta,
@@ -376,7 +355,6 @@ def check_exprs(exprs):
else:
assert False, 'unexpected meta type'
- normalize_if(expr)
check_if(expr, info, meta)
check_flags(expr, info)
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index e800876ad1..342792e410 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -555,16 +555,31 @@ class QAPIDoc(object):
self.args[member.name] = QAPIDoc.ArgSection(member.name)
self.args[member.name].connect(member)
+ def connect_feature(self, feature):
+ if feature.name not in self.features:
+ raise QAPISemError(feature.info,
+ "feature '%s' lacks documentation"
+ % feature.name)
+ self.features[feature.name] = QAPIDoc.ArgSection(feature.name)
+ self.features[feature.name].connect(feature)
+
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))
+
+ def check_args_section(args, info, what):
+ bogus = [name for name, section in args.items()
+ if not section.member]
+ if bogus:
+ raise QAPISemError(
+ self.info,
+ "documented member%s '%s' %s not exist"
+ % ("s" if len(bogus) > 1 else "",
+ "', '".join(bogus),
+ "do" if len(bogus) > 1 else "does"))
+
+ check_args_section(self.args, self.info, 'members')
+ check_args_section(self.features, self.info, 'features')
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index f7d68a35f4..cf0045f34e 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -27,8 +27,11 @@ from qapi.parser import QAPISchemaParser
class QAPISchemaEntity(object):
meta = None
- def __init__(self, name, info, doc, ifcond=None):
+ def __init__(self, name, info, doc, ifcond=None, features=None):
assert name is None or isinstance(name, str)
+ for f in features or []:
+ assert isinstance(f, QAPISchemaFeature)
+ f.set_defined_in(name)
self.name = name
self._module = None
# For explicitly defined entities, info points to the (explicit)
@@ -39,6 +42,7 @@ class QAPISchemaEntity(object):
self.info = info
self.doc = doc
self._ifcond = ifcond or []
+ self.features = features or []
self._checked = False
def c_name(self):
@@ -49,8 +53,21 @@ class QAPISchemaEntity(object):
if self.info:
self._module = os.path.relpath(self.info.fname,
os.path.dirname(schema.fname))
+ seen = {}
+ for f in self.features:
+ f.check_clash(self.info, seen)
+ if self.doc:
+ self.doc.connect_feature(f)
+
self._checked = True
+ def connect_doc(self, doc=None):
+ pass
+
+ def check_doc(self):
+ if self.doc:
+ self.doc.check()
+
@property
def ifcond(self):
assert self._checked
@@ -217,8 +234,12 @@ class QAPISchemaEnumType(QAPISchemaType):
seen = {}
for m in self.members:
m.check_clash(self.info, seen)
- if self.doc:
- self.doc.connect_member(m)
+
+ def connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ for m in self.members:
+ doc.connect_member(m)
def is_implicit(self):
# See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
@@ -296,7 +317,7 @@ class QAPISchemaObjectType(QAPISchemaType):
# 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)
+ QAPISchemaType.__init__(self, name, info, doc, ifcond, features)
self.meta = 'union' if variants else 'struct'
assert base is None or isinstance(base, str)
for m in local_members:
@@ -305,15 +326,11 @@ class QAPISchemaObjectType(QAPISchemaType):
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
@@ -345,22 +362,12 @@ class QAPISchemaObjectType(QAPISchemaType):
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,
@@ -372,6 +379,14 @@ class QAPISchemaObjectType(QAPISchemaType):
for m in self.members:
m.check_clash(info, seen)
+ def connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ if self.base and self.base.is_implicit():
+ self.base.connect_doc(doc)
+ for m in self.local_members:
+ doc.connect_member(m)
+
@property
def ifcond(self):
assert self._checked
@@ -639,10 +654,12 @@ class QAPISchemaAlternateType(QAPISchemaType):
"%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 connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ for v in self.variants.variants:
+ doc.connect_member(v)
def c_type(self):
return c_name(self.name) + pointer_suffix
@@ -662,12 +679,9 @@ class QAPISchemaCommand(QAPISchemaEntity):
def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
gen, success_response, boxed, allow_oob, allow_preconfig,
features):
- QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
+ QAPISchemaEntity.__init__(self, name, info, doc, ifcond, features)
assert not arg_type or isinstance(arg_type, str)
assert not ret_type or isinstance(ret_type, str)
- for f in features:
- assert isinstance(f, QAPISchemaFeature)
- f.set_defined_in(name)
self._arg_type_name = arg_type
self.arg_type = None
self._ret_type_name = ret_type
@@ -677,7 +691,6 @@ class QAPISchemaCommand(QAPISchemaEntity):
self.boxed = boxed
self.allow_oob = allow_oob
self.allow_preconfig = allow_preconfig
- self.features = features
def check(self, schema):
QAPISchemaEntity.check(self, schema)
@@ -707,10 +720,11 @@ class QAPISchemaCommand(QAPISchemaEntity):
"command's 'returns' cannot take %s"
% self.ret_type.describe())
- # Features are in a name space separate from members
- seen = {}
- for f in self.features:
- f.check_clash(self.info, seen)
+ def connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ if self.arg_type and self.arg_type.is_implicit():
+ self.arg_type.connect_doc(doc)
def visit(self, visitor):
QAPISchemaEntity.visit(self, visitor)
@@ -748,6 +762,12 @@ class QAPISchemaEvent(QAPISchemaEntity):
"event's 'data' can take %s only with 'boxed': true"
% self.arg_type.describe())
+ def connect_doc(self, doc=None):
+ doc = doc or self.doc
+ if doc:
+ if self.arg_type and self.arg_type.is_implicit():
+ self.arg_type.connect_doc(doc)
+
def visit(self, visitor):
QAPISchemaEntity.visit(self, visitor)
visitor.visit_event(self.name, self.info, self.ifcond,
@@ -873,8 +893,7 @@ class QAPISchema(object):
self._def_entity(QAPISchemaArrayType(name, info, element_type))
return name
- def _make_implicit_object_type(self, name, info, doc, ifcond,
- role, members):
+ def _make_implicit_object_type(self, name, info, ifcond, role, members):
if not members:
return None
# See also QAPISchemaObjectTypeMember.describe()
@@ -892,7 +911,7 @@ class QAPISchema(object):
# 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,
+ self._def_entity(QAPISchemaObjectType(name, info, None, ifcond,
None, members, None, []))
return name
@@ -939,7 +958,7 @@ class QAPISchema(object):
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),
+ typ, info, self.lookup_type(typ),
'wrapper', [self._make_member('data', typ, None, info)])
return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
@@ -952,7 +971,7 @@ class QAPISchema(object):
tag_member = None
if isinstance(base, dict):
base = self._make_implicit_object_type(
- name, info, doc, ifcond,
+ name, info, ifcond,
'base', self._make_members(base, info))
if tag_name:
variants = [self._make_variant(key, value['type'],
@@ -999,7 +1018,7 @@ class QAPISchema(object):
features = expr.get('features', [])
if isinstance(data, OrderedDict):
data = self._make_implicit_object_type(
- name, info, doc, ifcond, 'arg', self._make_members(data, info))
+ name, info, ifcond, 'arg', self._make_members(data, info))
if isinstance(rets, list):
assert len(rets) == 1
rets = self._make_array_type(rets[0], info)
@@ -1015,7 +1034,7 @@ class QAPISchema(object):
ifcond = expr.get('if')
if isinstance(data, OrderedDict):
data = self._make_implicit_object_type(
- name, info, doc, ifcond, 'arg', self._make_members(data, info))
+ name, info, ifcond, 'arg', self._make_members(data, info))
self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
def _def_exprs(self, exprs):
@@ -1043,6 +1062,8 @@ class QAPISchema(object):
def check(self):
for ent in self._entity_list:
ent.check(self)
+ ent.connect_doc()
+ ent.check_doc()
def visit(self, visitor):
visitor.visit_begin(self)
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 7f487f65e7..34ec03391c 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -341,7 +341,11 @@ qapi-schema += base-cycle-indirect.json
qapi-schema += command-int.json
qapi-schema += comments.json
qapi-schema += doc-bad-alternate-member.json
+qapi-schema += doc-bad-boxed-command-arg.json
qapi-schema += doc-bad-command-arg.json
+qapi-schema += doc-bad-enum-member.json
+qapi-schema += doc-bad-event-arg.json
+qapi-schema += doc-bad-feature.json
qapi-schema += doc-bad-section.json
qapi-schema += doc-bad-symbol.json
qapi-schema += doc-bad-union-member.json
@@ -365,6 +369,7 @@ qapi-schema += doc-missing-expr.json
qapi-schema += doc-missing-space.json
qapi-schema += doc-missing.json
qapi-schema += doc-no-symbol.json
+qapi-schema += doc-undoc-feature.json
qapi-schema += double-type.json
qapi-schema += duplicate-key.json
qapi-schema += empty.json
diff --git a/tests/qapi-schema/doc-bad-alternate-member.err b/tests/qapi-schema/doc-bad-alternate-member.err
index a1c282f935..d7286bb57c 100644
--- a/tests/qapi-schema/doc-bad-alternate-member.err
+++ b/tests/qapi-schema/doc-bad-alternate-member.err
@@ -1 +1 @@
-doc-bad-alternate-member.json:3: the following documented members are not in the declaration: aa, bb
+doc-bad-alternate-member.json:3: documented members 'aa', 'bb' do not exist
diff --git a/tests/qapi-schema/doc-bad-boxed-command-arg.err b/tests/qapi-schema/doc-bad-boxed-command-arg.err
new file mode 100644
index 0000000000..7137af3ec9
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-boxed-command-arg.err
@@ -0,0 +1 @@
+doc-bad-boxed-command-arg.json:9: documented member 'a' does not exist
diff --git a/tests/qapi-schema/doc-bad-boxed-command-arg.json b/tests/qapi-schema/doc-bad-boxed-command-arg.json
new file mode 100644
index 0000000000..bd143241ec
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-boxed-command-arg.json
@@ -0,0 +1,14 @@
+# Boxed arguments are not to be documented with the command
+
+##
+# @Args:
+# @a: an argument
+##
+{ 'struct': 'Args', 'data': { 'a': 'int' } }
+
+##
+# @cmd-boxed:
+# @a: bogus
+##
+{ 'command': 'cmd-boxed', 'boxed': true,
+ 'data': 'Args' }
diff --git a/tests/qapi-schema/doc-bad-boxed-command-arg.out b/tests/qapi-schema/doc-bad-boxed-command-arg.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-boxed-command-arg.out
diff --git a/tests/qapi-schema/doc-bad-command-arg.err b/tests/qapi-schema/doc-bad-command-arg.err
index 153ea0330a..18ed076cef 100644
--- a/tests/qapi-schema/doc-bad-command-arg.err
+++ b/tests/qapi-schema/doc-bad-command-arg.err
@@ -1 +1 @@
-doc-bad-command-arg.json:3: the following documented members are not in the declaration: b
+doc-bad-command-arg.json:3: documented member 'b' does not exist
diff --git a/tests/qapi-schema/doc-bad-enum-member.err b/tests/qapi-schema/doc-bad-enum-member.err
new file mode 100644
index 0000000000..7efeb47363
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-enum-member.err
@@ -0,0 +1 @@
+doc-bad-enum-member.json:3: documented member 'a' does not exist
diff --git a/tests/qapi-schema/doc-bad-enum-member.json b/tests/qapi-schema/doc-bad-enum-member.json
new file mode 100644
index 0000000000..9cab35c6e8
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-enum-member.json
@@ -0,0 +1,8 @@
+# Members listed in the doc comment must exist in the actual schema
+
+##
+# @Foo:
+# @a: a
+# @b: b
+##
+{ 'enum': 'Foo', 'data': [ 'b' ] }
diff --git a/tests/qapi-schema/doc-bad-enum-member.out b/tests/qapi-schema/doc-bad-enum-member.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-enum-member.out
diff --git a/tests/qapi-schema/doc-bad-event-arg.err b/tests/qapi-schema/doc-bad-event-arg.err
new file mode 100644
index 0000000000..d13cacf21f
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-event-arg.err
@@ -0,0 +1 @@
+doc-bad-event-arg.json:3: documented member 'a' does not exist
diff --git a/tests/qapi-schema/doc-bad-event-arg.json b/tests/qapi-schema/doc-bad-event-arg.json
new file mode 100644
index 0000000000..23c83cc81f
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-event-arg.json
@@ -0,0 +1,7 @@
+# Arguments listed in the doc comment must exist in the actual schema
+
+##
+# @FOO:
+# @a: a
+##
+{ 'event': 'FOO' }
diff --git a/tests/qapi-schema/doc-bad-event-arg.out b/tests/qapi-schema/doc-bad-event-arg.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-event-arg.out
diff --git a/tests/qapi-schema/doc-bad-feature.err b/tests/qapi-schema/doc-bad-feature.err
new file mode 100644
index 0000000000..e4c62adfa3
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-feature.err
@@ -0,0 +1 @@
+doc-bad-feature.json:3: documented member 'a' does not exist
diff --git a/tests/qapi-schema/doc-bad-feature.json b/tests/qapi-schema/doc-bad-feature.json
new file mode 100644
index 0000000000..3d49b8e607
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-feature.json
@@ -0,0 +1,9 @@
+# Features listed in the doc comment must exist in the actual schema
+
+##
+# @foo:
+#
+# Features:
+# @a: a
+##
+{ 'command': 'foo' }
diff --git a/tests/qapi-schema/doc-bad-feature.out b/tests/qapi-schema/doc-bad-feature.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-feature.out
diff --git a/tests/qapi-schema/doc-bad-union-member.err b/tests/qapi-schema/doc-bad-union-member.err
index 8b9d36eab1..6dd2726a65 100644
--- a/tests/qapi-schema/doc-bad-union-member.err
+++ b/tests/qapi-schema/doc-bad-union-member.err
@@ -1 +1 @@
-doc-bad-union-member.json:3: the following documented members are not in the declaration: a, b
+doc-bad-union-member.json:3: documented members 'a', 'b' do not exist
diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json
index 7dc21e58a3..d992e713d9 100644
--- a/tests/qapi-schema/doc-good.json
+++ b/tests/qapi-schema/doc-good.json
@@ -99,6 +99,14 @@
'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } }
##
+# @Alternate:
+# @i: an integer
+# @b is undocumented
+##
+{ 'alternate': 'Alternate',
+ 'data': { 'i': 'int', 'b': 'bool' } }
+
+##
# == Another subsection
##
@@ -149,3 +157,9 @@
{ 'command': 'cmd-boxed', 'boxed': true,
'data': 'Object',
'features': [ 'cmd-feat1', 'cmd-feat2' ] }
+
+##
+# @EVT-BOXED:
+##
+{ 'event': 'EVT-BOXED', 'boxed': true,
+ 'data': 'Object' }
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index f78fdef6a9..4c9406a464 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -42,6 +42,10 @@ object SugaredUnion
case one: q_obj_Variant1-wrapper
case two: q_obj_Variant2-wrapper
if ['IFTWO']
+alternate Alternate
+ tag type
+ case i: int
+ case b: bool
object q_obj_cmd-arg
member arg1: int optional=False
member arg2: str optional=True
@@ -54,6 +58,8 @@ command cmd-boxed Object -> None
gen=True success_response=True boxed=True oob=False preconfig=False
feature cmd-feat1
feature cmd-feat2
+event EVT-BOXED Object
+ boxed=True
doc freeform
body=
= Section
@@ -120,6 +126,8 @@ A paragraph
Another paragraph (but no @var: line)
arg=var1
+ feature=variant1-feat
+a feature
doc symbol=Variant2
body=
@@ -131,6 +139,14 @@ doc symbol=SugaredUnion
arg=type
+doc symbol=Alternate
+ body=
+
+ arg=i
+an integer
+@b is undocumented
+ arg=b
+
doc freeform
body=
== Another subsection
@@ -144,6 +160,10 @@ the second
argument
arg=arg3
+ feature=cmd-feat1
+a feature
+ feature=cmd-feat2
+another feature
section=Note
@arg3 is undocumented
section=Returns
@@ -166,7 +186,14 @@ Duis aute irure dolor
doc symbol=cmd-boxed
body=
If you're bored enough to read this, go see a video of boxed cats
+ feature=cmd-feat1
+a feature
+ feature=cmd-feat2
+another feature
section=Example
-> in
<- out
+doc symbol=EVT-BOXED
+ body=
+
diff --git a/tests/qapi-schema/doc-good.texi b/tests/qapi-schema/doc-good.texi
index 2ce8b883c9..d4b15dabf0 100644
--- a/tests/qapi-schema/doc-good.texi
+++ b/tests/qapi-schema/doc-good.texi
@@ -170,6 +170,23 @@ One of @t{"one"}, @t{"two"}
@end deftp
+
+@deftp {Alternate} Alternate
+
+
+
+@b{Members:}
+@table @asis
+@item @code{i: int}
+an integer
+@code{b} is undocumented
+@item @code{b: boolean}
+Not documented
+@end table
+
+@end deftp
+
+
@subsection Another subsection
@@ -258,3 +275,13 @@ another feature
@end deftypefn
+
+
+@deftypefn Event {} EVT-BOXED
+
+
+
+@b{Arguments:} the members of @code{Object}
+
+@end deftypefn
+
diff --git a/tests/qapi-schema/doc-undoc-feature.err b/tests/qapi-schema/doc-undoc-feature.err
new file mode 100644
index 0000000000..62fc82d2b9
--- /dev/null
+++ b/tests/qapi-schema/doc-undoc-feature.err
@@ -0,0 +1,2 @@
+doc-undoc-feature.json: In command 'foo':
+doc-undoc-feature.json:9: feature 'undoc' lacks documentation
diff --git a/tests/qapi-schema/doc-undoc-feature.json b/tests/qapi-schema/doc-undoc-feature.json
new file mode 100644
index 0000000000..c52f88e2cd
--- /dev/null
+++ b/tests/qapi-schema/doc-undoc-feature.json
@@ -0,0 +1,9 @@
+# Doc comment must cover all features
+
+##
+# @foo:
+#
+# Features:
+# @doc: documented feature
+##
+{ 'command': 'foo', 'features': ['undoc', 'doc'] }
diff --git a/tests/qapi-schema/doc-undoc-feature.out b/tests/qapi-schema/doc-undoc-feature.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/qapi-schema/doc-undoc-feature.out
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 2bd9fd8742..bad14edb47 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -117,6 +117,8 @@ def test_frontend(fname):
print(' body=\n%s' % doc.body.text)
for arg, section in doc.args.items():
print(' arg=%s\n%s' % (arg, section.text))
+ for feat, section in doc.features.items():
+ print(' feature=%s\n%s' % (feat, section.text))
for section in doc.sections:
print(' section=%s\n%s' % (section.name, section.text))