From nobody Thu Dec 18 23:01:57 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) client-ip=170.10.133.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1678293577; cv=none; d=zohomail.com; s=zohoarc; b=dIcF26KDV6oErXVvHc8+FkPcyXCCRndtgd+85d+HGdnVNG7vZV8eXHSyrr6HTy9oBfp5gL75kle44Q+DjXlmTWv7HRkIwcmiHL/Dky7lJ3sihepblU8ZKGvXbOB03TaKXHkSfmtr5XqcLOaV+lXcj62cBXS/w0U/wv7094nNSNI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1678293577; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=XUnMtHZrBwbpTByV82xiuGIeg963sjQG3MWnFHFdtRg=; b=aCfWjKisf8WzSAIHi2NGQ4T5HWc8nlurYhqkVFE7RH16LAF2DPROoPyaane9FLZACN+bbL3nod3oAwvwhwMT4vgEJg0Zym/3sJEx6+BUTAZiquXqYamIzxuqErxqHlIA/EyZYnkEycPyP+bH6kNjDY6/sysdSBzlKTJ+yTZvcbE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by mx.zohomail.com with SMTPS id 1678293577742923.5927262257832; Wed, 8 Mar 2023 08:39:37 -0800 (PST) Received: from mimecast-mx02.redhat.com (mx3-rdu2.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-620-4aMjoGCINIOITGcXUViHtA-1; Wed, 08 Mar 2023 11:39:32 -0500 Received: from smtp.corp.redhat.com (int-mx09.intmail.prod.int.rdu2.redhat.com [10.11.54.9]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 6EF021C0758F; Wed, 8 Mar 2023 16:39:22 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (unknown [10.30.29.100]) by smtp.corp.redhat.com (Postfix) with ESMTP id 01FAC492B08; Wed, 8 Mar 2023 16:39:22 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (localhost [IPv6:::1]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id 99E961946A51; Wed, 8 Mar 2023 16:39:20 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id 1799F1946A5E for ; Wed, 8 Mar 2023 16:39:19 +0000 (UTC) Received: by smtp.corp.redhat.com (Postfix) id F1C881121330; Wed, 8 Mar 2023 16:39:18 +0000 (UTC) Received: from virtlab420.virt.lab.eng.bos.redhat.com (virtlab420.virt.lab.eng.bos.redhat.com [10.19.152.148]) by smtp.corp.redhat.com (Postfix) with ESMTP id CDC111121314; Wed, 8 Mar 2023 16:39:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1678293576; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=XUnMtHZrBwbpTByV82xiuGIeg963sjQG3MWnFHFdtRg=; b=Xv3i9Ca9upPD1LbWdT10OAgeCOUWKtDYvWncmrLjY5Zwd2mprA5eXsyN06t3sZvrwvePzq yTi8ca9VYvvklYeY32S6GQGRN91efxyshGFats53+kdYGXGTfAxtashSxmkeK8oODfaok4 XWEg4IO18vPl2Bo9M6EACMc4Gh3upJY= X-MC-Unique: 4aMjoGCINIOITGcXUViHtA-1 X-Original-To: libvir-list@listman.corp.redhat.com From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= To: libvir-list@redhat.com Subject: [PATCH 08/16] rpcgen: add a C code generator for XDR protocol specs Date: Wed, 8 Mar 2023 11:39:05 -0500 Message-Id: <20230308163913.338952-9-berrange@redhat.com> In-Reply-To: <20230308163913.338952-1-berrange@redhat.com> References: <20230308163913.338952-1-berrange@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.3 X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libvir-list-bounces@redhat.com Sender: "libvir-list" X-Scanned-By: MIMEDefang 3.1 on 10.11.54.9 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1678293578752100003 This implements a C code generator that emits code that is (almost) identical to the classic 'rpcgen' program. The key differences are: - Skip inlining of calls for struct fields - Skip K&R style function prototypes in headers - Use int64_t instead of quad_t for OS portability - Saner whitespace / indentation The tests/demo.c and tests/demo.h files were created using the traditional 'rpcgen' program, and then editted to cut out the leading boilerplate, and the differences mentioned above. Signed-off-by: Daniel P. Berrang=C3=A9 --- build-aux/syntax-check.mk | 4 +- scripts/rpcgen/rpcgen/generator.py | 467 +++++++++++++++++++++++++ scripts/rpcgen/tests/demo.c | 351 +++++++++++++++++++ scripts/rpcgen/tests/demo.h | 216 ++++++++++++ scripts/rpcgen/tests/demo.x | 128 +++++++ scripts/rpcgen/tests/meson.build | 1 + scripts/rpcgen/tests/test_generator.py | 55 +++ 7 files changed, 1221 insertions(+), 1 deletion(-) create mode 100644 scripts/rpcgen/rpcgen/generator.py create mode 100644 scripts/rpcgen/tests/demo.c create mode 100644 scripts/rpcgen/tests/demo.h create mode 100644 scripts/rpcgen/tests/demo.x create mode 100644 scripts/rpcgen/tests/test_generator.py diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index 6d82a4301a..375fad188b 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -1429,7 +1429,7 @@ exclude_file_name_regexp--sc_prohibit_xmlURI =3D ^src= /util/viruri\.c$$ exclude_file_name_regexp--sc_prohibit_return_as_function =3D \.py$$ =20 exclude_file_name_regexp--sc_require_config_h =3D \ - ^(examples/|tools/virsh-edit\.c$$|tests/virmockstathelpers.c) + ^(examples/c/.*/.*\.c|tools/virsh-edit\.c|tests/virmockstathelpers\.c|scr= ipts/rpcgen/tests/demo\.c)$$ =20 exclude_file_name_regexp--sc_require_config_h_first =3D \ ^(examples/|tools/virsh-edit\.c$$|tests/virmockstathelpers.c) @@ -1493,6 +1493,8 @@ exclude_file_name_regexp--sc_prohibit_strcmp =3D \ exclude_file_name_regexp--sc_prohibit_select =3D \ ^build-aux/syntax-check\.mk|src/util/vireventglibwatch\.c|tests/meson\.b= uild$$ =20 +exclude_file_name_regexp--sc_header-ifdef =3D \ + ^scripts/rpcgen/tests/demo\.[ch]$$ =20 exclude_file_name_regexp--sc_black =3D \ ^tools/|src/|tests/|ci/|run\.in|scripts/[^/]*\.py diff --git a/scripts/rpcgen/rpcgen/generator.py b/scripts/rpcgen/rpcgen/gen= erator.py new file mode 100644 index 0000000000..110cd12c5e --- /dev/null +++ b/scripts/rpcgen/rpcgen/generator.py @@ -0,0 +1,467 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +from .visitor import XDRVisitor +from .parser import ( + XDRTypeString, + XDRTypeVoid, + XDRTypeOpaque, + XDRTypeCustom, + XDRDefinitionTypedef, + XDRDeclarationFixedArray, + XDRDeclarationVariableArray, + XDRDeclarationPointer, +) + + +class XDRTypeDeclarationGenerator(XDRVisitor): + def visit_definition_cescape(self, obj, indent, context): + return obj.code + "\n" + + def visit_definition_constant(self, obj, indent, context): + return "#%sdefine %s %s\n" % (indent, obj.name, obj.value) + + def visit_definition_enum(self, obj, indent, context): + code =3D "%senum %s %s;\n" % ( + indent, + obj.name, + self.visit_object(obj.body, indent), + ) + "%stypedef enum %s %s;\n" % (indent, obj.name, obj.name) + return code + + def visit_definition_struct(self, obj, indent, context): + code =3D "%sstruct %s %s;\n" % ( + indent, + obj.name, + self.visit_object(obj.body, indent), + ) + "%stypedef struct %s %s;\n" % (indent, obj.name, obj.name) + return code + + def visit_definition_union(self, obj, indent, context): + code =3D "%sstruct %s %s;\n" % ( + indent, + obj.name, + self.visit_object(obj.body, indent, obj.name), + ) + "%stypedef struct %s %s;\n" % (indent, obj.name, obj.name) + return code + + def visit_definition_typedef(self, obj, indent, context): + return "%stypedef %s;\n" % (indent, self.visit_object(obj.decl, in= dent)) + + def visit_declaration_scalar(self, obj, indent, context): + return "%s %s" % (self.visit_object(obj.typ, indent), obj.identifi= er) + + def visit_declaration_pointer(self, obj, indent, context): + return "%s *%s" % (self.visit_object(obj.typ, indent), obj.identif= ier) + + def visit_declaration_fixedarray(self, obj, indent, context): + return "%s %s[%s]" % ( + self.visit_object(obj.typ, indent), + obj.identifier, + obj.length, + ) + + def visit_declaration_variablearray(self, obj, indent, context): + if type(obj.typ) =3D=3D XDRTypeString: + return "%schar *%s" % (indent, obj.identifier) + else: + code =3D ( + "%sstruct {\n" % indent + + "%s u_int %s_len;\n" % (indent, obj.identifier) + + "%s %s *%s_val;\n" + % (indent, self.visit_object(obj.typ, ""), obj.identifier) + + "%s} %s" % (indent, obj.identifier) + ) + return code + + def visit_type_custom(self, obj, indent, context): + return "%s%s" % (indent, obj.identifier) + + def visit_type_opaque(self, obj, indent, context): + return "%schar" % indent + + def visit_type_string(self, obj, indent, context): + return "%sstring" % indent + + def visit_type_void(self, obj, indent, context): + return "%svoid" % indent + + def visit_type_char(self, obj, indent, context): + return "%schar" % indent + + def visit_type_unsignedchar(self, obj, indent, context): + return "%su_char" % indent + + def visit_type_short(self, obj, indent, context): + return "%sshort" % indent + + def visit_type_unsignedshort(self, obj, indent, context): + return "%su_short" % indent + + def visit_type_int(self, obj, indent, context): + return "%sint" % indent + + def visit_type_unsignedint(self, obj, indent, context): + return "%su_int" % indent + + def visit_type_hyper(self, obj, indent, context): + return "%sint64_t" % indent + + def visit_type_unsignedhyper(self, obj, indent, context): + return "%suint64_t" % indent + + def visit_type_bool(self, obj, indent, context): + return "%sbool_t" % indent + + def visit_type_float(self, obj, indent, context): + return "%sfloat" % indent + + def visit_type_double(self, obj, indent, context): + return "%sdouble" % indent + + def visit_type_enum(self, obj, indent, context): + return "%senum %s" % (indent, self.visit_object(obj.body.body, ind= ent)) + + def visit_type_struct(self, obj, indent, context): + return "%sstruct %s" % (indent, self.visit_object(obj.body, indent= )) + + def visit_type_union(self, obj, indent, context): + return "%sstruct %s" % (indent, self.visit_object(obj.body, indent= )) + + def visit_enum_value(self, obj, indent, context): + return "%s%s =3D %s" % (indent, obj.name, obj.value) + + def visit_enum_body(self, obj, indent, context): + code =3D "{\n" + for value in obj.values: + code =3D code + self.visit_object(value, indent + " ") + ",= \n" + code =3D code + "%s}" % indent + return code + + def visit_struct_body(self, obj, indent, context): + code =3D "{\n" + for value in obj.fields: + code =3D code + self.visit_object(value, indent + " ") + ";= \n" + code =3D code + "%s}" % indent + return code + + def visit_union_case(self, obj, indent, context): + return self.visit_object(obj.decl, indent) + + def visit_union_body(self, obj, indent, context): + prefix =3D context + if prefix !=3D "": + prefix =3D prefix + "_" + + code =3D ( + "%s{\n" % indent + + "%s %s;\n" % (indent, self.visit_object(obj.discriminator= )) + + "%s union {\n" % indent + ) + for value in obj.cases: + if type(value.decl.typ) =3D=3D XDRTypeVoid: + continue + code =3D code + self.visit_object(value, indent + " ") = + ";\n" + if obj.default is not None and type(obj.default.typ) !=3D XDRTypeV= oid: + code =3D code + self.visit_object(obj.default, indent + " = ") + ";\n" + code =3D code + "%s } %su;\n" % (indent, prefix) + "%s}" % inde= nt + return code + + +class XDRMarshallDeclarationGenerator(XDRVisitor): + def visit_definition_enum(self, obj, indent, context): + return "%sextern bool_t xdr_%s(XDR *, %s*);\n" % (indent, obj.nam= e, obj.name) + + def visit_definition_union(self, obj, indent, context): + return "%sextern bool_t xdr_%s(XDR *, %s*);\n" % (indent, obj.nam= e, obj.name) + + def visit_definition_struct(self, obj, indent, context): + return "%sextern bool_t xdr_%s(XDR *, %s*);\n" % (indent, obj.nam= e, obj.name) + + def visit_definition_typedef(self, obj, indent, context): + if isinstance(obj.decl, XDRDeclarationFixedArray): + return "%sextern bool_t xdr_%s(XDR *, %s);\n" % ( + indent, + obj.decl.identifier, + obj.decl.identifier, + ) + else: + return "%sextern bool_t xdr_%s(XDR *, %s*);\n" % ( + indent, + obj.decl.identifier, + obj.decl.identifier, + ) + + +class XDRMarshallImplementationGenerator(XDRVisitor): + def visit_definition_enum(self, obj, indent, context): + code =3D ( + "%sbool_t\n" % indent + + "%sxdr_%s(XDR *xdrs, %s *objp)\n" % (indent, obj.name, obj.n= ame) + + "%s{\n" % indent + + "%s if (!xdr_enum(xdrs, (enum_t *)objp))\n" % indent + + "%s return FALSE;\n" % indent + + "%s return TRUE;\n" % indent + + "%s}\n" % indent + ) + return code + + def generate_type_call(self, decl, field, typename, embedded=3DFalse, = indent=3D""): + if type(decl.typ) =3D=3D XDRTypeVoid: + return "" + if type(decl) =3D=3D XDRDeclarationFixedArray: + if type(decl.typ) =3D=3D XDRTypeOpaque: + code =3D "%s if (!xdr_%s(xdrs, %s, %s))\n" % ( + indent, + self.visit_object(decl.typ, context=3D"func"), + field, + decl.length, + ) + else: + code =3D "%s if (!xdr_vector(xdrs, (char *)%s, %s,\n" %= ( + indent, + field, + decl.length, + ) + "%s sizeof(%s), (xdrproc_t)xdr_%s))\n" % ( + indent, + self.visit_object(decl.typ), + self.visit_object(decl.typ, context=3D"func"), + ) + elif type(decl) =3D=3D XDRDeclarationVariableArray: + fieldRef =3D "." + pointerStr =3D "" + if embedded: + pointerStr =3D "&" + else: + fieldRef =3D "->" + + if type(decl.typ) =3D=3D XDRTypeString: + code =3D "%s if (!xdr_%s(xdrs, %s%s, %s))\n" % ( + indent, + self.visit_object(decl.typ, context=3D"func"), + pointerStr, + field, + decl.maxlength, + ) + elif type(decl.typ) =3D=3D XDRTypeOpaque: + code =3D "%s if (!xdr_bytes(xdrs, (char **)&%s%s%s_val,= " % ( + indent, + field, + fieldRef, + typename, + ) + "(u_int *) &%s%s%s_len, %s))\n" % ( + field, + fieldRef, + typename, + decl.maxlength, + ) + else: + code =3D ( + "%s if (!xdr_array(xdrs, (char **)&%s%s%s_val, " + % (indent, field, fieldRef, typename) + + "(u_int *) &%s%s%s_len, %s,\n" + % (field, fieldRef, typename, decl.maxlength) + + "%s sizeof(%s), (xdrproc_t)xdr_%s))\n" + % ( + indent, + self.visit_object(decl.typ), + self.visit_object(decl.typ, context=3D"func"), + ) + ) + elif type(decl) =3D=3D XDRDeclarationPointer: + pointerStr =3D "" + if embedded: + pointerStr =3D "&" + + code =3D "%s if (!xdr_pointer(xdrs, (char **)%s%s, " % ( + indent, + pointerStr, + field, + ) + "sizeof(%s), (xdrproc_t)xdr_%s))\n" % ( + self.visit_object(decl.typ, context=3D"func"), + self.visit_object(decl.typ, context=3D"func"), + ) + else: + pointerStr =3D "" + isFixedArray =3D ( + type(decl.typ) =3D=3D XDRTypeCustom + and type(decl.typ.definition) =3D=3D XDRDefinitionTypedef + and type(decl.typ.definition.decl) =3D=3D XDRDeclarationFi= xedArray + ) + + if embedded and not isFixedArray: + pointerStr =3D "&" + + code =3D "%s if (!xdr_%s(xdrs, %s%s))\n" % ( + indent, + self.visit_object(decl.typ, context=3D"func"), + pointerStr, + field, + ) + + code =3D code + "%s return FALSE;\n" % indent + return code + + def visit_definition_union(self, obj, indent, context): + code =3D ( + "%sbool_t\n" % indent + + "%sxdr_%s(XDR *xdrs, %s *objp)\n" % (indent, obj.name, obj.n= ame) + + "%s{\n" % indent + + self.generate_type_call( + obj.body.discriminator, + "objp->%s" % obj.body.discriminator.identifier, + obj.body.discriminator.identifier, + embedded=3DTrue, + indent=3Dindent, + ) + + "%s switch (objp->%s) {\n" + % (indent, obj.body.discriminator.identifier) + ) + + for case in obj.body.cases: + code =3D ( + code + + "%s case %s:\n" % (indent, case.value) + + self.generate_type_call( + case.decl, + "objp->%s_u.%s" % (obj.name, case.decl.identifier), + obj.name, + embedded=3DTrue, + indent=3Dindent + " ", + ) + + "%s break;\n" % indent + ) + + code =3D code + "%s default:\n" % indent + + if obj.body.default is not None: + code =3D ( + code + + self.generate_type_call( + obj.body.default, + "objp->%s_u.%s" % (obj.name, obj.body.default.identifi= er), + obj.name, + embedded=3DTrue, + indent=3Dindent + " ", + ) + + "%s break;\n" % indent + ) + else: + code =3D code + "%s return FALSE;\n" % indent + + code =3D ( + code + + "%s }\n" % indent + + "%s return TRUE;\n" % indent + + "%s}\n" % indent + ) + return code + + def visit_definition_struct(self, obj, indent, context): + code =3D ( + "%sbool_t\n" % indent + + "%sxdr_%s(XDR *xdrs, %s *objp)\n" % (indent, obj.name, obj.n= ame) + + "%s{\n" % indent + ) + for field in obj.body.fields: + code =3D code + self.generate_type_call( + field, + "objp->%s" % field.identifier, + field.identifier, + embedded=3DTrue, + indent=3Dindent, + ) + code =3D code + "%s return TRUE;\n" % indent + "%s}\n" % indent + return code + + def visit_definition_typedef(self, obj, indent, context): + code =3D "%sbool_t\n" % indent + if isinstance(obj.decl, XDRDeclarationFixedArray): + code =3D code + "%sxdr_%s(XDR *xdrs, %s objp)\n" % ( + indent, + obj.decl.identifier, + obj.decl.identifier, + ) + else: + code =3D code + "%sxdr_%s(XDR *xdrs, %s *objp)\n" % ( + indent, + obj.decl.identifier, + obj.decl.identifier, + ) + code =3D ( + code + + "%s{\n" % indent + + self.generate_type_call( + obj.decl, "objp", obj.decl.identifier, embedded=3DFalse, i= ndent=3Dindent + ) + + "%s return TRUE;\n" % indent + + "%s}\n" % indent + ) + return code + + def visit_declaration_pointer(self, obj, indent, context): + return "%s%s *%s" % (indent, self.visit_object(obj.typ), obj.ident= ifier) + + def visit_declaration_fixedarray(self, obj, indent, context): + return "%s%s %s[%s]" % ( + indent, + self.visit_object(obj.typ), + obj.identifier, + obj.length, + ) + + def visit_declaration_variablearray(self, obj, indent, context): + return "%s%s *%s" % (indent, self.visit_object(obj.typ), obj.ident= ifier) + + def visit_type_custom(self, obj, indent, context): + return "%s%s" % (indent, obj.identifier) + + def visit_type_opaque(self, obj, indent, context): + return "%sopaque" % indent + + def visit_type_string(self, obj, indent, context): + return "%sstring" % indent + + def visit_type_char(self, obj, indent, context): + return "%schar" % indent + + def visit_type_unsignedchar(self, obj, indent, context): + return "%su_char" % indent + + def visit_type_short(self, obj, indent, context): + return "%sshort" % indent + + def visit_type_unsignedshort(self, obj, indent, context): + return "%su_short" % indent + + def visit_type_int(self, obj, indent, context): + return "%sint" % indent + + def visit_type_unsignedint(self, obj, indent, context): + return "%su_int" % indent + + def visit_type_hyper(self, obj, indent, context): + return "%sint64_t" % indent + + def visit_type_unsignedhyper(self, obj, indent, context): + if context =3D=3D "func": + return "%su_int64_t" % indent + else: + return "%suint64_t" % indent + + def visit_type_bool(self, obj, indent, context): + if context =3D=3D "func": + return "%sbool" % indent + else: + return "%sbool_t" % indent + + def visit_type_float(self, obj, indent, context): + return "%sfloat" % indent + + def visit_type_double(self, obj, indent, context): + return "%sdouble" % indent + + def visit_enum_value(self, obj, indent, context): + return "%s%s =3D %s" % (indent, obj.name, obj.value) + + def visit_union_case(self, obj, indent, context): + return self.visit_object(obj.value, indent) diff --git a/scripts/rpcgen/tests/demo.c b/scripts/rpcgen/tests/demo.c new file mode 100644 index 0000000000..a261b4fe22 --- /dev/null +++ b/scripts/rpcgen/tests/demo.c @@ -0,0 +1,351 @@ +bool_t +xdr_TestEnum(XDR *xdrs, TestEnum *objp) +{ + if (!xdr_enum(xdrs, (enum_t *)objp)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestStruct(XDR *xdrs, TestStruct *objp) +{ + if (!xdr_char(xdrs, &objp->c1)) + return FALSE; + if (!xdr_char(xdrs, &objp->c2)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestUnion(XDR *xdrs, TestUnion *objp) +{ + if (!xdr_int(xdrs, &objp->type)) + return FALSE; + switch (objp->type) { + case 20: + if (!xdr_int(xdrs, &objp->TestUnion_u.i1)) + return FALSE; + break; + case 30: + if (!xdr_int(xdrs, &objp->TestUnion_u.i2)) + return FALSE; + break; + default: + if (!xdr_int(xdrs, &objp->TestUnion_u.i3)) + return FALSE; + break; + } + return TRUE; +} + +bool_t +xdr_TestUnionVoidDefault(XDR *xdrs, TestUnionVoidDefault *objp) +{ + if (!xdr_int(xdrs, &objp->type)) + return FALSE; + switch (objp->type) { + case 21: + if (!xdr_int(xdrs, &objp->TestUnionVoidDefault_u.i1)) + return FALSE; + break; + case 31: + if (!xdr_int(xdrs, &objp->TestUnionVoidDefault_u.i2)) + return FALSE; + break; + default: + break; + } + return TRUE; +} + +bool_t +xdr_TestUnionNoDefault(XDR *xdrs, TestUnionNoDefault *objp) +{ + if (!xdr_int(xdrs, &objp->type)) + return FALSE; + switch (objp->type) { + case 22: + if (!xdr_int(xdrs, &objp->TestUnionNoDefault_u.i1)) + return FALSE; + break; + case 32: + if (!xdr_int(xdrs, &objp->TestUnionNoDefault_u.i2)) + return FALSE; + break; + default: + return FALSE; + } + return TRUE; +} + +bool_t +xdr_TestIntScalar(XDR *xdrs, TestIntScalar *objp) +{ + if (!xdr_int(xdrs, objp)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestIntPointer(XDR *xdrs, TestIntPointer *objp) +{ + if (!xdr_pointer(xdrs, (char **)objp, sizeof(int), (xdrproc_t)xdr_int)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestIntFixedArray(XDR *xdrs, TestIntFixedArray objp) +{ + if (!xdr_vector(xdrs, (char *)objp, 3, + sizeof(int), (xdrproc_t)xdr_int)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestIntVariableArray(XDR *xdrs, TestIntVariableArray *objp) +{ + if (!xdr_array(xdrs, (char **)&objp->TestIntVariableArray_val, (u_int = *) &objp->TestIntVariableArray_len, 5, + sizeof(int), (xdrproc_t)xdr_int)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestStringVariableArray(XDR *xdrs, TestStringVariableArray *objp) +{ + if (!xdr_string(xdrs, objp, 7)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestOpaqueFixedArray(XDR *xdrs, TestOpaqueFixedArray objp) +{ + if (!xdr_opaque(xdrs, objp, 9)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestOpaqueVariableArray(XDR *xdrs, TestOpaqueVariableArray *objp) +{ + if (!xdr_bytes(xdrs, (char **)&objp->TestOpaqueVariableArray_val, (u_i= nt *) &objp->TestOpaqueVariableArray_len, 11)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestEnumScalar(XDR *xdrs, TestEnumScalar *objp) +{ + if (!xdr_TestEnum(xdrs, objp)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestEnumPointer(XDR *xdrs, TestEnumPointer *objp) +{ + if (!xdr_pointer(xdrs, (char **)objp, sizeof(TestEnum), (xdrproc_t)xdr= _TestEnum)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestEnumFixedArray(XDR *xdrs, TestEnumFixedArray objp) +{ + if (!xdr_vector(xdrs, (char *)objp, 13, + sizeof(TestEnum), (xdrproc_t)xdr_TestEnum)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestEnumVariableArray(XDR *xdrs, TestEnumVariableArray *objp) +{ + if (!xdr_array(xdrs, (char **)&objp->TestEnumVariableArray_val, (u_int= *) &objp->TestEnumVariableArray_len, 15, + sizeof(TestEnum), (xdrproc_t)xdr_TestEnum)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestStructScalar(XDR *xdrs, TestStructScalar *objp) +{ + if (!xdr_TestStruct(xdrs, objp)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestStructPointer(XDR *xdrs, TestStructPointer *objp) +{ + if (!xdr_pointer(xdrs, (char **)objp, sizeof(TestStruct), (xdrproc_t)x= dr_TestStruct)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestStructFixedArray(XDR *xdrs, TestStructFixedArray objp) +{ + if (!xdr_vector(xdrs, (char *)objp, 17, + sizeof(TestStruct), (xdrproc_t)xdr_TestStruct)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestStructVariableArray(XDR *xdrs, TestStructVariableArray *objp) +{ + if (!xdr_array(xdrs, (char **)&objp->TestStructVariableArray_val, (u_i= nt *) &objp->TestStructVariableArray_len, 19, + sizeof(TestStruct), (xdrproc_t)xdr_TestStruct)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestUnionScalar(XDR *xdrs, TestUnionScalar *objp) +{ + if (!xdr_TestUnion(xdrs, objp)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestUnionPointer(XDR *xdrs, TestUnionPointer *objp) +{ + if (!xdr_pointer(xdrs, (char **)objp, sizeof(TestUnion), (xdrproc_t)xd= r_TestUnion)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestUnionFixedArray(XDR *xdrs, TestUnionFixedArray objp) +{ + if (!xdr_vector(xdrs, (char *)objp, 21, + sizeof(TestUnion), (xdrproc_t)xdr_TestUnion)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestUnionVariableArray(XDR *xdrs, TestUnionVariableArray *objp) +{ + if (!xdr_array(xdrs, (char **)&objp->TestUnionVariableArray_val, (u_in= t *) &objp->TestUnionVariableArray_len, 23, + sizeof(TestUnion), (xdrproc_t)xdr_TestUnion)) + return FALSE; + return TRUE; +} + +bool_t +xdr_TestStructAllTypes(XDR *xdrs, TestStructAllTypes *objp) +{ + if (!xdr_char(xdrs, &objp->sc)) + return FALSE; + if (!xdr_u_char(xdrs, &objp->suc)) + return FALSE; + if (!xdr_short(xdrs, &objp->ss)) + return FALSE; + if (!xdr_u_short(xdrs, &objp->sus)) + return FALSE; + if (!xdr_int(xdrs, &objp->si)) + return FALSE; + if (!xdr_u_int(xdrs, &objp->sui)) + return FALSE; + if (!xdr_int64_t(xdrs, &objp->sh)) + return FALSE; + if (!xdr_u_int64_t(xdrs, &objp->suh)) + return FALSE; + if (!xdr_bool(xdrs, &objp->sb)) + return FALSE; + if (!xdr_float(xdrs, &objp->sf)) + return FALSE; + if (!xdr_double(xdrs, &objp->sd)) + return FALSE; + if (!xdr_pointer(xdrs, (char **)&objp->ip, sizeof(int), (xdrproc_t)xdr= _int)) + return FALSE; + if (!xdr_vector(xdrs, (char *)objp->ifa, TestConstDec, + sizeof(int), (xdrproc_t)xdr_int)) + return FALSE; + if (!xdr_array(xdrs, (char **)&objp->iva.iva_val, (u_int *) &objp->iva= .iva_len, TestConstHex, + sizeof(int), (xdrproc_t)xdr_int)) + return FALSE; + if (!xdr_string(xdrs, &objp->stva, TestConstOct)) + return FALSE; + if (!xdr_opaque(xdrs, objp->ofa, 33)) + return FALSE; + if (!xdr_bytes(xdrs, (char **)&objp->ova.ova_val, (u_int *) &objp->ova= .ova_len, 35)) + return FALSE; + if (!xdr_TestEnum(xdrs, &objp->e1)) + return FALSE; + if (!xdr_TestEnum(xdrs, &objp->e2)) + return FALSE; + if (!xdr_pointer(xdrs, (char **)&objp->ep, sizeof(TestEnum), (xdrproc_= t)xdr_TestEnum)) + return FALSE; + if (!xdr_vector(xdrs, (char *)objp->efa, 37, + sizeof(TestEnum), (xdrproc_t)xdr_TestEnum)) + return FALSE; + if (!xdr_array(xdrs, (char **)&objp->eva.eva_val, (u_int *) &objp->eva= .eva_len, 39, + sizeof(TestEnum), (xdrproc_t)xdr_TestEnum)) + return FALSE; + if (!xdr_TestStruct(xdrs, &objp->s)) + return FALSE; + if (!xdr_pointer(xdrs, (char **)&objp->sp, sizeof(TestStruct), (xdrpro= c_t)xdr_TestStruct)) + return FALSE; + if (!xdr_vector(xdrs, (char *)objp->sfa, 41, + sizeof(TestStruct), (xdrproc_t)xdr_TestStruct)) + return FALSE; + if (!xdr_array(xdrs, (char **)&objp->sva.sva_val, (u_int *) &objp->sva= .sva_len, 43, + sizeof(TestStruct), (xdrproc_t)xdr_TestStruct)) + return FALSE; + if (!xdr_TestUnion(xdrs, &objp->u)) + return FALSE; + if (!xdr_pointer(xdrs, (char **)&objp->up, sizeof(TestUnion), (xdrproc= _t)xdr_TestUnion)) + return FALSE; + if (!xdr_vector(xdrs, (char *)objp->ufa, 45, + sizeof(TestUnion), (xdrproc_t)xdr_TestUnion)) + return FALSE; + if (!xdr_array(xdrs, (char **)&objp->uva.uva_val, (u_int *) &objp->uva= .uva_len, 47, + sizeof(TestUnion), (xdrproc_t)xdr_TestUnion)) + return FALSE; + if (!xdr_TestIntScalar(xdrs, &objp->tis)) + return FALSE; + if (!xdr_TestIntPointer(xdrs, &objp->tip)) + return FALSE; + if (!xdr_TestIntFixedArray(xdrs, objp->tifa)) + return FALSE; + if (!xdr_TestIntVariableArray(xdrs, &objp->tiva)) + return FALSE; + if (!xdr_TestStringVariableArray(xdrs, &objp->tstva)) + return FALSE; + if (!xdr_TestOpaqueFixedArray(xdrs, objp->tofa)) + return FALSE; + if (!xdr_TestOpaqueVariableArray(xdrs, &objp->tova)) + return FALSE; + if (!xdr_TestEnumScalar(xdrs, &objp->tes)) + return FALSE; + if (!xdr_TestEnumPointer(xdrs, &objp->tep)) + return FALSE; + if (!xdr_TestEnumFixedArray(xdrs, objp->tefa)) + return FALSE; + if (!xdr_TestEnumVariableArray(xdrs, &objp->teva)) + return FALSE; + if (!xdr_TestStructScalar(xdrs, &objp->tss)) + return FALSE; + if (!xdr_TestStructPointer(xdrs, &objp->tsp)) + return FALSE; + if (!xdr_TestStructFixedArray(xdrs, objp->tsfa)) + return FALSE; + if (!xdr_TestStructVariableArray(xdrs, &objp->tsva)) + return FALSE; + if (!xdr_TestUnionScalar(xdrs, &objp->tu)) + return FALSE; + if (!xdr_TestUnionPointer(xdrs, &objp->tup)) + return FALSE; + if (!xdr_TestUnionFixedArray(xdrs, objp->tufa)) + return FALSE; + if (!xdr_TestUnionVariableArray(xdrs, &objp->tuva)) + return FALSE; + return TRUE; +} diff --git a/scripts/rpcgen/tests/demo.h b/scripts/rpcgen/tests/demo.h new file mode 100644 index 0000000000..6fac61e7e9 --- /dev/null +++ b/scripts/rpcgen/tests/demo.h @@ -0,0 +1,216 @@ +enum TestEnum { + TEST_ENUM_ONE =3D 1, + TEST_ENUM_TWO =3D 2, +}; +typedef enum TestEnum TestEnum; + +struct TestStruct { + char c1; + char c2; +}; +typedef struct TestStruct TestStruct; + +struct TestUnion { + int type; + union { + int i1; + int i2; + int i3; + } TestUnion_u; +}; +typedef struct TestUnion TestUnion; + +struct TestUnionVoidDefault { + int type; + union { + int i1; + int i2; + } TestUnionVoidDefault_u; +}; +typedef struct TestUnionVoidDefault TestUnionVoidDefault; + +struct TestUnionNoDefault { + int type; + union { + int i1; + int i2; + } TestUnionNoDefault_u; +}; +typedef struct TestUnionNoDefault TestUnionNoDefault; + +typedef int TestIntScalar; + +typedef int *TestIntPointer; + +typedef int TestIntFixedArray[3]; + +typedef struct { + u_int TestIntVariableArray_len; + int *TestIntVariableArray_val; +} TestIntVariableArray; + +typedef char *TestStringVariableArray; + +typedef char TestOpaqueFixedArray[9]; + +typedef struct { + u_int TestOpaqueVariableArray_len; + char *TestOpaqueVariableArray_val; +} TestOpaqueVariableArray; + +typedef TestEnum TestEnumScalar; + +typedef TestEnum *TestEnumPointer; + +typedef TestEnum TestEnumFixedArray[13]; + +typedef struct { + u_int TestEnumVariableArray_len; + TestEnum *TestEnumVariableArray_val; +} TestEnumVariableArray; + +typedef TestStruct TestStructScalar; + +typedef TestStruct *TestStructPointer; + +typedef TestStruct TestStructFixedArray[17]; + +typedef struct { + u_int TestStructVariableArray_len; + TestStruct *TestStructVariableArray_val; +} TestStructVariableArray; + +typedef TestUnion TestUnionScalar; + +typedef TestUnion *TestUnionPointer; + +typedef TestUnion TestUnionFixedArray[21]; + +typedef struct { + u_int TestUnionVariableArray_len; + TestUnion *TestUnionVariableArray_val; +} TestUnionVariableArray; + +#define TestConstDec 25 + +#define TestConstHex 0x27 + +#define TestConstOct 031 + +struct TestStructAllTypes { + char sc; + u_char suc; + short ss; + u_short sus; + int si; + u_int sui; + int64_t sh; + uint64_t suh; + bool_t sb; + float sf; + double sd; + int *ip; + int ifa[TestConstDec]; + struct { + u_int iva_len; + int *iva_val; + } iva; + char *stva; + char ofa[33]; + struct { + u_int ova_len; + char *ova_val; + } ova; + TestEnum e1; + TestEnum e2; + TestEnum *ep; + TestEnum efa[37]; + struct { + u_int eva_len; + TestEnum *eva_val; + } eva; + TestStruct s; + TestStruct *sp; + TestStruct sfa[41]; + struct { + u_int sva_len; + TestStruct *sva_val; + } sva; + TestUnion u; + TestUnion *up; + TestUnion ufa[45]; + struct { + u_int uva_len; + TestUnion *uva_val; + } uva; + TestIntScalar tis; + TestIntPointer tip; + TestIntFixedArray tifa; + TestIntVariableArray tiva; + TestStringVariableArray tstva; + TestOpaqueFixedArray tofa; + TestOpaqueVariableArray tova; + TestEnumScalar tes; + TestEnumPointer tep; + TestEnumFixedArray tefa; + TestEnumVariableArray teva; + TestStructScalar tss; + TestStructPointer tsp; + TestStructFixedArray tsfa; + TestStructVariableArray tsva; + TestUnionScalar tu; + TestUnionPointer tup; + TestUnionFixedArray tufa; + TestUnionVariableArray tuva; +}; +typedef struct TestStructAllTypes TestStructAllTypes; + +extern bool_t xdr_TestEnum(XDR *, TestEnum*); + +extern bool_t xdr_TestStruct(XDR *, TestStruct*); + +extern bool_t xdr_TestUnion(XDR *, TestUnion*); + +extern bool_t xdr_TestUnionVoidDefault(XDR *, TestUnionVoidDefault*); + +extern bool_t xdr_TestUnionNoDefault(XDR *, TestUnionNoDefault*); + +extern bool_t xdr_TestIntScalar(XDR *, TestIntScalar*); + +extern bool_t xdr_TestIntPointer(XDR *, TestIntPointer*); + +extern bool_t xdr_TestIntFixedArray(XDR *, TestIntFixedArray); + +extern bool_t xdr_TestIntVariableArray(XDR *, TestIntVariableArray*); + +extern bool_t xdr_TestStringVariableArray(XDR *, TestStringVariableArray*= ); + +extern bool_t xdr_TestOpaqueFixedArray(XDR *, TestOpaqueFixedArray); + +extern bool_t xdr_TestOpaqueVariableArray(XDR *, TestOpaqueVariableArray*= ); + +extern bool_t xdr_TestEnumScalar(XDR *, TestEnumScalar*); + +extern bool_t xdr_TestEnumPointer(XDR *, TestEnumPointer*); + +extern bool_t xdr_TestEnumFixedArray(XDR *, TestEnumFixedArray); + +extern bool_t xdr_TestEnumVariableArray(XDR *, TestEnumVariableArray*); + +extern bool_t xdr_TestStructScalar(XDR *, TestStructScalar*); + +extern bool_t xdr_TestStructPointer(XDR *, TestStructPointer*); + +extern bool_t xdr_TestStructFixedArray(XDR *, TestStructFixedArray); + +extern bool_t xdr_TestStructVariableArray(XDR *, TestStructVariableArray*= ); + +extern bool_t xdr_TestUnionScalar(XDR *, TestUnionScalar*); + +extern bool_t xdr_TestUnionPointer(XDR *, TestUnionPointer*); + +extern bool_t xdr_TestUnionFixedArray(XDR *, TestUnionFixedArray); + +extern bool_t xdr_TestUnionVariableArray(XDR *, TestUnionVariableArray*); + +extern bool_t xdr_TestStructAllTypes(XDR *, TestStructAllTypes*); diff --git a/scripts/rpcgen/tests/demo.x b/scripts/rpcgen/tests/demo.x new file mode 100644 index 0000000000..ec69913f3d --- /dev/null +++ b/scripts/rpcgen/tests/demo.x @@ -0,0 +1,128 @@ +enum TestEnum { + TEST_ENUM_ONE =3D 1, + TEST_ENUM_TWO =3D 2 +}; + +struct TestStruct { + char c1; + char c2; +}; + +union TestUnion switch (int type) { + case 20: + int i1; + case 30: + int i2; + default: + int i3; +}; + +union TestUnionVoidDefault switch (int type) { + case 21: + int i1; + case 31: + int i2; + default: + void; +}; + +union TestUnionNoDefault switch (int type) { + case 22: + int i1; + case 32: + int i2; +}; + +typedef int TestIntScalar; +typedef int *TestIntPointer; +typedef int TestIntFixedArray[3]; +typedef int TestIntVariableArray<5>; + +typedef string TestStringVariableArray<7>; + +typedef opaque TestOpaqueFixedArray[9]; +typedef opaque TestOpaqueVariableArray<11>; + +typedef TestEnum TestEnumScalar; +typedef TestEnum *TestEnumPointer; +typedef TestEnum TestEnumFixedArray[13]; +typedef TestEnum TestEnumVariableArray<15>; + +typedef TestStruct TestStructScalar; +typedef TestStruct *TestStructPointer; +typedef TestStruct TestStructFixedArray[17]; +typedef TestStruct TestStructVariableArray<19>; + +typedef TestUnion TestUnionScalar; +typedef TestUnion *TestUnionPointer; +typedef TestUnion TestUnionFixedArray[21]; +typedef TestUnion TestUnionVariableArray<23>; + +const TestConstDec =3D 25; +const TestConstHex =3D 0x27; +const TestConstOct =3D 031; + +struct TestStructAllTypes { + char sc; + unsigned char suc; + short ss; + unsigned short sus; + int si; + unsigned int sui; + hyper sh; + unsigned hyper suh; + bool sb; + float sf; + double sd; +/* quadruple sq; */ + + int *ip; + int ifa[TestConstDec]; + int iva; + + string stva; + + opaque ofa[33]; + opaque ova<35>; + + TestEnum e1; + TestEnum e2; + TestEnum *ep; + TestEnum efa[37]; + TestEnum eva<39>; + + TestStruct s; + TestStruct *sp; + TestStruct sfa[41]; + TestStruct sva<43>; + + TestUnion u; + TestUnion *up; + TestUnion ufa[45]; + TestUnion uva<47>; + + TestIntScalar tis; + TestIntPointer tip; + TestIntFixedArray tifa; + TestIntVariableArray tiva; + + TestStringVariableArray tstva; + + TestOpaqueFixedArray tofa; + TestOpaqueVariableArray tova; + + TestEnumScalar tes; + TestEnumPointer tep; + TestEnumFixedArray tefa; + TestEnumVariableArray teva; + + TestStructScalar tss; + TestStructPointer tsp; + TestStructFixedArray tsfa; + TestStructVariableArray tsva; + + TestUnionScalar tu; + TestUnionPointer tup; + TestUnionFixedArray tufa; + TestUnionVariableArray tuva; +}; diff --git a/scripts/rpcgen/tests/meson.build b/scripts/rpcgen/tests/meson.= build index 4b1ea308ce..953a3dbede 100644 --- a/scripts/rpcgen/tests/meson.build +++ b/scripts/rpcgen/tests/meson.build @@ -1,4 +1,5 @@ rpcgen_tests =3D files([ + 'test_generator.py', 'test_lexer.py', 'test_parser.py', ]) diff --git a/scripts/rpcgen/tests/test_generator.py b/scripts/rpcgen/tests/= test_generator.py new file mode 100644 index 0000000000..bc7660a6fc --- /dev/null +++ b/scripts/rpcgen/tests/test_generator.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os +from pathlib import Path + +from rpcgen.parser import XDRParser +from rpcgen.generator import ( + XDRTypeDeclarationGenerator, + XDRMarshallDeclarationGenerator, + XDRMarshallImplementationGenerator, +) + + +def test_generate_header(): + x =3D Path(Path(__file__).parent, "demo.x") + h =3D Path(Path(__file__).parent, "demo.h") + with x.open("r") as fp: + parser =3D XDRParser(fp) + spec =3D parser.parse() + + got =3D ( + XDRTypeDeclarationGenerator(spec).visit() + + "\n" + + XDRMarshallDeclarationGenerator(spec).visit() + ) + + with h.open("r") as fp: + want =3D fp.read() + + if "VIR_TEST_REGENERATE_OUTPUT" in os.environ: + want =3D got + with h.open("w") as fp: + fp.write(want) + + assert got =3D=3D want + + +def test_generate_source(): + x =3D Path(Path(__file__).parent, "demo.x") + h =3D Path(Path(__file__).parent, "demo.c") + with x.open("r") as fp: + parser =3D XDRParser(fp) + spec =3D parser.parse() + + got =3D XDRMarshallImplementationGenerator(spec).visit() + + with h.open("r") as fp: + want =3D fp.read() + + if "VIR_TEST_REGENERATE_OUTPUT" in os.environ: + want =3D got + with h.open("w") as fp: + fp.write(want) + + assert got =3D=3D want --=20 2.39.1