[RFCv3 06/25] tests: Add tests for xmlgen

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add some tests to make sure xmlgen's validity.

Signed-off-by: Shi Lei <shi_lei@xxxxxxxxxxxxxx>
---
 tests/meson.build                        |   1 +
 tests/xmlgenin/conf/array.h              |  17 +
 tests/xmlgenin/conf/empty.h              |   7 +
 tests/xmlgenin/conf/enum-first-item.h    |  12 +
 tests/xmlgenin/conf/external.h           |   9 +
 tests/xmlgenin/conf/genformat-separate.h |  11 +
 tests/xmlgenin/conf/genformat.h          |  11 +
 tests/xmlgenin/conf/genparse.h           |  11 +
 tests/xmlgenin/conf/namespace.h          |  12 +
 tests/xmlgenin/conf/required.h           |   9 +
 tests/xmlgenin/conf/skipparse.h          |  10 +
 tests/xmlgenin/conf/specify.h            |  13 +
 tests/xmlgenin/conf/xmlattr.h            |  10 +
 tests/xmlgenin/conf/xmlelem.h            |  10 +
 tests/xmlgenin/conf/xmlgroup.h           |   8 +
 tests/xmlgenin/conf/xmlswitch.h          |  17 +
 tests/xmlgenin/util/enums.h              |  58 +++
 tests/xmlgenin/util/structs.h            |  67 ++++
 tests/xmlgenout/array.txt                | 364 ++++++++++++++++++
 tests/xmlgenout/empty.txt                | 181 +++++++++
 tests/xmlgenout/enum-first-item.txt      | 297 ++++++++++++++
 tests/xmlgenout/external.txt             | 205 ++++++++++
 tests/xmlgenout/genformat-separate.txt   | 190 +++++++++
 tests/xmlgenout/genformat.txt            | 142 +++++++
 tests/xmlgenout/genparse.txt             | 154 ++++++++
 tests/xmlgenout/namespace.txt            | 222 +++++++++++
 tests/xmlgenout/required.txt             | 236 ++++++++++++
 tests/xmlgenout/skipparse.txt            | 223 +++++++++++
 tests/xmlgenout/specify.txt              | 291 ++++++++++++++
 tests/xmlgenout/xmlattr.txt              | 252 ++++++++++++
 tests/xmlgenout/xmlelem.txt              | 243 ++++++++++++
 tests/xmlgenout/xmlgroup.txt             | 204 ++++++++++
 tests/xmlgenout/xmlswitch.txt            | 470 +++++++++++++++++++++++
 tests/xmlgentest.c                       | 107 ++++++
 34 files changed, 4074 insertions(+)
 create mode 100644 tests/xmlgenin/conf/array.h
 create mode 100644 tests/xmlgenin/conf/empty.h
 create mode 100644 tests/xmlgenin/conf/enum-first-item.h
 create mode 100644 tests/xmlgenin/conf/external.h
 create mode 100644 tests/xmlgenin/conf/genformat-separate.h
 create mode 100644 tests/xmlgenin/conf/genformat.h
 create mode 100644 tests/xmlgenin/conf/genparse.h
 create mode 100644 tests/xmlgenin/conf/namespace.h
 create mode 100644 tests/xmlgenin/conf/required.h
 create mode 100644 tests/xmlgenin/conf/skipparse.h
 create mode 100644 tests/xmlgenin/conf/specify.h
 create mode 100644 tests/xmlgenin/conf/xmlattr.h
 create mode 100644 tests/xmlgenin/conf/xmlelem.h
 create mode 100644 tests/xmlgenin/conf/xmlgroup.h
 create mode 100644 tests/xmlgenin/conf/xmlswitch.h
 create mode 100644 tests/xmlgenin/util/enums.h
 create mode 100644 tests/xmlgenin/util/structs.h
 create mode 100644 tests/xmlgenout/array.txt
 create mode 100644 tests/xmlgenout/empty.txt
 create mode 100644 tests/xmlgenout/enum-first-item.txt
 create mode 100644 tests/xmlgenout/external.txt
 create mode 100644 tests/xmlgenout/genformat-separate.txt
 create mode 100644 tests/xmlgenout/genformat.txt
 create mode 100644 tests/xmlgenout/genparse.txt
 create mode 100644 tests/xmlgenout/namespace.txt
 create mode 100644 tests/xmlgenout/required.txt
 create mode 100644 tests/xmlgenout/skipparse.txt
 create mode 100644 tests/xmlgenout/specify.txt
 create mode 100644 tests/xmlgenout/xmlattr.txt
 create mode 100644 tests/xmlgenout/xmlelem.txt
 create mode 100644 tests/xmlgenout/xmlgroup.txt
 create mode 100644 tests/xmlgenout/xmlswitch.txt
 create mode 100644 tests/xmlgentest.c

diff --git a/tests/meson.build b/tests/meson.build
index 14ace476..166416cc 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -340,6 +340,7 @@ tests += [
   { 'name': 'viruritest' },
   { 'name': 'vshtabletest', 'link_with': [ libvirt_shell_lib ] },
   { 'name': 'virmigtest' },
+  { 'name': 'xmlgentest' },
 ]
 
 if host_machine.system() == 'linux'
