go-marshal tests should respect build tags.

Previously, the go-marshal-generated tests did not respect build
tags. This can cause the test to unbuildable under some build
configurations, as the original types the tests refer to may not be
defined.

This CL copies the build tags from the input files to the test,
similar to the generated library; however test packages have an
additional constraint. A test package cannot be totally empty
(i.e. have no test/example/benchmark defined), otherwise the go
compiler returns an error. To ensure the generated test package always
contains a testable entity under all build configurations, we now emit
an extra test file with no build tags that contains a single no-op
example.

PiperOrigin-RevId: 334496821
This commit is contained in:
Rahat Mahmood 2020-09-29 17:27:16 -07:00 committed by gVisor bot
parent 6ae83404af
commit fee2c07728
4 changed files with 69 additions and 35 deletions

View File

@ -214,7 +214,10 @@ def go_library(name, srcs, deps = [], imports = [], stateify = True, marshal = F
for (suffix, _) in marshal_sets.items():
_go_test(
name = name + suffix + "_abi_autogen_test",
srcs = [name + suffix + "_abi_autogen_test.go"],
srcs = [
name + suffix + "_abi_autogen_test.go",
name + suffix + "_abi_autogen_unconditional_test.go",
],
library = ":" + name,
deps = marshal_test_deps,
**kwargs

View File

@ -4,11 +4,13 @@ def _go_marshal_impl(ctx):
"""Execute the go_marshal tool."""
output = ctx.outputs.lib
output_test = ctx.outputs.test
output_test_unconditional = ctx.outputs.test_unconditional
# Run the marshal command.
args = ["-output=%s" % output.path]
args += ["-pkg=%s" % ctx.attr.package]
args += ["-output_test=%s" % output_test.path]
args.append("-pkg=%s" % ctx.attr.package)
args.append("-output_test=%s" % output_test.path)
args.append("-output_test_unconditional=%s" % output_test_unconditional.path)
if ctx.attr.debug:
args += ["-debug"]
@ -18,7 +20,7 @@ def _go_marshal_impl(ctx):
args += [f.path for f in src.files.to_list()]
ctx.actions.run(
inputs = ctx.files.srcs,
outputs = [output, output_test],
outputs = [output, output_test, output_test_unconditional],
mnemonic = "GoMarshal",
progress_message = "go_marshal: %s" % ctx.label,
arguments = args,
@ -48,6 +50,7 @@ go_marshal = rule(
outputs = {
"lib": "%{name}_unsafe.go",
"test": "%{name}_test.go",
"test_unconditional": "%{name}_unconditional_test.go",
},
)

View File

@ -68,6 +68,8 @@ type Generator struct {
output *os.File
// Output file to write generated tests.
outputTest *os.File
// Output file to write unconditionally generated tests.
outputTestUC *os.File
// Package name for the generated file.
pkg string
// Set of extra packages to import in the generated file.
@ -75,7 +77,7 @@ type Generator struct {
}
// NewGenerator creates a new code Generator.
func NewGenerator(srcs []string, out, outTest, pkg string, imports []string) (*Generator, error) {
func NewGenerator(srcs []string, out, outTest, outTestUnconditional, pkg string, imports []string) (*Generator, error) {
f, err := os.OpenFile(out, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return nil, fmt.Errorf("Couldn't open output file %q: %v", out, err)
@ -84,10 +86,15 @@ func NewGenerator(srcs []string, out, outTest, pkg string, imports []string) (*G
if err != nil {
return nil, fmt.Errorf("Couldn't open test output file %q: %v", out, err)
}
fTestUC, err := os.OpenFile(outTestUnconditional, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return nil, fmt.Errorf("Couldn't open unconditional test output file %q: %v", out, err)
}
g := Generator{
inputs: srcs,
output: f,
outputTest: fTest,
outputTestUC: fTestUC,
pkg: pkg,
imports: newImportTable(),
}
@ -454,6 +461,46 @@ func (g *Generator) Run() error {
// source file.
func (g *Generator) writeTests(ts []*testGenerator) error {
var b sourceBuffer
// Write the unconditional test file. This file is always compiled,
// regardless of what build tags were specified on the original input
// files. We use this file to guarantee we never end up with an empty test
// file, as that causes the build to fail with "no tests/benchmarks/examples
// found".
//
// There's no easy way to determine ahead of time if we'll end up with an
// empty build file since build constraints can arbitrarily cause some of
// the original types to be not defined. We also have no way to tell bazel
// to omit the entire test suite since the output files are already defined
// before go-marshal is called.
b.emit("// Automatically generated marshal tests. See tools/go_marshal.\n\n")
b.emit("package %s\n\n", g.pkg)
b.emit("func Example() {\n")
b.inIndent(func() {
b.emit("// This example is intentionally empty, and ensures this package contains at\n")
b.emit("// least one testable entity. go-marshal is forced to emit a test package if the\n")
b.emit("// input package is marked marshallable, but emitting no testable entities \n")
b.emit("// results in a build failure.\n")
})
b.emit("}\n")
if err := b.write(g.outputTestUC); err != nil {
return err
}
// Now generate the real test file that contains the real types we
// processed. These need to be conditionally compiled according to the build
// tags, as the original types may not be defined under all build
// configurations.
b.reset()
b.emit("// Automatically generated marshal tests. See tools/go_marshal.\n\n")
// Emit build tags.
if t := tags.Aggregate(g.inputs); len(t) > 0 {
b.emit(strings.Join(t.Lines(), "\n"))
b.emit("\n\n")
}
b.emit("package %s\n\n", g.pkg)
if err := b.write(g.outputTest); err != nil {
return err
@ -470,26 +517,6 @@ func (g *Generator) writeTests(ts []*testGenerator) error {
}
// Write test functions.
// If we didn't generate any Marshallable implementations, we can't just
// emit an empty test file, since that causes the build to fail with "no
// tests/benchmarks/examples found". Unfortunately we can't signal bazel to
// omit the entire package since the outputs are already defined before
// go-marshal is called. If we'd otherwise emit an empty test suite, emit an
// empty example instead.
if len(ts) == 0 {
b.reset()
b.emit("func Example() {\n")
b.inIndent(func() {
b.emit("// This example is intentionally empty to ensure this file contains at least\n")
b.emit("// one testable entity. go-marshal is forced to emit a test file if a package\n")
b.emit("// is marked marshallable, but emitting a test file with no entities results\n")
b.emit("// in a build failure.\n")
})
b.emit("}\n")
return b.write(g.outputTest)
}
for _, t := range ts {
if err := t.write(g.outputTest); err != nil {
return err

View File

@ -34,6 +34,7 @@ var (
pkg = flag.String("pkg", "", "output package")
output = flag.String("output", "", "output file")
outputTest = flag.String("output_test", "", "output file for tests")
outputTestUnconditional = flag.String("output_test_unconditional", "", "output file for unconditional tests")
imports = flag.String("imports", "", "comma-separated list of extra packages to import in generated code")
)
@ -61,7 +62,7 @@ func main() {
// as an import.
extraImports = strings.Split(*imports, ",")
}
g, err := gomarshal.NewGenerator(flag.Args(), *output, *outputTest, *pkg, extraImports)
g, err := gomarshal.NewGenerator(flag.Args(), *output, *outputTest, *outputTestUnconditional, *pkg, extraImports)
if err != nil {
panic(err)
}