diff --git a/tests/xmlgenin/conf/array.h b/tests/xmlgenin/conf/array.h
new file mode 100644
index 00000000..8f003be4
--- /dev/null
+++ b/tests/xmlgenin/conf/array.h
@@ -0,0 +1,17 @@
+/* Test array features */
+
+#pragma once
+
+typedef struct _virArrayDef virArrayDef;
+struct _virArrayDef {                   /* genparse, genformat */
+    size_t nnames;
+    char **names;                       /* xmlelem:hostname, array */
+    size_t nfwds;
+    virNetworkDNSForwarder *forwarders; /* xmlelem, array:nfwds */
+    size_t ntxts;
+    virNetworkDNSTxtDef *txts;          /* xmlelem, array */
+
+    size_t nroutes;
+    /* ptr to array of static routes on this interface */
+    virNetDevIPRoute **routes;          /* xmlelem, array */
+};
diff --git a/tests/xmlgenin/conf/empty.h b/tests/xmlgenin/conf/empty.h
new file mode 100644
index 00000000..c8292c8e
--- /dev/null
+++ b/tests/xmlgenin/conf/empty.h
@@ -0,0 +1,7 @@
+/* Test empty struct */
+
+#pragma once
+
+typedef struct _virEmptyDef virEmptyDef;
+struct _virEmptyDef {                   /* genparse, genformat */
+};
diff --git a/tests/xmlgenin/conf/enum-first-item.h b/tests/xmlgenin/conf/enum-first-item.h
new file mode 100644
index 00000000..c62dfe8a
--- /dev/null
+++ b/tests/xmlgenin/conf/enum-first-item.h
@@ -0,0 +1,12 @@
+/* Test first item for enum features */
+
+#pragma once
+
+typedef struct _virEnumFirstItemDef virEnumFirstItemDef;
+struct _virEnumFirstItemDef {                       /* genparse, genformat */
+    virNetworkBridgeMACTableManagerType has_def;    /* xmlattr */
+    virNetworkForwardType has_none;                 /* xmlattr */
+    virTristateBool has_absent;                     /* xmlattr */
+
+    virDomainGraphicsType no_default;               /* xmlattr */
+};
diff --git a/tests/xmlgenin/conf/external.h b/tests/xmlgenin/conf/external.h
new file mode 100644
index 00000000..52b46acd
--- /dev/null
+++ b/tests/xmlgenin/conf/external.h
@@ -0,0 +1,9 @@
+/* Test specify features */
+
+#pragma once
+
+typedef struct _virExternalDef virExternalDef;
+struct _virExternalDef {    /* genparse, genformat */
+    /* xmlNode is an external type which is not defined in libvirt */
+    xmlNode *metadata;      /* xmlelem */
+};
diff --git a/tests/xmlgenin/conf/genformat-separate.h b/tests/xmlgenin/conf/genformat-separate.h
new file mode 100644
index 00000000..8e4643af
--- /dev/null
+++ b/tests/xmlgenin/conf/genformat-separate.h
@@ -0,0 +1,11 @@
+/* Test genformat-separate features */
+
+#pragma once
+
+typedef struct _virSeparateDef virSeparateDef;
+struct _virSeparateDef {                /* genformat:separate */
+    virTristateBool enable;             /* xmlattr */
+    virSocketAddr ip;                   /* xmlelem */
+    size_t ntxts;
+    virNetworkDNSTxtDef *txts;          /* xmlelem, array */
+};
diff --git a/tests/xmlgenin/conf/genformat.h b/tests/xmlgenin/conf/genformat.h
new file mode 100644
index 00000000..fb92351a
--- /dev/null
+++ b/tests/xmlgenin/conf/genformat.h
@@ -0,0 +1,11 @@
+/* Test genformat features */
+
+#pragma once
+
+typedef struct _virGenFormatDef virGenFormatDef;
+struct _virGenFormatDef {               /* genformat */
+    virTristateBool enable;             /* xmlattr */
+    virSocketAddr ip;                   /* xmlelem */
+    size_t ntxts;
+    virNetworkDNSTxtDef *txts;          /* xmlelem, array */
+};
diff --git a/tests/xmlgenin/conf/genparse.h b/tests/xmlgenin/conf/genparse.h
new file mode 100644
index 00000000..daf53817
--- /dev/null
+++ b/tests/xmlgenin/conf/genparse.h
@@ -0,0 +1,11 @@
+/* Test genparse features */
+
+#pragma once
+
+typedef struct _virGenParseDef virGenParseDef;
+struct _virGenParseDef {                /* genparse */
+    virTristateBool enable;             /* xmlattr */
+    virSocketAddr ip;                   /* xmlelem */
+    size_t ntxts;
+    virNetworkDNSTxtDef *txts;          /* xmlelem, array */
+};
diff --git a/tests/xmlgenin/conf/namespace.h b/tests/xmlgenin/conf/namespace.h
new file mode 100644
index 00000000..0637e64a
--- /dev/null
+++ b/tests/xmlgenin/conf/namespace.h
@@ -0,0 +1,12 @@
+/* Test namespace features */
+
+#pragma once
+
+typedef struct _virNameSpaceDef virNameSpaceDef;
+struct _virNameSpaceDef {   /* genparse, genformat */
+    char *name;             /* xmlelem */
+
+    /* Network specific XML namespace data */
+    void *namespaceData;
+    virXMLNamespace ns;
+};
diff --git a/tests/xmlgenin/conf/required.h b/tests/xmlgenin/conf/required.h
new file mode 100644
index 00000000..d45af727
--- /dev/null
+++ b/tests/xmlgenin/conf/required.h
@@ -0,0 +1,9 @@
+/* Test required features */
+
+#pragma once
+
+typedef struct _virRequiredDef virRequiredDef;
+struct _virRequiredDef {        /* genparse, genformat */
+    char *name;                 /* xmlattr, required */
+    virNetworkBootpDef bootp;   /* xmlelem, required */
+};
diff --git a/tests/xmlgenin/conf/skipparse.h b/tests/xmlgenin/conf/skipparse.h
new file mode 100644
index 00000000..680a1107
--- /dev/null
+++ b/tests/xmlgenin/conf/skipparse.h
@@ -0,0 +1,10 @@
+/* Test skipparse features */
+
+#pragma once
+
+typedef struct _virSkipParseDef virSkipParseDef;
+struct _virSkipParseDef {           /* genparse, genformat */
+    int connections;                /* xmlattr, skipparse */
+    size_t nifs;
+    virNetworkForwardIfDef *ifs;    /* xmlelem, array, skipparse */
+};
diff --git a/tests/xmlgenin/conf/specify.h b/tests/xmlgenin/conf/specify.h
new file mode 100644
index 00000000..419bd24d
--- /dev/null
+++ b/tests/xmlgenin/conf/specify.h
@@ -0,0 +1,13 @@
+/* Test specify features */
+
+#pragma once
+
+typedef struct _virSpecifyDef virSpecifyDef;
+struct _virSpecifyDef {                     /* genparse, genformat */
+    virNetworkDHCPLeaseTimeUnitType unit;   /* xmlattr */
+    unsigned long expiry;                   /* xmlattr, specify:unit */
+    virMacAddr mac;                         /* xmlattr:mac/address */
+    bool mac_specified;                     /* specify:mac */
+    virUUID uuid;                           /* xmlelem, xmltext */
+    bool uuid_specified;                    /* specify:uuid */
+};
diff --git a/tests/xmlgenin/conf/xmlattr.h b/tests/xmlgenin/conf/xmlattr.h
new file mode 100644
index 00000000..60dfae9a
--- /dev/null
+++ b/tests/xmlgenin/conf/xmlattr.h
@@ -0,0 +1,10 @@
+/* Test xmlattr features */
+
+#pragma once
+
+typedef struct _virXMLAttrDef virXMLAttrDef;
+struct _virXMLAttrDef {         /* genparse, genformat */
+    char *family;               /* xmlattr */
+    virTristateBool localPTR;   /* xmlattr:localPtr */
+    char *tftproot;             /* xmlattr:tftp/root */
+};
diff --git a/tests/xmlgenin/conf/xmlelem.h b/tests/xmlgenin/conf/xmlelem.h
new file mode 100644
index 00000000..d111f2b5
--- /dev/null
+++ b/tests/xmlgenin/conf/xmlelem.h
@@ -0,0 +1,10 @@
+/* Test xmlelem features */
+
+#pragma once
+
+typedef struct _virXMLElemDef virXMLElemDef;
+struct _virXMLElemDef {         /* genparse, genformat */
+    virPortRange port;          /* xmlelem */
+    virSocketAddrRange addr;    /* xmlelem:address */
+    virUUID uuid;               /* xmlelem, xmltext */
+};
diff --git a/tests/xmlgenin/conf/xmlgroup.h b/tests/xmlgenin/conf/xmlgroup.h
new file mode 100644
index 00000000..ed12d919
--- /dev/null
+++ b/tests/xmlgenin/conf/xmlgroup.h
@@ -0,0 +1,8 @@
+/* Test xmlgroup features */
+
+#pragma once
+
+typedef struct _virXMLGroupDef virXMLGroupDef;
+struct _virXMLGroupDef {    /* genparse, genformat */
+    virUtilAuthDef auth;    /* xmlgroup */
+};
diff --git a/tests/xmlgenin/conf/xmlswitch.h b/tests/xmlgenin/conf/xmlswitch.h
new file mode 100644
index 00000000..8730f1b3
--- /dev/null
+++ b/tests/xmlgenin/conf/xmlswitch.h
@@ -0,0 +1,17 @@
+/* Test xmlswitch features */
+
+#pragma once
+
+typedef struct _virXMLSwitchDef virXMLSwitchDef;
+struct _virXMLSwitchDef {                               /* genparse, genformat */
+    virDomainGraphicsType type;                         /* xmlattr */
+
+    union {
+        virDomainGraphicsSDLDef sdl;                    /* xmlgroup */
+        virDomainGraphicsVNCDef vnc;                    /* xmlgroup */
+        virDomainGraphicsRDPDef rdp;                    /* xmlgroup */
+        virDomainGraphicsDesktopDef desktop;            /* xmlgroup */
+        virDomainGraphicsSpiceDef spice;                /* xmlgroup */
+        virDomainGraphicsEGLHeadlessDef egl_headless;   /* xmlgroup */
+    } data;                                             /* xmlswitch:type */
+};
diff --git a/tests/xmlgenin/util/enums.h b/tests/xmlgenin/util/enums.h
new file mode 100644
index 00000000..89d2eccd
--- /dev/null
+++ b/tests/xmlgenin/util/enums.h
@@ -0,0 +1,58 @@
+/* Basic enum */
+
+#pragma once
+
+/*
+ * The tool xmlgen scans all header-files in 'util' and 'conf'
+ * to find all enum types, and further, to determine whether
+ * they have default item.
+ *
+ */
+
+typedef enum {
+    VIR_DOMAIN_GRAPHICS_TYPE_SDL,
+    VIR_DOMAIN_GRAPHICS_TYPE_VNC,
+    VIR_DOMAIN_GRAPHICS_TYPE_RDP,
+    VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP,
+    VIR_DOMAIN_GRAPHICS_TYPE_SPICE,
+    VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS,
+
+    VIR_DOMAIN_GRAPHICS_TYPE_LAST
+} virDomainGraphicsType;
+
+typedef enum {
+    VIR_NETWORK_FORWARD_NONE   = 0,
+    VIR_NETWORK_FORWARD_NAT,
+    VIR_NETWORK_FORWARD_ROUTE,
+    VIR_NETWORK_FORWARD_OPEN,
+    VIR_NETWORK_FORWARD_BRIDGE,
+    VIR_NETWORK_FORWARD_PRIVATE,
+    VIR_NETWORK_FORWARD_VEPA,
+    VIR_NETWORK_FORWARD_PASSTHROUGH,
+    VIR_NETWORK_FORWARD_HOSTDEV,
+
+    VIR_NETWORK_FORWARD_LAST,
+} virNetworkForwardType;
+
+typedef enum {
+    VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_DEFAULT = 0,
+    VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_KERNEL,
+    VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_LIBVIRT,
+    VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_LAST,
+} virNetworkBridgeMACTableManagerType;
+
+typedef enum {
+    VIR_TRISTATE_BOOL_ABSENT = 0,
+    VIR_TRISTATE_BOOL_YES,
+    VIR_TRISTATE_BOOL_NO,
+
+    VIR_TRISTATE_BOOL_LAST
+} virTristateBool;
+
+typedef enum {
+    VIR_NETWORK_DHCP_LEASETIME_UNIT_SECONDS = 0,
+    VIR_NETWORK_DHCP_LEASETIME_UNIT_MINUTES,
+    VIR_NETWORK_DHCP_LEASETIME_UNIT_HOURS,
+
+    VIR_NETWORK_DHCP_LEASETIME_UNIT_LAST,
+} virNetworkDHCPLeaseTimeUnitType;
diff --git a/tests/xmlgenin/util/structs.h b/tests/xmlgenin/util/structs.h
new file mode 100644
index 00000000..80c10096
--- /dev/null
+++ b/tests/xmlgenin/util/structs.h
@@ -0,0 +1,67 @@
+/* Basic structs */
+
+#pragma once
+
+/*
+ * The tool xmlgen scans all header-files in 'util' and 'conf'
+ * to find all struct types.
+ * For tests, it's unnecessary to include any members for these structs.
+ */
+
+typedef struct _virNetDevIPRoute virNetDevIPRoute;
+struct _virNetDevIPRoute {
+};
+
+typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder;
+struct _virNetworkDNSForwarder {
+};
+
+typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef;
+struct _virNetworkDNSTxtDef {
+};
+
+typedef struct _virSocketAddr virSocketAddr;
+struct _virSocketAddr {
+};
+
+typedef struct _virNetworkForwardIfDef virNetworkForwardIfDef;
+struct _virNetworkForwardIfDef {
+};
+
+typedef struct _virPortRange virPortRange;
+struct _virPortRange {
+};
+
+typedef struct _virSocketAddrRange virSocketAddrRange;
+struct _virSocketAddrRange {
+};
+
+typedef struct _virUtilAuthDef virUtilAuthDef;
+struct _virUtilAuthDef {
+};
+
+typedef struct _virNetworkBootpDef virNetworkBootpDef;
+struct _virNetworkBootpDef {
+};
+
+typedef struct _virMacAddr virMacAddr;
+struct _virMacAddr {
+};
+
+struct _virDomainGraphicsSDLDef {
+};
+
+struct _virDomainGraphicsVNCDef {
+};
+
+struct _virDomainGraphicsRDPDef {
+};
+
+struct _virDomainGraphicsDesktopDef {
+};
+
+struct _virDomainGraphicsSpiceDef {
+};
+
+struct _virDomainGraphicsEGLHeadlessDef {
+};
diff --git a/tests/xmlgenout/array.txt b/tests/xmlgenout/array.txt
new file mode 100644
index 00000000..ccb2d168
--- /dev/null
+++ b/tests/xmlgenout/array.txt
@@ -0,0 +1,364 @@
+[conf/array.generated.h]
+
+void
+virArrayDefClear(virArrayDef *def);
+
+
+[conf/array.generated.c]
+
+void
+virArrayDefClear(virArrayDef *def)
+{
+    if (!def)
+        return;
+
+    if (def->nnames) {
+        size_t i;
+        for (i = 0; i < def->nnames; i++) {
+            g_free(def->names[i]);
+            def->names[i] = NULL;
+        }
+    }
+    g_free(def->names);
+    def->names = NULL;
+    def->nnames = 0;
+
+    if (def->nfwds) {
+        size_t i;
+        for (i = 0; i < def->nfwds; i++)
+            virNetworkDNSForwarderClear(&def->forwarders[i]);
+    }
+    g_free(def->forwarders);
+    def->forwarders = NULL;
+    def->nfwds = 0;
+
+    if (def->ntxts) {
+        size_t i;
+        for (i = 0; i < def->ntxts; i++)
+            virNetworkDNSTxtDefClear(&def->txts[i]);
+    }
+    g_free(def->txts);
+    def->txts = NULL;
+    def->ntxts = 0;
+
+    if (def->nroutes) {
+        size_t i;
+        for (i = 0; i < def->nroutes; i++) {
+            virNetDevIPRouteClear(def->routes[i]);
+            g_free(def->routes[i]);
+            def->routes[i] = NULL;
+        }
+    }
+    g_free(def->routes);
+    def->routes = NULL;
+    def->nroutes = 0;
+}
+
+
+[conf/array.generated.h]
+
+int
+virArrayDefParseXML(xmlNodePtr node,
+                    virArrayDef *def,
+                    const char *instname,
+                    void *parent,
+                    void *opaque);
+
+
+[conf/array.generated.h]
+
+#ifdef ENABLE_VIR_ARRAY_DEF_PARSE_HOOK
+
+int
+virArrayDefParseHook(xmlNodePtr node,
+                     virArrayDef *def,
+                     const char *instname,
+                     void *parent,
+                     void *opaque,
+                     int nHostnameNodes,
+                     int nForwarderNodes,
+                     int nTxtNodes,
+                     int nRouteNodes);
+
+#endif
+
+
+[conf/array.generated.h]
+
+#ifdef ENABLE_VIR_ARRAY_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virArrayDefParseXMLSetArgs(xmlNodePtr node,
+                           void *parent,
+                           void **pparent,
+                           void **popaque);
+
+#endif
+
+
+[conf/array.generated.c]
+
+int
+virArrayDefParseXML(xmlNodePtr node,
+                    virArrayDef *def,
+                    const char *instname G_GNUC_UNUSED,
+                    void *parent G_GNUC_UNUSED,
+                    void *opaque G_GNUC_UNUSED)
+{
+    int nHostnameNodes = 0;
+    xmlNodePtr *nodes = NULL;
+    int nForwarderNodes = 0;
+    int nTxtNodes = 0;
+    int nRouteNodes = 0;
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_ARRAY_DEF_PARSE_HOOK_SET_ARGS
+    virArrayDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    nHostnameNodes = virXMLChildNodeSet(node, "hostname", &nodes);
+    if (nHostnameNodes > 0) {
+        size_t i;
+
+        def->names = g_new0(typeof(*def->names), nHostnameNodes);
+        for (i = 0; i < nHostnameNodes; i++) {
+            xmlNodePtr tnode = nodes[i];
+            def->names[i] = virXMLNodeContentString(tnode);
+        }
+        def->nnames = nHostnameNodes;
+        g_free(nodes);
+        nodes = NULL;
+    } else if (nHostnameNodes < 0) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("Invalid %s element found."),
+                       "hostname");
+        goto error;
+    }
+
+    nForwarderNodes = virXMLChildNodeSet(node, "forwarder", &nodes);
+    if (nForwarderNodes > 0) {
+        size_t i;
+
+        def->forwarders = g_new0(typeof(*def->forwarders), nForwarderNodes);
+        for (i = 0; i < nForwarderNodes; i++) {
+            xmlNodePtr tnode = nodes[i];
+            if (virNetworkDNSForwarderParseXML(tnode, &def->forwarders[i], instname, arg_parent, arg_opaque) < 0)
+                goto error;
+        }
+        def->nfwds = nForwarderNodes;
+        g_free(nodes);
+        nodes = NULL;
+    } else if (nForwarderNodes < 0) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("Invalid %s element found."),
+                       "forwarder");
+        goto error;
+    }
+
+    nTxtNodes = virXMLChildNodeSet(node, "txt", &nodes);
+    if (nTxtNodes > 0) {
+        size_t i;
+
+        def->txts = g_new0(typeof(*def->txts), nTxtNodes);
+        for (i = 0; i < nTxtNodes; i++) {
+            xmlNodePtr tnode = nodes[i];
+            if (virNetworkDNSTxtDefParseXML(tnode, &def->txts[i], instname, arg_parent, arg_opaque) < 0)
+                goto error;
+        }
+        def->ntxts = nTxtNodes;
+        g_free(nodes);
+        nodes = NULL;
+    } else if (nTxtNodes < 0) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("Invalid %s element found."),
+                       "txt");
+        goto error;
+    }
+
+    nRouteNodes = virXMLChildNodeSet(node, "route", &nodes);
+    if (nRouteNodes > 0) {
+        size_t i;
+
+        def->routes = g_new0(typeof(*def->routes), nRouteNodes);
+        for (i = 0; i < nRouteNodes; i++) {
+            xmlNodePtr tnode = nodes[i];
+            def->routes[i] = g_new0(typeof(*def->routes[i]), 1);
+            if (virNetDevIPRouteParseXML(tnode, def->routes[i], instname, arg_parent, arg_opaque) < 0)
+                goto error;
+        }
+        def->nroutes = nRouteNodes;
+        g_free(nodes);
+        nodes = NULL;
+    } else if (nRouteNodes < 0) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("Invalid %s element found."),
+                       "route");
+        goto error;
+    }
+
+#ifdef ENABLE_VIR_ARRAY_DEF_PARSE_HOOK
+    if (virArrayDefParseHook(node, def, instname, parent, opaque, nHostnameNodes, nForwarderNodes, nTxtNodes, nRouteNodes) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    g_free(nodes);
+    nodes = NULL;
+    virArrayDefClear(def);
+    return -1;
+}
+
+
+[conf/array.generated.h]
+
+int
+virArrayDefFormatBuf(virBuffer *buf,
+                     const char *name,
+                     const virArrayDef *def,
+                     const void *parent,
+                     void *opaque);
+
+
+[conf/array.generated.h]
+
+#ifdef ENABLE_VIR_ARRAY_DEF_FORMAT_HOOK
+
+int
+virArrayDefFormatHook(const virArrayDef *def,
+                      const void *parent,
+                      const void *opaque,
+                      virTristateBool *empty,
+                      virTristateBool *shortcut,
+                      virBuffer *namesBuf,
+                      virBuffer *forwardersBuf,
+                      virBuffer *txtsBuf,
+                      virBuffer *routesBuf);
+
+#endif /* ENABLE_VIR_ARRAY_DEF_FORMAT_HOOK */
+
+
+[conf/array.generated.c]
+
+int
+virArrayDefFormatBuf(virBuffer *buf,
+                     const char *name,
+                     const virArrayDef *def,
+                     const void *parent G_GNUC_UNUSED,
+                     void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) namesBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) forwardersBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) txtsBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) routesBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_ARRAY_DEF_FORMAT_HOOK
+    if (virArrayDefFormatHook(def, parent, opaque, &empty, &shortcut, &namesBuf, &forwardersBuf, &txtsBuf, &routesBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_ARRAY_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(def->nnames || def->nfwds || def->ntxts || def->nroutes))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    virBufferInheritIndent(&namesBuf, buf);
+    if (!virBufferTouched(&namesBuf) && def->nnames) {
+        size_t i;
+        for (i = 0; i < def->nnames; i++)
+            virBufferEscapeString(buf, "<hostname>%s</hostname>\n", def->names[i]);
+    }
+    virBufferAddBuffer(buf, &namesBuf);
+
+    virBufferInheritIndent(&forwardersBuf, buf);
+    if (!virBufferTouched(&forwardersBuf) && def->nfwds) {
+        size_t i;
+        for (i = 0; i < def->nfwds; i++) {
+            if (virNetworkDNSForwarderFormatBuf(buf, "forwarder", &def->forwarders[i], def, opaque) < 0)
+                return -1;
+        }
+    }
+    virBufferAddBuffer(buf, &forwardersBuf);
+
+    virBufferInheritIndent(&txtsBuf, buf);
+    if (!virBufferTouched(&txtsBuf) && def->ntxts) {
+        size_t i;
+        for (i = 0; i < def->ntxts; i++) {
+            if (virNetworkDNSTxtDefFormatBuf(buf, "txt", &def->txts[i], def, opaque) < 0)
+                return -1;
+        }
+    }
+    virBufferAddBuffer(buf, &txtsBuf);
+
+    virBufferInheritIndent(&routesBuf, buf);
+    if (!virBufferTouched(&routesBuf) && def->nroutes) {
+        size_t i;
+        for (i = 0; i < def->nroutes; i++) {
+            if (virNetDevIPRouteFormatBuf(buf, "route", def->routes[i], def, opaque) < 0)
+                return -1;
+        }
+    }
+    virBufferAddBuffer(buf, &routesBuf);
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/array.generated.h]
+
+bool
+virArrayDefCheck(const virArrayDef *def,
+                 const void *parent,
+                 void *opaque);
+
+
+[conf/array.generated.c]
+
+#ifndef RESET_VIR_ARRAY_DEF_CHECK
+
+bool
+virArrayDefCheck(const virArrayDef *def,
+                 const void *parent G_GNUC_UNUSED,
+                 void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->nnames || def->nfwds || def->ntxts || def->nroutes;
+}
+
+#endif /* RESET_VIR_ARRAY_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/array.h" */
+/* Makesure "array.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_ARRAY_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_ARRAY_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_ARRAY_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_ARRAY_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "array.generated.h"
diff --git a/tests/xmlgenout/empty.txt b/tests/xmlgenout/empty.txt
new file mode 100644
index 00000000..c83a2c12
--- /dev/null
+++ b/tests/xmlgenout/empty.txt
@@ -0,0 +1,181 @@
+[conf/empty.generated.h]
+
+void
+virEmptyDefClear(virEmptyDef *def);
+
+
+[conf/empty.generated.c]
+
+void
+virEmptyDefClear(virEmptyDef *def)
+{
+    if (!def)
+        return;
+}
+
+
+[conf/empty.generated.h]
+
+int
+virEmptyDefParseXML(xmlNodePtr node,
+                    virEmptyDef *def,
+                    const char *instname,
+                    void *parent,
+                    void *opaque);
+
+
+[conf/empty.generated.h]
+
+#ifdef ENABLE_VIR_EMPTY_DEF_PARSE_HOOK
+
+int
+virEmptyDefParseHook(xmlNodePtr node,
+                     virEmptyDef *def,
+                     const char *instname,
+                     void *parent,
+                     void *opaque);
+
+#endif
+
+
+[conf/empty.generated.h]
+
+#ifdef ENABLE_VIR_EMPTY_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virEmptyDefParseXMLSetArgs(xmlNodePtr node,
+                           void *parent,
+                           void **pparent,
+                           void **popaque);
+
+#endif
+
+
+[conf/empty.generated.c]
+
+int
+virEmptyDefParseXML(xmlNodePtr node,
+                    virEmptyDef *def,
+                    const char *instname G_GNUC_UNUSED,
+                    void *parent G_GNUC_UNUSED,
+                    void *opaque G_GNUC_UNUSED)
+{
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_EMPTY_DEF_PARSE_HOOK_SET_ARGS
+    virEmptyDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+#ifdef ENABLE_VIR_EMPTY_DEF_PARSE_HOOK
+    if (virEmptyDefParseHook(node, def, instname, parent, opaque) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virEmptyDefClear(def);
+    return -1;
+}
+
+
+[conf/empty.generated.h]
+
+int
+virEmptyDefFormatBuf(virBuffer *buf,
+                     const char *name,
+                     const virEmptyDef *def,
+                     const void *parent,
+                     void *opaque);
+
+
+[conf/empty.generated.h]
+
+#ifdef ENABLE_VIR_EMPTY_DEF_FORMAT_HOOK
+
+int
+virEmptyDefFormatHook(const virEmptyDef *def,
+                      const void *parent,
+                      const void *opaque,
+                      virTristateBool *empty,
+                      virTristateBool *shortcut);
+
+#endif /* ENABLE_VIR_EMPTY_DEF_FORMAT_HOOK */
+
+
+[conf/empty.generated.c]
+
+int
+virEmptyDefFormatBuf(virBuffer *buf,
+                     const char *name,
+                     const virEmptyDef *def,
+                     const void *parent G_GNUC_UNUSED,
+                     void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_EMPTY_DEF_FORMAT_HOOK
+    if (virEmptyDefFormatHook(def, parent, opaque, &empty, &shortcut) < 0)
+        return -1;
+#endif /* ENABLE_VIR_EMPTY_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty)
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferAddLit(buf, "/>\n");
+
+    return 0;
+}
+
+
+[conf/empty.generated.h]
+
+bool
+virEmptyDefCheck(const virEmptyDef *def,
+                 const void *parent,
+                 void *opaque);
+
+
+[conf/empty.generated.c]
+
+#ifndef RESET_VIR_EMPTY_DEF_CHECK
+
+bool
+virEmptyDefCheck(const virEmptyDef *def,
+                 const void *parent G_GNUC_UNUSED,
+                 void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return false;
+}
+
+#endif /* RESET_VIR_EMPTY_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/empty.h" */
+/* Makesure "empty.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_EMPTY_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_EMPTY_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_EMPTY_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_EMPTY_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "empty.generated.h"
diff --git a/tests/xmlgenout/enum-first-item.txt b/tests/xmlgenout/enum-first-item.txt
new file mode 100644
index 00000000..4186d46d
--- /dev/null
+++ b/tests/xmlgenout/enum-first-item.txt
@@ -0,0 +1,297 @@
+[conf/enum-first-item.generated.h]
+
+void
+virEnumFirstItemDefClear(virEnumFirstItemDef *def);
+
+
+[conf/enum-first-item.generated.c]
+
+void
+virEnumFirstItemDefClear(virEnumFirstItemDef *def)
+{
+    if (!def)
+        return;
+
+    def->has_def = 0;
+
+    def->has_none = 0;
+
+    def->has_absent = 0;
+
+    def->no_default = 0;
+}
+
+
+[conf/enum-first-item.generated.h]
+
+int
+virEnumFirstItemDefParseXML(xmlNodePtr node,
+                            virEnumFirstItemDef *def,
+                            const char *instname,
+                            void *parent,
+                            void *opaque);
+
+
+[conf/enum-first-item.generated.h]
+
+#ifdef ENABLE_VIR_ENUM_FIRST_ITEM_DEF_PARSE_HOOK
+
+int
+virEnumFirstItemDefParseHook(xmlNodePtr node,
+                             virEnumFirstItemDef *def,
+                             const char *instname,
+                             void *parent,
+                             void *opaque,
+                             const char *has_defStr,
+                             const char *has_noneStr,
+                             const char *has_absentStr,
+                             const char *no_defaultStr);
+
+#endif
+
+
+[conf/enum-first-item.generated.h]
+
+#ifdef ENABLE_VIR_ENUM_FIRST_ITEM_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virEnumFirstItemDefParseXMLSetArgs(xmlNodePtr node,
+                                   void *parent,
+                                   void **pparent,
+                                   void **popaque);
+
+#endif
+
+
+[conf/enum-first-item.generated.c]
+
+int
+virEnumFirstItemDefParseXML(xmlNodePtr node,
+                            virEnumFirstItemDef *def,
+                            const char *instname G_GNUC_UNUSED,
+                            void *parent G_GNUC_UNUSED,
+                            void *opaque G_GNUC_UNUSED)
+{
+    g_autofree char *has_defStr = NULL;
+    g_autofree char *has_noneStr = NULL;
+    g_autofree char *has_absentStr = NULL;
+    g_autofree char *no_defaultStr = NULL;
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_ENUM_FIRST_ITEM_DEF_PARSE_HOOK_SET_ARGS
+    virEnumFirstItemDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    has_defStr = virXMLPropString(node, "has_def");
+    if (has_defStr) {
+        if ((def->has_def = virNetworkBridgeMACTableManagerTypeFromString(has_defStr)) <= 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid '%s' setting '%s' in '%s'"),
+                           "has_def", has_defStr, instname);
+            goto error;
+        }
+    }
+
+    has_noneStr = virXMLPropString(node, "has_none");
+    if (has_noneStr) {
+        if ((def->has_none = virNetworkForwardTypeFromString(has_noneStr)) <= 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid '%s' setting '%s' in '%s'"),
+                           "has_none", has_noneStr, instname);
+            goto error;
+        }
+    }
+
+    has_absentStr = virXMLPropString(node, "has_absent");
+    if (has_absentStr) {
+        if ((def->has_absent = virTristateBoolTypeFromString(has_absentStr)) <= 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid '%s' setting '%s' in '%s'"),
+                           "has_absent", has_absentStr, instname);
+            goto error;
+        }
+    }
+
+    no_defaultStr = virXMLPropString(node, "no_default");
+    if (no_defaultStr) {
+        if ((def->no_default = virDomainGraphicsTypeFromString(no_defaultStr)) < 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid '%s' setting '%s' in '%s'"),
+                           "no_default", no_defaultStr, instname);
+            goto error;
+        }
+    }
+
+#ifdef ENABLE_VIR_ENUM_FIRST_ITEM_DEF_PARSE_HOOK
+    if (virEnumFirstItemDefParseHook(node, def, instname, parent, opaque, has_defStr, has_noneStr, has_absentStr, no_defaultStr) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virEnumFirstItemDefClear(def);
+    return -1;
+}
+
+
+[conf/enum-first-item.generated.h]
+
+int
+virEnumFirstItemDefFormatBuf(virBuffer *buf,
+                             const char *name,
+                             const virEnumFirstItemDef *def,
+                             const void *parent,
+                             void *opaque);
+
+
+[conf/enum-first-item.generated.h]
+
+#ifdef ENABLE_VIR_ENUM_FIRST_ITEM_DEF_FORMAT_HOOK
+
+int
+virEnumFirstItemDefFormatHook(const virEnumFirstItemDef *def,
+                              const void *parent,
+                              const void *opaque,
+                              virTristateBool *empty,
+                              virTristateBool *shortcut,
+                              virBuffer *has_defBuf,
+                              virBuffer *has_noneBuf,
+                              virBuffer *has_absentBuf,
+                              virBuffer *no_defaultBuf);
+
+#endif /* ENABLE_VIR_ENUM_FIRST_ITEM_DEF_FORMAT_HOOK */
+
+
+[conf/enum-first-item.generated.c]
+
+int
+virEnumFirstItemDefFormatBuf(virBuffer *buf,
+                             const char *name,
+                             const virEnumFirstItemDef *def,
+                             const void *parent G_GNUC_UNUSED,
+                             void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) has_defBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) has_noneBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) has_absentBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) no_defaultBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_ENUM_FIRST_ITEM_DEF_FORMAT_HOOK
+    if (virEnumFirstItemDefFormatHook(def, parent, opaque, &empty, &shortcut, &has_defBuf, &has_noneBuf, &has_absentBuf, &no_defaultBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_ENUM_FIRST_ITEM_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(def->has_def > 0 || def->has_none > 0 || def->has_absent > 0 || def->no_default >= 0))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferInheritIndent(&has_defBuf, buf);
+    if (!virBufferTouched(&has_defBuf) && def->has_def > 0) {
+        const char *str = virNetworkBridgeMACTableManagerTypeToString(def->has_def);
+        if (!str) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown %s type %d"),
+                           "has_def", def->has_def);
+            return -1;
+        }
+        virBufferAsprintf(&has_defBuf, " has_def='%s'", str);
+    }
+    virBufferAddBuffer(buf, &has_defBuf);
+
+    virBufferInheritIndent(&has_noneBuf, buf);
+    if (!virBufferTouched(&has_noneBuf) && def->has_none > 0) {
+        const char *str = virNetworkForwardTypeToString(def->has_none);
+        if (!str) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown %s type %d"),
+                           "has_none", def->has_none);
+            return -1;
+        }
+        virBufferAsprintf(&has_noneBuf, " has_none='%s'", str);
+    }
+    virBufferAddBuffer(buf, &has_noneBuf);
+
+    virBufferInheritIndent(&has_absentBuf, buf);
+    if (!virBufferTouched(&has_absentBuf) && def->has_absent > 0) {
+        const char *str = virTristateBoolTypeToString(def->has_absent);
+        if (!str) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown %s type %d"),
+                           "has_absent", def->has_absent);
+            return -1;
+        }
+        virBufferAsprintf(&has_absentBuf, " has_absent='%s'", str);
+    }
+    virBufferAddBuffer(buf, &has_absentBuf);
+
+    virBufferInheritIndent(&no_defaultBuf, buf);
+    if (!virBufferTouched(&no_defaultBuf) && def->no_default >= 0) {
+        const char *str = virDomainGraphicsTypeToString(def->no_default);
+        if (!str) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown %s type %d"),
+                           "no_default", def->no_default);
+            return -1;
+        }
+        virBufferAsprintf(&no_defaultBuf, " no_default='%s'", str);
+    }
+    virBufferAddBuffer(buf, &no_defaultBuf);
+
+    virBufferAddLit(buf, "/>\n");
+
+    return 0;
+}
+
+
+[conf/enum-first-item.generated.h]
+
+bool
+virEnumFirstItemDefCheck(const virEnumFirstItemDef *def,
+                         const void *parent,
+                         void *opaque);
+
+
+[conf/enum-first-item.generated.c]
+
+#ifndef RESET_VIR_ENUM_FIRST_ITEM_DEF_CHECK
+
+bool
+virEnumFirstItemDefCheck(const virEnumFirstItemDef *def,
+                         const void *parent G_GNUC_UNUSED,
+                         void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->has_def > 0 || def->has_none > 0 || def->has_absent > 0 || def->no_default >= 0;
+}
+
+#endif /* RESET_VIR_ENUM_FIRST_ITEM_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/enum-first-item.h" */
+/* Makesure "enum-first-item.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_ENUM_FIRST_ITEM_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_ENUM_FIRST_ITEM_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_ENUM_FIRST_ITEM_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_ENUM_FIRST_ITEM_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "enum-first-item.generated.h"
diff --git a/tests/xmlgenout/external.txt b/tests/xmlgenout/external.txt
new file mode 100644
index 00000000..4f0ea691
--- /dev/null
+++ b/tests/xmlgenout/external.txt
@@ -0,0 +1,205 @@
+[conf/external.generated.h]
+
+void
+virExternalDefClear(virExternalDef *def);
+
+
+[conf/external.generated.c]
+
+void
+virExternalDefClear(virExternalDef *def)
+{
+    if (!def)
+        return;
+
+    xmlNodeClear(&def->metadata);
+}
+
+
+[conf/external.generated.h]
+
+int
+virExternalDefParseXML(xmlNodePtr node,
+                       virExternalDef *def,
+                       const char *instname,
+                       void *parent,
+                       void *opaque);
+
+
+[conf/external.generated.h]
+
+#ifdef ENABLE_VIR_EXTERNAL_DEF_PARSE_HOOK
+
+int
+virExternalDefParseHook(xmlNodePtr node,
+                        virExternalDef *def,
+                        const char *instname,
+                        void *parent,
+                        void *opaque,
+                        xmlNodePtr metadataNode);
+
+#endif
+
+
+[conf/external.generated.h]
+
+#ifdef ENABLE_VIR_EXTERNAL_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virExternalDefParseXMLSetArgs(xmlNodePtr node,
+                              void *parent,
+                              void **pparent,
+                              void **popaque);
+
+#endif
+
+
+[conf/external.generated.c]
+
+int
+virExternalDefParseXML(xmlNodePtr node,
+                       virExternalDef *def,
+                       const char *instname G_GNUC_UNUSED,
+                       void *parent G_GNUC_UNUSED,
+                       void *opaque G_GNUC_UNUSED)
+{
+    xmlNodePtr metadataNode = NULL;
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_EXTERNAL_DEF_PARSE_HOOK_SET_ARGS
+    virExternalDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    metadataNode = virXMLChildNode(node, "metadata");
+    if (metadataNode) {
+        if (xmlNodeParseXML(metadataNode, &def->metadata, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+    }
+
+#ifdef ENABLE_VIR_EXTERNAL_DEF_PARSE_HOOK
+    if (virExternalDefParseHook(node, def, instname, parent, opaque, metadataNode) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virExternalDefClear(def);
+    return -1;
+}
+
+
+[conf/external.generated.h]
+
+int
+virExternalDefFormatBuf(virBuffer *buf,
+                        const char *name,
+                        const virExternalDef *def,
+                        const void *parent,
+                        void *opaque);
+
+
+[conf/external.generated.h]
+
+#ifdef ENABLE_VIR_EXTERNAL_DEF_FORMAT_HOOK
+
+int
+virExternalDefFormatHook(const virExternalDef *def,
+                         const void *parent,
+                         const void *opaque,
+                         virTristateBool *empty,
+                         virTristateBool *shortcut,
+                         virBuffer *metadataBuf);
+
+#endif /* ENABLE_VIR_EXTERNAL_DEF_FORMAT_HOOK */
+
+
+[conf/external.generated.c]
+
+int
+virExternalDefFormatBuf(virBuffer *buf,
+                        const char *name,
+                        const virExternalDef *def,
+                        const void *parent G_GNUC_UNUSED,
+                        void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) metadataBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_EXTERNAL_DEF_FORMAT_HOOK
+    if (virExternalDefFormatHook(def, parent, opaque, &empty, &shortcut, &metadataBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_EXTERNAL_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(xmlNodeCheck(&def->metadata, def, opaque)))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    virBufferInheritIndent(&metadataBuf, buf);
+    if (!virBufferTouched(&metadataBuf) && xmlNodeCheck(&def->metadata, def, opaque)) {
+        if (xmlNodeFormatBuf(&metadataBuf, "metadata", &def->metadata, def, opaque) < 0)
+            return -1;
+    }
+    virBufferAddBuffer(buf, &metadataBuf);
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/external.generated.h]
+
+bool
+virExternalDefCheck(const virExternalDef *def,
+                    const void *parent,
+                    void *opaque);
+
+
+[conf/external.generated.c]
+
+#ifndef RESET_VIR_EXTERNAL_DEF_CHECK
+
+bool
+virExternalDefCheck(const virExternalDef *def,
+                    const void *parent G_GNUC_UNUSED,
+                    void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return xmlNodeCheck(&def->metadata, def, opaque);
+}
+
+#endif /* RESET_VIR_EXTERNAL_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/external.h" */
+/* Makesure "external.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_EXTERNAL_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_EXTERNAL_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_EXTERNAL_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_EXTERNAL_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "external.generated.h"
diff --git a/tests/xmlgenout/genformat-separate.txt b/tests/xmlgenout/genformat-separate.txt
new file mode 100644
index 00000000..90729d2a
--- /dev/null
+++ b/tests/xmlgenout/genformat-separate.txt
@@ -0,0 +1,190 @@
+[conf/genformat-separate.generated.h]
+
+#ifdef ENABLE_VIR_SEPARATE_DEF_FORMAT_ATTR_HOOK
+
+int
+virSeparateDefFormatAttrHook(const virSeparateDef *def,
+                             const void *parent,
+                             const void *opaque,
+                             virBuffer *enableBuf);
+
+#endif /* ENABLE_VIR_SEPARATE_DEF_FORMAT_ATTR_HOOK */
+
+
+[conf/genformat-separate.generated.h]
+
+int
+virSeparateDefFormatAttr(virBuffer *buf,
+                         const virSeparateDef *def,
+                         const void *parent,
+                         void *opaque);
+
+
+[conf/genformat-separate.generated.c]
+
+int
+virSeparateDefFormatAttr(virBuffer *buf,
+                         const virSeparateDef *def,
+                         const void *parent G_GNUC_UNUSED,
+                         void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) enableBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_SEPARATE_DEF_FORMAT_ATTR_HOOK
+    if (virSeparateDefFormatAttrHook(def, parent, opaque, &enableBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_SEPARATE_DEF_FORMAT_ATTR_HOOK */
+
+    virBufferInheritIndent(&enableBuf, buf);
+    if (!virBufferTouched(&enableBuf) && def->enable > 0) {
+        const char *str = virTristateBoolTypeToString(def->enable);
+        if (!str) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown %s type %d"),
+                           "enable", def->enable);
+            return -1;
+        }
+        virBufferAsprintf(&enableBuf, " enable='%s'", str);
+    }
+    virBufferAddBuffer(buf, &enableBuf);
+
+    return 0;
+}
+
+
+[conf/genformat-separate.generated.h]
+
+bool
+virSeparateDefCheckAttr(const virSeparateDef *def,
+                        const void *parent,
+                        void *opaque);
+
+
+[conf/genformat-separate.generated.c]
+
+#ifndef RESET_VIR_SEPARATE_DEF_CHECK_ATTR
+
+bool
+virSeparateDefCheckAttr(const virSeparateDef *def,
+                        const void *parent G_GNUC_UNUSED,
+                        void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->enable > 0;
+}
+
+#endif /* RESET_VIR_SEPARATE_DEF_CHECK_ATTR */
+
+
+[conf/genformat-separate.generated.h]
+
+#ifdef ENABLE_VIR_SEPARATE_DEF_FORMAT_ELEM_HOOK
+
+int
+virSeparateDefFormatElemHook(const virSeparateDef *def,
+                             const void *parent,
+                             const void *opaque,
+                             virBuffer *ipBuf,
+                             virBuffer *txtsBuf);
+
+#endif /* ENABLE_VIR_SEPARATE_DEF_FORMAT_ELEM_HOOK */
+
+
+[conf/genformat-separate.generated.h]
+
+int
+virSeparateDefFormatElem(virBuffer *buf,
+                         const virSeparateDef *def,
+                         const void *parent,
+                         void *opaque);
+
+
+[conf/genformat-separate.generated.c]
+
+int
+virSeparateDefFormatElem(virBuffer *buf,
+                         const virSeparateDef *def,
+                         const void *parent G_GNUC_UNUSED,
+                         void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) ipBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) txtsBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_SEPARATE_DEF_FORMAT_ELEM_HOOK
+    if (virSeparateDefFormatElemHook(def, parent, opaque, &ipBuf, &txtsBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_SEPARATE_DEF_FORMAT_ELEM_HOOK */
+
+    virBufferInheritIndent(&ipBuf, buf);
+    if (!virBufferTouched(&ipBuf) && virSocketAddrCheck(&def->ip, def, opaque)) {
+        if (virSocketAddrFormatBuf(&ipBuf, "ip", &def->ip, def, opaque) < 0)
+            return -1;
+    }
+    virBufferAddBuffer(buf, &ipBuf);
+
+    virBufferInheritIndent(&txtsBuf, buf);
+    if (!virBufferTouched(&txtsBuf) && def->ntxts) {
+        size_t i;
+        for (i = 0; i < def->ntxts; i++) {
+            if (virNetworkDNSTxtDefFormatBuf(buf, "txt", &def->txts[i], def, opaque) < 0)
+                return -1;
+        }
+    }
+    virBufferAddBuffer(buf, &txtsBuf);
+
+    return 0;
+}
+
+
+[conf/genformat-separate.generated.h]
+
+bool
+virSeparateDefCheckElem(const virSeparateDef *def,
+                        const void *parent,
+                        void *opaque);
+
+
+[conf/genformat-separate.generated.c]
+
+#ifndef RESET_VIR_SEPARATE_DEF_CHECK_ELEM
+
+bool
+virSeparateDefCheckElem(const virSeparateDef *def,
+                        const void *parent G_GNUC_UNUSED,
+                        void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return virSocketAddrCheck(&def->ip, def, opaque) || def->ntxts;
+}
+
+#endif /* RESET_VIR_SEPARATE_DEF_CHECK_ELEM */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/genformat-separate.h" */
+/* Makesure "genformat-separate.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_SEPARATE_DEF_FORMAT_ATTR_HOOK */
+/* #define ENABLE_VIR_SEPARATE_DEF_FORMAT_ELEM_HOOK */
+
+/* #define RESET_VIR_SEPARATE_DEF_CHECK_ATTR */
+/* #define RESET_VIR_SEPARATE_DEF_CHECK_ELEM */
+
+/* Makesure below is the bottom line! */
+#include "genformat-separate.generated.h"
diff --git a/tests/xmlgenout/genformat.txt b/tests/xmlgenout/genformat.txt
new file mode 100644
index 00000000..72e4e0c0
--- /dev/null
+++ b/tests/xmlgenout/genformat.txt
@@ -0,0 +1,142 @@
+[conf/genformat.generated.h]
+
+int
+virGenFormatDefFormatBuf(virBuffer *buf,
+                         const char *name,
+                         const virGenFormatDef *def,
+                         const void *parent,
+                         void *opaque);
+
+
+[conf/genformat.generated.h]
+
+#ifdef ENABLE_VIR_GEN_FORMAT_DEF_FORMAT_HOOK
+
+int
+virGenFormatDefFormatHook(const virGenFormatDef *def,
+                          const void *parent,
+                          const void *opaque,
+                          virTristateBool *empty,
+                          virTristateBool *shortcut,
+                          virBuffer *enableBuf,
+                          virBuffer *ipBuf,
+                          virBuffer *txtsBuf);
+
+#endif /* ENABLE_VIR_GEN_FORMAT_DEF_FORMAT_HOOK */
+
+
+[conf/genformat.generated.c]
+
+int
+virGenFormatDefFormatBuf(virBuffer *buf,
+                         const char *name,
+                         const virGenFormatDef *def,
+                         const void *parent G_GNUC_UNUSED,
+                         void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) enableBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) ipBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) txtsBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_GEN_FORMAT_DEF_FORMAT_HOOK
+    if (virGenFormatDefFormatHook(def, parent, opaque, &empty, &shortcut, &enableBuf, &ipBuf, &txtsBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_GEN_FORMAT_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(def->enable > 0 || virSocketAddrCheck(&def->ip, def, opaque) || def->ntxts))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferInheritIndent(&enableBuf, buf);
+    if (!virBufferTouched(&enableBuf) && def->enable > 0) {
+        const char *str = virTristateBoolTypeToString(def->enable);
+        if (!str) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown %s type %d"),
+                           "enable", def->enable);
+            return -1;
+        }
+        virBufferAsprintf(&enableBuf, " enable='%s'", str);
+    }
+    virBufferAddBuffer(buf, &enableBuf);
+
+    if (shortcut != VIR_TRISTATE_BOOL_NO) {
+        if (shortcut || !(virSocketAddrCheck(&def->ip, def, opaque) || def->ntxts)) {
+            virBufferAddLit(buf, "/>\n");
+            return 0;
+        }
+    }
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    virBufferInheritIndent(&ipBuf, buf);
+    if (!virBufferTouched(&ipBuf) && virSocketAddrCheck(&def->ip, def, opaque)) {
+        if (virSocketAddrFormatBuf(&ipBuf, "ip", &def->ip, def, opaque) < 0)
+            return -1;
+    }
+    virBufferAddBuffer(buf, &ipBuf);
+
+    virBufferInheritIndent(&txtsBuf, buf);
+    if (!virBufferTouched(&txtsBuf) && def->ntxts) {
+        size_t i;
+        for (i = 0; i < def->ntxts; i++) {
+            if (virNetworkDNSTxtDefFormatBuf(buf, "txt", &def->txts[i], def, opaque) < 0)
+                return -1;
+        }
+    }
+    virBufferAddBuffer(buf, &txtsBuf);
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/genformat.generated.h]
+
+bool
+virGenFormatDefCheck(const virGenFormatDef *def,
+                     const void *parent,
+                     void *opaque);
+
+
+[conf/genformat.generated.c]
+
+#ifndef RESET_VIR_GEN_FORMAT_DEF_CHECK
+
+bool
+virGenFormatDefCheck(const virGenFormatDef *def,
+                     const void *parent G_GNUC_UNUSED,
+                     void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->enable > 0 || virSocketAddrCheck(&def->ip, def, opaque) || def->ntxts;
+}
+
+#endif /* RESET_VIR_GEN_FORMAT_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/genformat.h" */
+/* Makesure "genformat.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_GEN_FORMAT_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_GEN_FORMAT_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "genformat.generated.h"
diff --git a/tests/xmlgenout/genparse.txt b/tests/xmlgenout/genparse.txt
new file mode 100644
index 00000000..e24762e1
--- /dev/null
+++ b/tests/xmlgenout/genparse.txt
@@ -0,0 +1,154 @@
+[conf/genparse.generated.h]
+
+void
+virGenParseDefClear(virGenParseDef *def);
+
+
+[conf/genparse.generated.c]
+
+void
+virGenParseDefClear(virGenParseDef *def)
+{
+    if (!def)
+        return;
+
+    def->enable = 0;
+
+    virSocketAddrClear(&def->ip);
+
+    if (def->ntxts) {
+        size_t i;
+        for (i = 0; i < def->ntxts; i++)
+            virNetworkDNSTxtDefClear(&def->txts[i]);
+    }
+    g_free(def->txts);
+    def->txts = NULL;
+    def->ntxts = 0;
+}
+
+
+[conf/genparse.generated.h]
+
+int
+virGenParseDefParseXML(xmlNodePtr node,
+                       virGenParseDef *def,
+                       const char *instname,
+                       void *parent,
+                       void *opaque);
+
+
+[conf/genparse.generated.h]
+
+#ifdef ENABLE_VIR_GEN_PARSE_DEF_PARSE_HOOK
+
+int
+virGenParseDefParseHook(xmlNodePtr node,
+                        virGenParseDef *def,
+                        const char *instname,
+                        void *parent,
+                        void *opaque,
+                        const char *enableStr,
+                        xmlNodePtr ipNode,
+                        int nTxtNodes);
+
+#endif
+
+
+[conf/genparse.generated.h]
+
+#ifdef ENABLE_VIR_GEN_PARSE_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virGenParseDefParseXMLSetArgs(xmlNodePtr node,
+                              void *parent,
+                              void **pparent,
+                              void **popaque);
+
+#endif
+
+
+[conf/genparse.generated.c]
+
+int
+virGenParseDefParseXML(xmlNodePtr node,
+                       virGenParseDef *def,
+                       const char *instname G_GNUC_UNUSED,
+                       void *parent G_GNUC_UNUSED,
+                       void *opaque G_GNUC_UNUSED)
+{
+    g_autofree char *enableStr = NULL;
+    xmlNodePtr ipNode = NULL;
+    int nTxtNodes = 0;
+    xmlNodePtr *nodes = NULL;
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_GEN_PARSE_DEF_PARSE_HOOK_SET_ARGS
+    virGenParseDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    enableStr = virXMLPropString(node, "enable");
+    if (enableStr) {
+        if ((def->enable = virTristateBoolTypeFromString(enableStr)) <= 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid '%s' setting '%s' in '%s'"),
+                           "enable", enableStr, instname);
+            goto error;
+        }
+    }
+
+    ipNode = virXMLChildNode(node, "ip");
+    if (ipNode) {
+        if (virSocketAddrParseXML(ipNode, &def->ip, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+    }
+
+    nTxtNodes = virXMLChildNodeSet(node, "txt", &nodes);
+    if (nTxtNodes > 0) {
+        size_t i;
+
+        def->txts = g_new0(typeof(*def->txts), nTxtNodes);
+        for (i = 0; i < nTxtNodes; i++) {
+            xmlNodePtr tnode = nodes[i];
+            if (virNetworkDNSTxtDefParseXML(tnode, &def->txts[i], instname, arg_parent, arg_opaque) < 0)
+                goto error;
+        }
+        def->ntxts = nTxtNodes;
+        g_free(nodes);
+        nodes = NULL;
+    } else if (nTxtNodes < 0) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("Invalid %s element found."),
+                       "txt");
+        goto error;
+    }
+
+#ifdef ENABLE_VIR_GEN_PARSE_DEF_PARSE_HOOK
+    if (virGenParseDefParseHook(node, def, instname, parent, opaque, enableStr, ipNode, nTxtNodes) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    g_free(nodes);
+    nodes = NULL;
+    virGenParseDefClear(def);
+    return -1;
+}
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/genparse.h" */
+/* Makesure "genparse.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_GEN_PARSE_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_GEN_PARSE_DEF_PARSE_HOOK_SET_ARGS */
+
+/* Makesure below is the bottom line! */
+#include "genparse.generated.h"
diff --git a/tests/xmlgenout/namespace.txt b/tests/xmlgenout/namespace.txt
new file mode 100644
index 00000000..df1660dd
--- /dev/null
+++ b/tests/xmlgenout/namespace.txt
@@ -0,0 +1,222 @@
+[conf/namespace.generated.h]
+
+void
+virNameSpaceDefClear(virNameSpaceDef *def);
+
+
+[conf/namespace.generated.c]
+
+void
+virNameSpaceDefClear(virNameSpaceDef *def)
+{
+    if (!def)
+        return;
+
+    g_free(def->name);
+    def->name = NULL;
+
+    if (def->namespaceData && def->ns.free)
+        (def->ns.free)(def->namespaceData);
+}
+
+
+[conf/namespace.generated.h]
+
+int
+virNameSpaceDefParseXML(xmlNodePtr node,
+                        virNameSpaceDef *def,
+                        const char *instname,
+                        void *parent,
+                        void *opaque,
+                        xmlXPathContextPtr ctxt,
+                        virNetworkXMLOption *xmlopt);
+
+
+[conf/namespace.generated.h]
+
+#ifdef ENABLE_VIR_NAME_SPACE_DEF_PARSE_HOOK
+
+int
+virNameSpaceDefParseHook(xmlNodePtr node,
+                         virNameSpaceDef *def,
+                         const char *instname,
+                         void *parent,
+                         void *opaque,
+                         xmlXPathContextPtr ctxt,
+                         virNetworkXMLOption *xmlopt);
+
+#endif
+
+
+[conf/namespace.generated.h]
+
+#ifdef ENABLE_VIR_NAME_SPACE_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virNameSpaceDefParseXMLSetArgs(xmlNodePtr node,
+                               void *parent,
+                               void **pparent,
+                               void **popaque);
+
+#endif
+
+
+[conf/namespace.generated.c]
+
+int
+virNameSpaceDefParseXML(xmlNodePtr node,
+                        virNameSpaceDef *def,
+                        const char *instname G_GNUC_UNUSED,
+                        void *parent G_GNUC_UNUSED,
+                        void *opaque G_GNUC_UNUSED,
+                        xmlXPathContextPtr ctxt,
+                        virNetworkXMLOption *xmlopt)
+{
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_NAME_SPACE_DEF_PARSE_HOOK_SET_ARGS
+    virNameSpaceDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    def->name = virXMLChildNodeContent(node, "name");
+
+#ifdef ENABLE_VIR_NAME_SPACE_DEF_PARSE_HOOK
+    if (virNameSpaceDefParseHook(node, def, instname, parent, opaque, ctxt, xmlopt) < 0)
+        goto error;
+#endif
+
+    if (xmlopt)
+        def->ns = xmlopt->ns;
+    if (def->ns.parse) {
+        if (virXMLNamespaceRegister(ctxt, &def->ns) < 0)
+            goto error;
+        if ((def->ns.parse)(ctxt, &def->namespaceData) < 0)
+            goto error;
+    }
+    return 0;
+
+ error:
+    virNameSpaceDefClear(def);
+    return -1;
+}
+
+
+[conf/namespace.generated.h]
+
+int
+virNameSpaceDefFormatBuf(virBuffer *buf,
+                         const char *name,
+                         const virNameSpaceDef *def,
+                         const void *parent,
+                         void *opaque);
+
+
+[conf/namespace.generated.h]
+
+#ifdef ENABLE_VIR_NAME_SPACE_DEF_FORMAT_HOOK
+
+int
+virNameSpaceDefFormatHook(const virNameSpaceDef *def,
+                          const void *parent,
+                          const void *opaque,
+                          virTristateBool *empty,
+                          virTristateBool *shortcut,
+                          virBuffer *nameBuf);
+
+#endif /* ENABLE_VIR_NAME_SPACE_DEF_FORMAT_HOOK */
+
+
+[conf/namespace.generated.c]
+
+int
+virNameSpaceDefFormatBuf(virBuffer *buf,
+                         const char *name,
+                         const virNameSpaceDef *def,
+                         const void *parent G_GNUC_UNUSED,
+                         void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) nameBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_NAME_SPACE_DEF_FORMAT_HOOK
+    if (virNameSpaceDefFormatHook(def, parent, opaque, &empty, &shortcut, &nameBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_NAME_SPACE_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(def->name))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    if (def->namespaceData && def->ns.format)
+        virXMLNamespaceFormatNS(buf, &def->ns);
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    virBufferInheritIndent(&nameBuf, buf);
+    if (!virBufferTouched(&nameBuf) && def->name)
+        virBufferEscapeString(&nameBuf, "<name>%s</name>\n", def->name);
+    virBufferAddBuffer(buf, &nameBuf);
+
+    if (def->namespaceData && def->ns.format) {
+        if ((def->ns.format)(buf, def->namespaceData) < 0)
+            return -1;
+    }
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/namespace.generated.h]
+
+bool
+virNameSpaceDefCheck(const virNameSpaceDef *def,
+                     const void *parent,
+                     void *opaque);
+
+
+[conf/namespace.generated.c]
+
+#ifndef RESET_VIR_NAME_SPACE_DEF_CHECK
+
+bool
+virNameSpaceDefCheck(const virNameSpaceDef *def,
+                     const void *parent G_GNUC_UNUSED,
+                     void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->name;
+}
+
+#endif /* RESET_VIR_NAME_SPACE_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/namespace.h" */
+/* Makesure "namespace.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_NAME_SPACE_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_NAME_SPACE_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_NAME_SPACE_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_NAME_SPACE_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "namespace.generated.h"
diff --git a/tests/xmlgenout/required.txt b/tests/xmlgenout/required.txt
new file mode 100644
index 00000000..df509284
--- /dev/null
+++ b/tests/xmlgenout/required.txt
@@ -0,0 +1,236 @@
+[conf/required.generated.h]
+
+void
+virRequiredDefClear(virRequiredDef *def);
+
+
+[conf/required.generated.c]
+
+void
+virRequiredDefClear(virRequiredDef *def)
+{
+    if (!def)
+        return;
+
+    g_free(def->name);
+    def->name = NULL;
+
+    virNetworkBootpDefClear(&def->bootp);
+}
+
+
+[conf/required.generated.h]
+
+int
+virRequiredDefParseXML(xmlNodePtr node,
+                       virRequiredDef *def,
+                       const char *instname,
+                       void *parent,
+                       void *opaque);
+
+
+[conf/required.generated.h]
+
+#ifdef ENABLE_VIR_REQUIRED_DEF_PARSE_HOOK
+
+int
+virRequiredDefParseHook(xmlNodePtr node,
+                        virRequiredDef *def,
+                        const char *instname,
+                        void *parent,
+                        void *opaque,
+                        xmlNodePtr bootpNode);
+
+#endif
+
+
+[conf/required.generated.h]
+
+#ifdef ENABLE_VIR_REQUIRED_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virRequiredDefParseXMLSetArgs(xmlNodePtr node,
+                              void *parent,
+                              void **pparent,
+                              void **popaque);
+
+#endif
+
+
+[conf/required.generated.c]
+
+int
+virRequiredDefParseXML(xmlNodePtr node,
+                       virRequiredDef *def,
+                       const char *instname G_GNUC_UNUSED,
+                       void *parent G_GNUC_UNUSED,
+                       void *opaque G_GNUC_UNUSED)
+{
+    xmlNodePtr bootpNode = NULL;
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_REQUIRED_DEF_PARSE_HOOK_SET_ARGS
+    virRequiredDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    def->name = virXMLPropString(node, "name");
+    if (def->name == NULL) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("Missing '%s' setting in '%s'"),
+                       "name", instname);
+        goto error;
+    }
+
+    bootpNode = virXMLChildNode(node, "bootp");
+    if (bootpNode == NULL) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("Missing '%s' setting in '%s'"),
+                       "bootp", instname);
+        goto error;
+    }
+    if (bootpNode) {
+        if (virNetworkBootpDefParseXML(bootpNode, &def->bootp, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+    }
+
+#ifdef ENABLE_VIR_REQUIRED_DEF_PARSE_HOOK
+    if (virRequiredDefParseHook(node, def, instname, parent, opaque, bootpNode) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virRequiredDefClear(def);
+    return -1;
+}
+
+
+[conf/required.generated.h]
+
+int
+virRequiredDefFormatBuf(virBuffer *buf,
+                        const char *name,
+                        const virRequiredDef *def,
+                        const void *parent,
+                        void *opaque);
+
+
+[conf/required.generated.h]
+
+#ifdef ENABLE_VIR_REQUIRED_DEF_FORMAT_HOOK
+
+int
+virRequiredDefFormatHook(const virRequiredDef *def,
+                         const void *parent,
+                         const void *opaque,
+                         virTristateBool *empty,
+                         virTristateBool *shortcut,
+                         virBuffer *nameBuf,
+                         virBuffer *bootpBuf);
+
+#endif /* ENABLE_VIR_REQUIRED_DEF_FORMAT_HOOK */
+
+
+[conf/required.generated.c]
+
+int
+virRequiredDefFormatBuf(virBuffer *buf,
+                        const char *name,
+                        const virRequiredDef *def,
+                        const void *parent G_GNUC_UNUSED,
+                        void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) nameBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) bootpBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_REQUIRED_DEF_FORMAT_HOOK
+    if (virRequiredDefFormatHook(def, parent, opaque, &empty, &shortcut, &nameBuf, &bootpBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_REQUIRED_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(def->name || virNetworkBootpDefCheck(&def->bootp, def, opaque)))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferInheritIndent(&nameBuf, buf);
+    if (!virBufferTouched(&nameBuf))
+        virBufferEscapeString(&nameBuf, " name='%s'", def->name);
+    virBufferAddBuffer(buf, &nameBuf);
+
+    if (shortcut != VIR_TRISTATE_BOOL_NO) {
+        if (shortcut || !(virNetworkBootpDefCheck(&def->bootp, def, opaque))) {
+            virBufferAddLit(buf, "/>\n");
+            return 0;
+        }
+    }
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    virBufferInheritIndent(&bootpBuf, buf);
+    if (!virBufferTouched(&bootpBuf)) {
+        if (virNetworkBootpDefFormatBuf(&bootpBuf, "bootp", &def->bootp, def, opaque) < 0)
+            return -1;
+    }
+    virBufferAddBuffer(buf, &bootpBuf);
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/required.generated.h]
+
+bool
+virRequiredDefCheck(const virRequiredDef *def,
+                    const void *parent,
+                    void *opaque);
+
+
+[conf/required.generated.c]
+
+#ifndef RESET_VIR_REQUIRED_DEF_CHECK
+
+bool
+virRequiredDefCheck(const virRequiredDef *def,
+                    const void *parent G_GNUC_UNUSED,
+                    void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->name || virNetworkBootpDefCheck(&def->bootp, def, opaque);
+}
+
+#endif /* RESET_VIR_REQUIRED_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/required.h" */
+/* Makesure "required.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_REQUIRED_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_REQUIRED_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_REQUIRED_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_REQUIRED_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "required.generated.h"
diff --git a/tests/xmlgenout/skipparse.txt b/tests/xmlgenout/skipparse.txt
new file mode 100644
index 00000000..b6f6546a
--- /dev/null
+++ b/tests/xmlgenout/skipparse.txt
@@ -0,0 +1,223 @@
+[conf/skipparse.generated.h]
+
+void
+virSkipParseDefClear(virSkipParseDef *def);
+
+
+[conf/skipparse.generated.c]
+
+void
+virSkipParseDefClear(virSkipParseDef *def)
+{
+    if (!def)
+        return;
+
+    def->connections = 0;
+
+    if (def->nifs) {
+        size_t i;
+        for (i = 0; i < def->nifs; i++)
+            virNetworkForwardIfDefClear(&def->ifs[i]);
+    }
+    g_free(def->ifs);
+    def->ifs = NULL;
+    def->nifs = 0;
+}
+
+
+[conf/skipparse.generated.h]
+
+int
+virSkipParseDefParseXML(xmlNodePtr node,
+                        virSkipParseDef *def,
+                        const char *instname,
+                        void *parent,
+                        void *opaque);
+
+
+[conf/skipparse.generated.h]
+
+#ifdef ENABLE_VIR_SKIP_PARSE_DEF_PARSE_HOOK
+
+int
+virSkipParseDefParseHook(xmlNodePtr node,
+                         virSkipParseDef *def,
+                         const char *instname,
+                         void *parent,
+                         void *opaque);
+
+#endif
+
+
+[conf/skipparse.generated.h]
+
+#ifdef ENABLE_VIR_SKIP_PARSE_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virSkipParseDefParseXMLSetArgs(xmlNodePtr node,
+                               void *parent,
+                               void **pparent,
+                               void **popaque);
+
+#endif
+
+
+[conf/skipparse.generated.c]
+
+int
+virSkipParseDefParseXML(xmlNodePtr node,
+                        virSkipParseDef *def,
+                        const char *instname G_GNUC_UNUSED,
+                        void *parent G_GNUC_UNUSED,
+                        void *opaque G_GNUC_UNUSED)
+{
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_SKIP_PARSE_DEF_PARSE_HOOK_SET_ARGS
+    virSkipParseDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+#ifdef ENABLE_VIR_SKIP_PARSE_DEF_PARSE_HOOK
+    if (virSkipParseDefParseHook(node, def, instname, parent, opaque) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virSkipParseDefClear(def);
+    return -1;
+}
+
+
+[conf/skipparse.generated.h]
+
+int
+virSkipParseDefFormatBuf(virBuffer *buf,
+                         const char *name,
+                         const virSkipParseDef *def,
+                         const void *parent,
+                         void *opaque);
+
+
+[conf/skipparse.generated.h]
+
+#ifdef ENABLE_VIR_SKIP_PARSE_DEF_FORMAT_HOOK
+
+int
+virSkipParseDefFormatHook(const virSkipParseDef *def,
+                          const void *parent,
+                          const void *opaque,
+                          virTristateBool *empty,
+                          virTristateBool *shortcut,
+                          virBuffer *connectionsBuf,
+                          virBuffer *ifsBuf);
+
+#endif /* ENABLE_VIR_SKIP_PARSE_DEF_FORMAT_HOOK */
+
+
+[conf/skipparse.generated.c]
+
+int
+virSkipParseDefFormatBuf(virBuffer *buf,
+                         const char *name,
+                         const virSkipParseDef *def,
+                         const void *parent G_GNUC_UNUSED,
+                         void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) connectionsBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) ifsBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_SKIP_PARSE_DEF_FORMAT_HOOK
+    if (virSkipParseDefFormatHook(def, parent, opaque, &empty, &shortcut, &connectionsBuf, &ifsBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_SKIP_PARSE_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(def->connections || def->nifs))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferInheritIndent(&connectionsBuf, buf);
+    if (!virBufferTouched(&connectionsBuf) && def->connections)
+        virBufferAsprintf(&connectionsBuf, " connections='%d'", def->connections);
+    virBufferAddBuffer(buf, &connectionsBuf);
+
+    if (shortcut != VIR_TRISTATE_BOOL_NO) {
+        if (shortcut || !(def->nifs)) {
+            virBufferAddLit(buf, "/>\n");
+            return 0;
+        }
+    }
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    virBufferInheritIndent(&ifsBuf, buf);
+    if (!virBufferTouched(&ifsBuf) && def->nifs) {
+        size_t i;
+        for (i = 0; i < def->nifs; i++) {
+            if (virNetworkForwardIfDefFormatBuf(buf, "if", &def->ifs[i], def, opaque) < 0)
+                return -1;
+        }
+    }
+    virBufferAddBuffer(buf, &ifsBuf);
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/skipparse.generated.h]
+
+bool
+virSkipParseDefCheck(const virSkipParseDef *def,
+                     const void *parent,
+                     void *opaque);
+
+
+[conf/skipparse.generated.c]
+
+#ifndef RESET_VIR_SKIP_PARSE_DEF_CHECK
+
+bool
+virSkipParseDefCheck(const virSkipParseDef *def,
+                     const void *parent G_GNUC_UNUSED,
+                     void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->connections || def->nifs;
+}
+
+#endif /* RESET_VIR_SKIP_PARSE_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/skipparse.h" */
+/* Makesure "skipparse.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_SKIP_PARSE_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_SKIP_PARSE_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_SKIP_PARSE_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_SKIP_PARSE_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "skipparse.generated.h"
diff --git a/tests/xmlgenout/specify.txt b/tests/xmlgenout/specify.txt
new file mode 100644
index 00000000..dd6dac9c
--- /dev/null
+++ b/tests/xmlgenout/specify.txt
@@ -0,0 +1,291 @@
+[conf/specify.generated.h]
+
+void
+virSpecifyDefClear(virSpecifyDef *def);
+
+
+[conf/specify.generated.c]
+
+void
+virSpecifyDefClear(virSpecifyDef *def)
+{
+    if (!def)
+        return;
+
+    def->unit = 0;
+
+    def->expiry = 0;
+
+    virMacAddrClear(&def->mac);
+    def->mac_specified = false;
+
+    virUUIDClear(&def->uuid);
+    def->uuid_specified = false;
+}
+
+
+[conf/specify.generated.h]
+
+int
+virSpecifyDefParseXML(xmlNodePtr node,
+                      virSpecifyDef *def,
+                      const char *instname,
+                      void *parent,
+                      void *opaque);
+
+
+[conf/specify.generated.h]
+
+#ifdef ENABLE_VIR_SPECIFY_DEF_PARSE_HOOK
+
+int
+virSpecifyDefParseHook(xmlNodePtr node,
+                       virSpecifyDef *def,
+                       const char *instname,
+                       void *parent,
+                       void *opaque,
+                       const char *unitStr,
+                       const char *expiryStr,
+                       const char *macStr,
+                       const char *uuidStr);
+
+#endif
+
+
+[conf/specify.generated.h]
+
+#ifdef ENABLE_VIR_SPECIFY_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virSpecifyDefParseXMLSetArgs(xmlNodePtr node,
+                             void *parent,
+                             void **pparent,
+                             void **popaque);
+
+#endif
+
+
+[conf/specify.generated.c]
+
+int
+virSpecifyDefParseXML(xmlNodePtr node,
+                      virSpecifyDef *def,
+                      const char *instname G_GNUC_UNUSED,
+                      void *parent G_GNUC_UNUSED,
+                      void *opaque G_GNUC_UNUSED)
+{
+    g_autofree char *unitStr = NULL;
+    g_autofree char *expiryStr = NULL;
+    g_autofree char *macStr = NULL;
+    g_autofree char *uuidStr = NULL;
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_SPECIFY_DEF_PARSE_HOOK_SET_ARGS
+    virSpecifyDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    unitStr = virXMLPropString(node, "unit");
+    if (unitStr) {
+        if ((def->unit = virNetworkDHCPLeaseTimeUnitTypeFromString(unitStr)) < 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid '%s' setting '%s' in '%s'"),
+                           "unit", unitStr, instname);
+            goto error;
+        }
+    }
+
+    expiryStr = virXMLPropString(node, "expiry");
+    if (expiryStr) {
+        if (virStrToLong_ulp(expiryStr, NULL, 0, &def->expiry) < 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid '%s' setting '%s' in '%s'"),
+                           "expiry", expiryStr, instname);
+            goto error;
+        }
+    }
+
+    macStr = virXMLChildPropString(node, "mac/address");
+    if (macStr) {
+        if (virMacAddrParseXML(macStr, &def->mac, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+        def->mac_specified = true;
+    }
+
+    uuidStr = virXMLChildNodeContent(node, "uuid");
+    if (uuidStr) {
+        if (virUUIDParseXML(uuidStr, &def->uuid, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+        def->uuid_specified = true;
+    }
+
+#ifdef ENABLE_VIR_SPECIFY_DEF_PARSE_HOOK
+    if (virSpecifyDefParseHook(node, def, instname, parent, opaque, unitStr, expiryStr, macStr, uuidStr) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virSpecifyDefClear(def);
+    return -1;
+}
+
+
+[conf/specify.generated.h]
+
+int
+virSpecifyDefFormatBuf(virBuffer *buf,
+                       const char *name,
+                       const virSpecifyDef *def,
+                       const void *parent,
+                       void *opaque);
+
+
+[conf/specify.generated.h]
+
+#ifdef ENABLE_VIR_SPECIFY_DEF_FORMAT_HOOK
+
+int
+virSpecifyDefFormatHook(const virSpecifyDef *def,
+                        const void *parent,
+                        const void *opaque,
+                        virTristateBool *empty,
+                        virTristateBool *shortcut,
+                        virBuffer *unitBuf,
+                        virBuffer *expiryBuf,
+                        virBuffer *mac_addressBuf,
+                        virBuffer *uuidBuf);
+
+#endif /* ENABLE_VIR_SPECIFY_DEF_FORMAT_HOOK */
+
+
+[conf/specify.generated.c]
+
+int
+virSpecifyDefFormatBuf(virBuffer *buf,
+                       const char *name,
+                       const virSpecifyDef *def,
+                       const void *parent G_GNUC_UNUSED,
+                       void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) unitBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) expiryBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) mac_addressBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) uuidBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_SPECIFY_DEF_FORMAT_HOOK
+    if (virSpecifyDefFormatHook(def, parent, opaque, &empty, &shortcut, &unitBuf, &expiryBuf, &mac_addressBuf, &uuidBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_SPECIFY_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(def->expiry || def->uuid_specified || def->mac_specified))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferInheritIndent(&unitBuf, buf);
+    if (!virBufferTouched(&unitBuf) && def->expiry) {
+        const char *str = virNetworkDHCPLeaseTimeUnitTypeToString(def->unit);
+        if (!str) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown %s type %d"),
+                           "unit", def->unit);
+            return -1;
+        }
+        virBufferAsprintf(&unitBuf, " unit='%s'", str);
+    }
+    virBufferAddBuffer(buf, &unitBuf);
+
+    virBufferInheritIndent(&expiryBuf, buf);
+    if (!virBufferTouched(&expiryBuf) && def->expiry)
+        virBufferAsprintf(&expiryBuf, " expiry='%lu'", def->expiry);
+    virBufferAddBuffer(buf, &expiryBuf);
+
+    if (shortcut != VIR_TRISTATE_BOOL_NO) {
+        if (shortcut || !(def->uuid_specified || def->mac_specified)) {
+            virBufferAddLit(buf, "/>\n");
+            return 0;
+        }
+    }
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    if (virBufferUse(&mac_addressBuf) || def->mac_specified) {
+        virBufferAddLit(buf, "<mac");
+
+        virBufferInheritIndent(&mac_addressBuf, buf);
+        if (!virBufferTouched(&mac_addressBuf) && def->mac_specified) {
+            if (virMacAddrFormatBuf(&mac_addressBuf, " address='%s'", &def->mac, def, opaque) < 0)
+                return -1;
+        }
+        virBufferAddBuffer(buf, &mac_addressBuf);
+
+        virBufferAddLit(buf, "/>\n");
+    }
+
+    virBufferInheritIndent(&uuidBuf, buf);
+    if (!virBufferTouched(&uuidBuf) && def->uuid_specified) {
+        if (virUUIDFormatBuf(&uuidBuf, "<uuid>%s</uuid>\n", &def->uuid, def, opaque) < 0)
+            return -1;
+    }
+    virBufferAddBuffer(buf, &uuidBuf);
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/specify.generated.h]
+
+bool
+virSpecifyDefCheck(const virSpecifyDef *def,
+                   const void *parent,
+                   void *opaque);
+
+
+[conf/specify.generated.c]
+
+#ifndef RESET_VIR_SPECIFY_DEF_CHECK
+
+bool
+virSpecifyDefCheck(const virSpecifyDef *def,
+                   const void *parent G_GNUC_UNUSED,
+                   void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->expiry || def->uuid_specified || def->mac_specified;
+}
+
+#endif /* RESET_VIR_SPECIFY_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/specify.h" */
+/* Makesure "specify.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_SPECIFY_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_SPECIFY_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_SPECIFY_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_SPECIFY_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "specify.generated.h"
diff --git a/tests/xmlgenout/xmlattr.txt b/tests/xmlgenout/xmlattr.txt
new file mode 100644
index 00000000..228eb3cc
--- /dev/null
+++ b/tests/xmlgenout/xmlattr.txt
@@ -0,0 +1,252 @@
+[conf/xmlattr.generated.h]
+
+void
+virXMLAttrDefClear(virXMLAttrDef *def);
+
+
+[conf/xmlattr.generated.c]
+
+void
+virXMLAttrDefClear(virXMLAttrDef *def)
+{
+    if (!def)
+        return;
+
+    g_free(def->family);
+    def->family = NULL;
+
+    def->localPTR = 0;
+
+    g_free(def->tftproot);
+    def->tftproot = NULL;
+}
+
+
+[conf/xmlattr.generated.h]
+
+int
+virXMLAttrDefParseXML(xmlNodePtr node,
+                      virXMLAttrDef *def,
+                      const char *instname,
+                      void *parent,
+                      void *opaque);
+
+
+[conf/xmlattr.generated.h]
+
+#ifdef ENABLE_VIR_XMLATTR_DEF_PARSE_HOOK
+
+int
+virXMLAttrDefParseHook(xmlNodePtr node,
+                       virXMLAttrDef *def,
+                       const char *instname,
+                       void *parent,
+                       void *opaque,
+                       const char *localPTRStr);
+
+#endif
+
+
+[conf/xmlattr.generated.h]
+
+#ifdef ENABLE_VIR_XMLATTR_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virXMLAttrDefParseXMLSetArgs(xmlNodePtr node,
+                             void *parent,
+                             void **pparent,
+                             void **popaque);
+
+#endif
+
+
+[conf/xmlattr.generated.c]
+
+int
+virXMLAttrDefParseXML(xmlNodePtr node,
+                      virXMLAttrDef *def,
+                      const char *instname G_GNUC_UNUSED,
+                      void *parent G_GNUC_UNUSED,
+                      void *opaque G_GNUC_UNUSED)
+{
+    g_autofree char *localPTRStr = NULL;
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_XMLATTR_DEF_PARSE_HOOK_SET_ARGS
+    virXMLAttrDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    def->family = virXMLPropString(node, "family");
+
+    localPTRStr = virXMLPropString(node, "localPtr");
+    if (localPTRStr) {
+        if ((def->localPTR = virTristateBoolTypeFromString(localPTRStr)) <= 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid '%s' setting '%s' in '%s'"),
+                           "localPtr", localPTRStr, instname);
+            goto error;
+        }
+    }
+
+    def->tftproot = virXMLChildPropString(node, "tftp/root");
+
+#ifdef ENABLE_VIR_XMLATTR_DEF_PARSE_HOOK
+    if (virXMLAttrDefParseHook(node, def, instname, parent, opaque, localPTRStr) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virXMLAttrDefClear(def);
+    return -1;
+}
+
+
+[conf/xmlattr.generated.h]
+
+int
+virXMLAttrDefFormatBuf(virBuffer *buf,
+                       const char *name,
+                       const virXMLAttrDef *def,
+                       const void *parent,
+                       void *opaque);
+
+
+[conf/xmlattr.generated.h]
+
+#ifdef ENABLE_VIR_XMLATTR_DEF_FORMAT_HOOK
+
+int
+virXMLAttrDefFormatHook(const virXMLAttrDef *def,
+                        const void *parent,
+                        const void *opaque,
+                        virTristateBool *empty,
+                        virTristateBool *shortcut,
+                        virBuffer *familyBuf,
+                        virBuffer *localPTRBuf,
+                        virBuffer *tftp_rootBuf);
+
+#endif /* ENABLE_VIR_XMLATTR_DEF_FORMAT_HOOK */
+
+
+[conf/xmlattr.generated.c]
+
+int
+virXMLAttrDefFormatBuf(virBuffer *buf,
+                       const char *name,
+                       const virXMLAttrDef *def,
+                       const void *parent G_GNUC_UNUSED,
+                       void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) familyBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) localPTRBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) tftp_rootBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_XMLATTR_DEF_FORMAT_HOOK
+    if (virXMLAttrDefFormatHook(def, parent, opaque, &empty, &shortcut, &familyBuf, &localPTRBuf, &tftp_rootBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_XMLATTR_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(def->family || def->localPTR > 0 || def->tftproot))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferInheritIndent(&familyBuf, buf);
+    if (!virBufferTouched(&familyBuf) && def->family)
+        virBufferEscapeString(&familyBuf, " family='%s'", def->family);
+    virBufferAddBuffer(buf, &familyBuf);
+
+    virBufferInheritIndent(&localPTRBuf, buf);
+    if (!virBufferTouched(&localPTRBuf) && def->localPTR > 0) {
+        const char *str = virTristateBoolTypeToString(def->localPTR);
+        if (!str) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown %s type %d"),
+                           "localPtr", def->localPTR);
+            return -1;
+        }
+        virBufferAsprintf(&localPTRBuf, " localPtr='%s'", str);
+    }
+    virBufferAddBuffer(buf, &localPTRBuf);
+
+    if (shortcut != VIR_TRISTATE_BOOL_NO) {
+        if (shortcut || !(def->tftproot)) {
+            virBufferAddLit(buf, "/>\n");
+            return 0;
+        }
+    }
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    if (virBufferUse(&tftp_rootBuf) || def->tftproot) {
+        virBufferAddLit(buf, "<tftp");
+
+        virBufferInheritIndent(&tftp_rootBuf, buf);
+        if (!virBufferTouched(&tftp_rootBuf) && def->tftproot)
+            virBufferEscapeString(&tftp_rootBuf, " root='%s'", def->tftproot);
+        virBufferAddBuffer(buf, &tftp_rootBuf);
+
+        virBufferAddLit(buf, "/>\n");
+    }
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/xmlattr.generated.h]
+
+bool
+virXMLAttrDefCheck(const virXMLAttrDef *def,
+                   const void *parent,
+                   void *opaque);
+
+
+[conf/xmlattr.generated.c]
+
+#ifndef RESET_VIR_XMLATTR_DEF_CHECK
+
+bool
+virXMLAttrDefCheck(const virXMLAttrDef *def,
+                   const void *parent G_GNUC_UNUSED,
+                   void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->family || def->localPTR > 0 || def->tftproot;
+}
+
+#endif /* RESET_VIR_XMLATTR_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/xmlattr.h" */
+/* Makesure "xmlattr.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_XMLATTR_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_XMLATTR_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_XMLATTR_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_XMLATTR_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "xmlattr.generated.h"
diff --git a/tests/xmlgenout/xmlelem.txt b/tests/xmlgenout/xmlelem.txt
new file mode 100644
index 00000000..d910eda2
--- /dev/null
+++ b/tests/xmlgenout/xmlelem.txt
@@ -0,0 +1,243 @@
+[conf/xmlelem.generated.h]
+
+void
+virXMLElemDefClear(virXMLElemDef *def);
+
+
+[conf/xmlelem.generated.c]
+
+void
+virXMLElemDefClear(virXMLElemDef *def)
+{
+    if (!def)
+        return;
+
+    virPortRangeClear(&def->port);
+
+    virSocketAddrRangeClear(&def->addr);
+
+    virUUIDClear(&def->uuid);
+}
+
+
+[conf/xmlelem.generated.h]
+
+int
+virXMLElemDefParseXML(xmlNodePtr node,
+                      virXMLElemDef *def,
+                      const char *instname,
+                      void *parent,
+                      void *opaque);
+
+
+[conf/xmlelem.generated.h]
+
+#ifdef ENABLE_VIR_XMLELEM_DEF_PARSE_HOOK
+
+int
+virXMLElemDefParseHook(xmlNodePtr node,
+                       virXMLElemDef *def,
+                       const char *instname,
+                       void *parent,
+                       void *opaque,
+                       xmlNodePtr portNode,
+                       xmlNodePtr addrNode,
+                       const char *uuidStr);
+
+#endif
+
+
+[conf/xmlelem.generated.h]
+
+#ifdef ENABLE_VIR_XMLELEM_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virXMLElemDefParseXMLSetArgs(xmlNodePtr node,
+                             void *parent,
+                             void **pparent,
+                             void **popaque);
+
+#endif
+
+
+[conf/xmlelem.generated.c]
+
+int
+virXMLElemDefParseXML(xmlNodePtr node,
+                      virXMLElemDef *def,
+                      const char *instname G_GNUC_UNUSED,
+                      void *parent G_GNUC_UNUSED,
+                      void *opaque G_GNUC_UNUSED)
+{
+    xmlNodePtr portNode = NULL;
+    xmlNodePtr addrNode = NULL;
+    g_autofree char *uuidStr = NULL;
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_XMLELEM_DEF_PARSE_HOOK_SET_ARGS
+    virXMLElemDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    portNode = virXMLChildNode(node, "port");
+    if (portNode) {
+        if (virPortRangeParseXML(portNode, &def->port, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+    }
+
+    addrNode = virXMLChildNode(node, "address");
+    if (addrNode) {
+        if (virSocketAddrRangeParseXML(addrNode, &def->addr, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+    }
+
+    uuidStr = virXMLChildNodeContent(node, "uuid");
+    if (uuidStr) {
+        if (virUUIDParseXML(uuidStr, &def->uuid, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+    }
+
+#ifdef ENABLE_VIR_XMLELEM_DEF_PARSE_HOOK
+    if (virXMLElemDefParseHook(node, def, instname, parent, opaque, portNode, addrNode, uuidStr) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virXMLElemDefClear(def);
+    return -1;
+}
+
+
+[conf/xmlelem.generated.h]
+
+int
+virXMLElemDefFormatBuf(virBuffer *buf,
+                       const char *name,
+                       const virXMLElemDef *def,
+                       const void *parent,
+                       void *opaque);
+
+
+[conf/xmlelem.generated.h]
+
+#ifdef ENABLE_VIR_XMLELEM_DEF_FORMAT_HOOK
+
+int
+virXMLElemDefFormatHook(const virXMLElemDef *def,
+                        const void *parent,
+                        const void *opaque,
+                        virTristateBool *empty,
+                        virTristateBool *shortcut,
+                        virBuffer *portBuf,
+                        virBuffer *addrBuf,
+                        virBuffer *uuidBuf);
+
+#endif /* ENABLE_VIR_XMLELEM_DEF_FORMAT_HOOK */
+
+
+[conf/xmlelem.generated.c]
+
+int
+virXMLElemDefFormatBuf(virBuffer *buf,
+                       const char *name,
+                       const virXMLElemDef *def,
+                       const void *parent G_GNUC_UNUSED,
+                       void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) portBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) addrBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) uuidBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_XMLELEM_DEF_FORMAT_HOOK
+    if (virXMLElemDefFormatHook(def, parent, opaque, &empty, &shortcut, &portBuf, &addrBuf, &uuidBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_XMLELEM_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(virPortRangeCheck(&def->port, def, opaque) || virSocketAddrRangeCheck(&def->addr, def, opaque) || virUUIDCheck(&def->uuid, def, opaque)))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    virBufferInheritIndent(&portBuf, buf);
+    if (!virBufferTouched(&portBuf) && virPortRangeCheck(&def->port, def, opaque)) {
+        if (virPortRangeFormatBuf(&portBuf, "port", &def->port, def, opaque) < 0)
+            return -1;
+    }
+    virBufferAddBuffer(buf, &portBuf);
+
+    virBufferInheritIndent(&addrBuf, buf);
+    if (!virBufferTouched(&addrBuf) && virSocketAddrRangeCheck(&def->addr, def, opaque)) {
+        if (virSocketAddrRangeFormatBuf(&addrBuf, "address", &def->addr, def, opaque) < 0)
+            return -1;
+    }
+    virBufferAddBuffer(buf, &addrBuf);
+
+    virBufferInheritIndent(&uuidBuf, buf);
+    if (!virBufferTouched(&uuidBuf) && virUUIDCheck(&def->uuid, def, opaque)) {
+        if (virUUIDFormatBuf(&uuidBuf, "<uuid>%s</uuid>\n", &def->uuid, def, opaque) < 0)
+            return -1;
+    }
+    virBufferAddBuffer(buf, &uuidBuf);
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/xmlelem.generated.h]
+
+bool
+virXMLElemDefCheck(const virXMLElemDef *def,
+                   const void *parent,
+                   void *opaque);
+
+
+[conf/xmlelem.generated.c]
+
+#ifndef RESET_VIR_XMLELEM_DEF_CHECK
+
+bool
+virXMLElemDefCheck(const virXMLElemDef *def,
+                   const void *parent G_GNUC_UNUSED,
+                   void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return virPortRangeCheck(&def->port, def, opaque) || virSocketAddrRangeCheck(&def->addr, def, opaque) || virUUIDCheck(&def->uuid, def, opaque);
+}
+
+#endif /* RESET_VIR_XMLELEM_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/xmlelem.h" */
+/* Makesure "xmlelem.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_XMLELEM_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_XMLELEM_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_XMLELEM_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_XMLELEM_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "xmlelem.generated.h"
diff --git a/tests/xmlgenout/xmlgroup.txt b/tests/xmlgenout/xmlgroup.txt
new file mode 100644
index 00000000..ff9d7e09
--- /dev/null
+++ b/tests/xmlgenout/xmlgroup.txt
@@ -0,0 +1,204 @@
+[conf/xmlgroup.generated.h]
+
+void
+virXMLGroupDefClear(virXMLGroupDef *def);
+
+
+[conf/xmlgroup.generated.c]
+
+void
+virXMLGroupDefClear(virXMLGroupDef *def)
+{
+    if (!def)
+        return;
+
+    virUtilAuthDefClear(&def->auth);
+}
+
+
+[conf/xmlgroup.generated.h]
+
+int
+virXMLGroupDefParseXML(xmlNodePtr node,
+                       virXMLGroupDef *def,
+                       const char *instname,
+                       void *parent,
+                       void *opaque);
+
+
+[conf/xmlgroup.generated.h]
+
+#ifdef ENABLE_VIR_XMLGROUP_DEF_PARSE_HOOK
+
+int
+virXMLGroupDefParseHook(xmlNodePtr node,
+                        virXMLGroupDef *def,
+                        const char *instname,
+                        void *parent,
+                        void *opaque);
+
+#endif
+
+
+[conf/xmlgroup.generated.h]
+
+#ifdef ENABLE_VIR_XMLGROUP_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virXMLGroupDefParseXMLSetArgs(xmlNodePtr node,
+                              void *parent,
+                              void **pparent,
+                              void **popaque);
+
+#endif
+
+
+[conf/xmlgroup.generated.c]
+
+int
+virXMLGroupDefParseXML(xmlNodePtr node,
+                       virXMLGroupDef *def,
+                       const char *instname G_GNUC_UNUSED,
+                       void *parent G_GNUC_UNUSED,
+                       void *opaque G_GNUC_UNUSED)
+{
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_XMLGROUP_DEF_PARSE_HOOK_SET_ARGS
+    virXMLGroupDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    if (virUtilAuthDefParseXML(node, &def->auth, instname, arg_parent, arg_opaque) < 0)
+        goto error;
+
+#ifdef ENABLE_VIR_XMLGROUP_DEF_PARSE_HOOK
+    if (virXMLGroupDefParseHook(node, def, instname, parent, opaque) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virXMLGroupDefClear(def);
+    return -1;
+}
+
+
+[conf/xmlgroup.generated.h]
+
+int
+virXMLGroupDefFormatBuf(virBuffer *buf,
+                        const char *name,
+                        const virXMLGroupDef *def,
+                        const void *parent,
+                        void *opaque);
+
+
+[conf/xmlgroup.generated.h]
+
+#ifdef ENABLE_VIR_XMLGROUP_DEF_FORMAT_HOOK
+
+int
+virXMLGroupDefFormatHook(const virXMLGroupDef *def,
+                         const void *parent,
+                         const void *opaque,
+                         virTristateBool *empty,
+                         virTristateBool *shortcut);
+
+#endif /* ENABLE_VIR_XMLGROUP_DEF_FORMAT_HOOK */
+
+
+[conf/xmlgroup.generated.c]
+
+int
+virXMLGroupDefFormatBuf(virBuffer *buf,
+                        const char *name,
+                        const virXMLGroupDef *def,
+                        const void *parent G_GNUC_UNUSED,
+                        void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_XMLGROUP_DEF_FORMAT_HOOK
+    if (virXMLGroupDefFormatHook(def, parent, opaque, &empty, &shortcut) < 0)
+        return -1;
+#endif /* ENABLE_VIR_XMLGROUP_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(virUtilAuthDefCheckAttr(&def->auth, def, opaque) || virUtilAuthDefCheckElem(&def->auth, def, opaque)))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    if (virUtilAuthDefFormatAttr(buf, &def->auth, def, opaque) < 0)
+        return -1;
+
+    if (shortcut != VIR_TRISTATE_BOOL_NO) {
+        if (shortcut || !(virUtilAuthDefCheckElem(&def->auth, def, opaque))) {
+            virBufferAddLit(buf, "/>\n");
+            return 0;
+        }
+    }
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    if (virUtilAuthDefFormatElem(buf, &def->auth, def, opaque) < 0)
+        return -1;
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/xmlgroup.generated.h]
+
+bool
+virXMLGroupDefCheck(const virXMLGroupDef *def,
+                    const void *parent,
+                    void *opaque);
+
+
+[conf/xmlgroup.generated.c]
+
+#ifndef RESET_VIR_XMLGROUP_DEF_CHECK
+
+bool
+virXMLGroupDefCheck(const virXMLGroupDef *def,
+                    const void *parent G_GNUC_UNUSED,
+                    void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return virUtilAuthDefCheckAttr(&def->auth, def, opaque) || virUtilAuthDefCheckElem(&def->auth, def, opaque);
+}
+
+#endif /* RESET_VIR_XMLGROUP_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/xmlgroup.h" */
+/* Makesure "xmlgroup.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_XMLGROUP_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_XMLGROUP_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_XMLGROUP_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_XMLGROUP_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "xmlgroup.generated.h"
diff --git a/tests/xmlgenout/xmlswitch.txt b/tests/xmlgenout/xmlswitch.txt
new file mode 100644
index 00000000..772f1353
--- /dev/null
+++ b/tests/xmlgenout/xmlswitch.txt
@@ -0,0 +1,470 @@
+[conf/xmlswitch.generated.h]
+
+void
+virXMLSwitchDefClear(virXMLSwitchDef *def);
+
+
+[conf/xmlswitch.generated.c]
+
+void
+virXMLSwitchDefClear(virXMLSwitchDef *def)
+{
+    if (!def)
+        return;
+
+    switch (def->type) {
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
+        virDomainGraphicsSDLDefClear(&def->data.sdl);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
+        virDomainGraphicsVNCDefClear(&def->data.vnc);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+        virDomainGraphicsRDPDefClear(&def->data.rdp);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
+        virDomainGraphicsDesktopDefClear(&def->data.desktop);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
+        virDomainGraphicsSpiceDefClear(&def->data.spice);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
+        virDomainGraphicsEGLHeadlessDefClear(&def->data.egl_headless);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
+        break;
+    }
+
+    def->type = 0;
+}
+
+
+[conf/xmlswitch.generated.h]
+
+int
+virXMLSwitchDefParseXML(xmlNodePtr node,
+                        virXMLSwitchDef *def,
+                        const char *instname,
+                        void *parent,
+                        void *opaque);
+
+
+[conf/xmlswitch.generated.h]
+
+#ifdef ENABLE_VIR_XMLSWITCH_DEF_PARSE_HOOK
+
+int
+virXMLSwitchDefParseHook(xmlNodePtr node,
+                         virXMLSwitchDef *def,
+                         const char *instname,
+                         void *parent,
+                         void *opaque,
+                         const char *typeStr);
+
+#endif
+
+
+[conf/xmlswitch.generated.h]
+
+#ifdef ENABLE_VIR_XMLSWITCH_DEF_PARSE_HOOK_SET_ARGS
+
+void
+virXMLSwitchDefParseXMLSetArgs(xmlNodePtr node,
+                               void *parent,
+                               void **pparent,
+                               void **popaque);
+
+#endif
+
+
+[conf/xmlswitch.generated.c]
+
+int
+virXMLSwitchDefParseXML(xmlNodePtr node,
+                        virXMLSwitchDef *def,
+                        const char *instname G_GNUC_UNUSED,
+                        void *parent G_GNUC_UNUSED,
+                        void *opaque G_GNUC_UNUSED)
+{
+    g_autofree char *typeStr = NULL;
+    void *arg_parent G_GNUC_UNUSED = def;
+    void *arg_opaque G_GNUC_UNUSED = opaque;
+
+    if (!def)
+        goto error;
+
+#ifdef ENABLE_VIR_XMLSWITCH_DEF_PARSE_HOOK_SET_ARGS
+    virXMLSwitchDefParseXMLSetArgs(node, parent, &arg_parent, &arg_opaque);
+#endif
+
+    typeStr = virXMLPropString(node, "type");
+    if (typeStr) {
+        if ((def->type = virDomainGraphicsTypeFromString(typeStr)) < 0) {
+            virReportError(VIR_ERR_XML_ERROR,
+                           _("Invalid '%s' setting '%s' in '%s'"),
+                           "type", typeStr, instname);
+            goto error;
+        }
+    }
+
+    switch (def->type) {
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
+        if (virDomainGraphicsSDLDefParseXML(node, &def->data.sdl, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
+        if (virDomainGraphicsVNCDefParseXML(node, &def->data.vnc, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+        if (virDomainGraphicsRDPDefParseXML(node, &def->data.rdp, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
+        if (virDomainGraphicsDesktopDefParseXML(node, &def->data.desktop, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
+        if (virDomainGraphicsSpiceDefParseXML(node, &def->data.spice, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
+        if (virDomainGraphicsEGLHeadlessDefParseXML(node, &def->data.egl_headless, instname, arg_parent, arg_opaque) < 0)
+            goto error;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
+        break;
+    }
+
+#ifdef ENABLE_VIR_XMLSWITCH_DEF_PARSE_HOOK
+    if (virXMLSwitchDefParseHook(node, def, instname, parent, opaque, typeStr) < 0)
+        goto error;
+#endif
+
+    return 0;
+
+ error:
+    virXMLSwitchDefClear(def);
+    return -1;
+}
+
+
+[conf/xmlswitch.generated.h]
+
+bool
+virXMLSwitchDefDataCheckAttr(const virXMLSwitchDef *def,
+                             void *opaque);
+
+
+[conf/xmlswitch.generated.c]
+
+#ifndef RESET_VIR_XMLSWITCH_DEF_DATA_CHECK_ATTR
+
+bool
+virXMLSwitchDefDataCheckAttr(const virXMLSwitchDef *def,
+                             void *opaque G_GNUC_UNUSED)
+{
+    bool ret = false;
+    if (!def)
+        return false;
+
+    switch (def->type) {
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
+        ret = virDomainGraphicsSDLDefCheckAttr(&def->data.sdl, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
+        ret = virDomainGraphicsVNCDefCheckAttr(&def->data.vnc, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+        ret = virDomainGraphicsRDPDefCheckAttr(&def->data.rdp, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
+        ret = virDomainGraphicsDesktopDefCheckAttr(&def->data.desktop, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
+        ret = virDomainGraphicsSpiceDefCheckAttr(&def->data.spice, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
+        ret = virDomainGraphicsEGLHeadlessDefCheckAttr(&def->data.egl_headless, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
+        break;
+    }
+
+    return ret;
+}
+
+#endif /* RESET_VIR_XMLSWITCH_DEF_DATA_CHECK_ATTR */
+
+
+[conf/xmlswitch.generated.h]
+
+bool
+virXMLSwitchDefDataCheckElem(const virXMLSwitchDef *def,
+                             void *opaque);
+
+
+[conf/xmlswitch.generated.c]
+
+#ifndef RESET_VIR_XMLSWITCH_DEF_DATA_CHECK_ELEM
+
+bool
+virXMLSwitchDefDataCheckElem(const virXMLSwitchDef *def,
+                             void *opaque G_GNUC_UNUSED)
+{
+    bool ret = false;
+    if (!def)
+        return false;
+
+    switch (def->type) {
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
+        ret = virDomainGraphicsSDLDefCheckElem(&def->data.sdl, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
+        ret = virDomainGraphicsVNCDefCheckElem(&def->data.vnc, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+        ret = virDomainGraphicsRDPDefCheckElem(&def->data.rdp, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
+        ret = virDomainGraphicsDesktopDefCheckElem(&def->data.desktop, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
+        ret = virDomainGraphicsSpiceDefCheckElem(&def->data.spice, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
+        ret = virDomainGraphicsEGLHeadlessDefCheckElem(&def->data.egl_headless, def, opaque);
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
+        break;
+    }
+
+    return ret;
+}
+
+#endif /* RESET_VIR_XMLSWITCH_DEF_DATA_CHECK_ELEM */
+
+
+[conf/xmlswitch.generated.h]
+
+int
+virXMLSwitchDefFormatBuf(virBuffer *buf,
+                         const char *name,
+                         const virXMLSwitchDef *def,
+                         const void *parent,
+                         void *opaque);
+
+
+[conf/xmlswitch.generated.h]
+
+#ifdef ENABLE_VIR_XMLSWITCH_DEF_FORMAT_HOOK
+
+int
+virXMLSwitchDefFormatHook(const virXMLSwitchDef *def,
+                          const void *parent,
+                          const void *opaque,
+                          virTristateBool *empty,
+                          virTristateBool *shortcut,
+                          virBuffer *typeBuf);
+
+#endif /* ENABLE_VIR_XMLSWITCH_DEF_FORMAT_HOOK */
+
+
+[conf/xmlswitch.generated.c]
+
+int
+virXMLSwitchDefFormatBuf(virBuffer *buf,
+                         const char *name,
+                         const virXMLSwitchDef *def,
+                         const void *parent G_GNUC_UNUSED,
+                         void *opaque G_GNUC_UNUSED)
+{
+    virTristateBool empty G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    virTristateBool shortcut G_GNUC_UNUSED = VIR_TRISTATE_BOOL_ABSENT;
+    g_auto(virBuffer) typeBuf = VIR_BUFFER_INITIALIZER;
+
+    if (!def || !buf)
+        return 0;
+
+#ifdef ENABLE_VIR_XMLSWITCH_DEF_FORMAT_HOOK
+    if (virXMLSwitchDefFormatHook(def, parent, opaque, &empty, &shortcut, &typeBuf) < 0)
+        return -1;
+#endif /* ENABLE_VIR_XMLSWITCH_DEF_FORMAT_HOOK */
+
+    if (empty != VIR_TRISTATE_BOOL_NO)
+        if (empty || !(def->type >= 0 || virXMLSwitchDefDataCheckAttr(def, opaque) || virXMLSwitchDefDataCheckElem(def, opaque)))
+            return 0;
+
+    virBufferAsprintf(buf, "<%s", name);
+
+    virBufferInheritIndent(&typeBuf, buf);
+    if (!virBufferTouched(&typeBuf) && def->type >= 0) {
+        const char *str = virDomainGraphicsTypeToString(def->type);
+        if (!str) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Unknown %s type %d"),
+                           "type", def->type);
+            return -1;
+        }
+        virBufferAsprintf(&typeBuf, " type='%s'", str);
+    }
+    virBufferAddBuffer(buf, &typeBuf);
+
+    switch (def->type) {
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
+        if (virDomainGraphicsSDLDefFormatAttr(buf, &def->data.sdl, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
+        if (virDomainGraphicsVNCDefFormatAttr(buf, &def->data.vnc, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+        if (virDomainGraphicsRDPDefFormatAttr(buf, &def->data.rdp, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
+        if (virDomainGraphicsDesktopDefFormatAttr(buf, &def->data.desktop, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
+        if (virDomainGraphicsSpiceDefFormatAttr(buf, &def->data.spice, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
+        if (virDomainGraphicsEGLHeadlessDefFormatAttr(buf, &def->data.egl_headless, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
+        break;
+    }
+
+    if (shortcut != VIR_TRISTATE_BOOL_NO) {
+        if (shortcut || !(virXMLSwitchDefDataCheckElem(def, opaque))) {
+            virBufferAddLit(buf, "/>\n");
+            return 0;
+        }
+    }
+
+    virBufferAddLit(buf, ">\n");
+
+    virBufferAdjustIndent(buf, 2);
+
+    switch (def->type) {
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
+        if (virDomainGraphicsSDLDefFormatElem(buf, &def->data.sdl, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
+        if (virDomainGraphicsVNCDefFormatElem(buf, &def->data.vnc, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+        if (virDomainGraphicsRDPDefFormatElem(buf, &def->data.rdp, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
+        if (virDomainGraphicsDesktopDefFormatElem(buf, &def->data.desktop, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
+        if (virDomainGraphicsSpiceDefFormatElem(buf, &def->data.spice, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
+        if (virDomainGraphicsEGLHeadlessDefFormatElem(buf, &def->data.egl_headless, def, opaque) < 0)
+            return -1;
+        break;
+
+    case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
+        break;
+    }
+
+    virBufferAdjustIndent(buf, -2);
+    virBufferAsprintf(buf, "</%s>\n", name);
+
+    return 0;
+}
+
+
+[conf/xmlswitch.generated.h]
+
+bool
+virXMLSwitchDefCheck(const virXMLSwitchDef *def,
+                     const void *parent,
+                     void *opaque);
+
+
+[conf/xmlswitch.generated.c]
+
+#ifndef RESET_VIR_XMLSWITCH_DEF_CHECK
+
+bool
+virXMLSwitchDefCheck(const virXMLSwitchDef *def,
+                     const void *parent G_GNUC_UNUSED,
+                     void *opaque G_GNUC_UNUSED)
+{
+    if (!def)
+        return false;
+
+    return def->type >= 0 || virXMLSwitchDefDataCheckAttr(def, opaque) || virXMLSwitchDefDataCheckElem(def, opaque);
+}
+
+#endif /* RESET_VIR_XMLSWITCH_DEF_CHECK */
+
+
+[Tips]
+
+/* Put these lines at the bottom of "conf/xmlswitch.h" */
+/* Makesure "xmlswitch.h" to be appended into conf_xmlgen_input in src/conf/meson.build */
+
+/* Define macro to enable hook or redefine check when necessary */
+/* #define ENABLE_VIR_XMLSWITCH_DEF_PARSE_HOOK */
+/* #define ENABLE_VIR_XMLSWITCH_DEF_PARSE_HOOK_SET_ARGS */
+/* #define ENABLE_VIR_XMLSWITCH_DEF_FORMAT_HOOK */
+
+/* #define RESET_VIR_XMLSWITCH_DEF_CHECK */
+
+/* Makesure below is the bottom line! */
+#include "xmlswitch.generated.h"
diff --git a/tests/xmlgentest.c b/tests/xmlgentest.c
new file mode 100644
index 00000000..a20773dc
--- /dev/null
+++ b/tests/xmlgentest.c
@@ -0,0 +1,107 @@
+#include <config.h>
+
+#include "internal.h"
+#include "testutils.h"
+#include "vircommand.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+typedef enum {
+    TEST_COMPARE_RESULT_SUCCESS,
+    TEST_COMPARE_RESULT_FAIL_GENERATE,
+    TEST_COMPARE_RESULT_FAIL_COMPARE,
+} testCompareResult;
+
+struct testInfo {
+    const char *name;
+    const char *target;
+    testCompareResult expectResult;
+};
+
+static int
+testCompareGenFiles(const void *data)
+{
+    int ret = 0;
+    testCompareResult result = TEST_COMPARE_RESULT_SUCCESS;
+    const struct testInfo *info = data;
+    g_autofree char *outbuf = NULL;
+    g_autoptr(virCommand) cmd = NULL;
+    g_autofree char *outfile = NULL;
+    char *actual = NULL;
+
+    cmd = virCommandNewArgList(PYTHON3, "-B",
+                               abs_top_srcdir "/scripts/xmlgen/main.py",
+                               "-s", abs_srcdir "/xmlgenin",
+                               "-b", abs_builddir "/xmlgenin",
+                               "-d", "",
+                               "show", info->target, NULL);
+
+    virCommandSetOutputBuffer(cmd, &outbuf);
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        result = TEST_COMPARE_RESULT_FAIL_GENERATE;
+        goto cleanup;
+    }
+
+    /* Skip first empty line */
+    if (outbuf && outbuf[0] == '\n')
+        actual = outbuf + 1;
+    else
+        actual = outbuf;
+
+    outfile = g_strdup_printf("%s/xmlgenout/%s.txt", abs_srcdir, info->name);
+    if (virTestCompareToFile(actual, outfile) < 0) {
+        result = TEST_COMPARE_RESULT_FAIL_COMPARE;
+        goto cleanup;
+    }
+
+ cleanup:
+    if (result == info->expectResult) {
+        ret = 0;
+        if (info->expectResult != TEST_COMPARE_RESULT_SUCCESS) {
+            VIR_TEST_DEBUG("Got expected failure code=%d msg=%s",
+                           result, virGetLastErrorMessage());
+        }
+    } else {
+        ret = -1;
+        VIR_TEST_DEBUG("Expected result code=%d but received code=%d",
+                       info->expectResult, result);
+    }
+    virResetLastError();
+
+    return ret;
+}
+
+static int
+mymain(void)
+{
+    int ret = 0;
+
+#define DO_TEST(name, target) \
+    do { \
+        const struct testInfo info = {name, target, TEST_COMPARE_RESULT_SUCCESS}; \
+        if (virTestRun("xmlgen: generate for " target " in test " name, \
+                       testCompareGenFiles, &info) < 0) \
+            ret = -1; \
+    } while (0)
+
+    DO_TEST("empty", "virEmptyDef");
+    DO_TEST("genparse", "virGenParseDef");
+    DO_TEST("genformat", "virGenFormatDef");
+    DO_TEST("genformat-separate", "virSeparateDef");
+    DO_TEST("xmlattr", "virXMLAttrDef");
+    DO_TEST("xmlelem", "virXMLElemDef");
+    DO_TEST("array", "virArrayDef");
+    DO_TEST("required", "virRequiredDef");
+    DO_TEST("specify", "virSpecifyDef");
+    DO_TEST("skipparse", "virSkipParseDef");
+    DO_TEST("xmlgroup", "virXMLGroupDef");
+    DO_TEST("xmlswitch", "virXMLSwitchDef");
+    DO_TEST("enum-first-item", "virEnumFirstItemDef");
+    DO_TEST("namespace", "virNameSpaceDef");
+    DO_TEST("external", "virExternalDef");
+
+    return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+VIR_TEST_MAIN(mymain)
-- 
2.25.1






[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]

  Powered by Linux