gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/go_marshal/gomarshal/generator_tests.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gomarshal
    16  
    17  import (
    18  	"fmt"
    19  	"go/ast"
    20  	"io"
    21  	"strings"
    22  )
    23  
    24  var standardImports = []string{
    25  	"bytes",
    26  	"fmt",
    27  	"reflect",
    28  	"testing",
    29  
    30  	"gvisor.dev/gvisor/tools/go_marshal/analysis",
    31  }
    32  
    33  var sliceAPIImports = []string{
    34  	"encoding/binary",
    35  	"gvisor.dev/gvisor/pkg/hostarch",
    36  }
    37  
    38  type testGenerator struct {
    39  	sourceBuffer
    40  
    41  	// The type we're serializing.
    42  	t *ast.TypeSpec
    43  
    44  	// Receiver argument for generated methods.
    45  	r string
    46  
    47  	// Imports used by generated code.
    48  	imports *importTable
    49  
    50  	// Import statement for the package declaring the type we generated code
    51  	// for. We need this to construct test instances for the type, since the
    52  	// tests aren't written in the same package.
    53  	decl *importStmt
    54  }
    55  
    56  func newTestGenerator(t *ast.TypeSpec, r string) *testGenerator {
    57  	g := &testGenerator{
    58  		t:       t,
    59  		r:       r,
    60  		imports: newImportTable(),
    61  	}
    62  
    63  	for _, i := range standardImports {
    64  		g.imports.add(i).markUsed()
    65  	}
    66  	// These imports are used if a type requests the slice API. Don't
    67  	// mark them as used by default.
    68  	for _, i := range sliceAPIImports {
    69  		g.imports.add(i)
    70  	}
    71  
    72  	return g
    73  }
    74  
    75  func (g *testGenerator) typeName() string {
    76  	return g.t.Name.Name
    77  }
    78  
    79  func (g *testGenerator) testFuncName(base string) string {
    80  	return fmt.Sprintf("%s%s", base, strings.Title(g.t.Name.Name))
    81  }
    82  
    83  func (g *testGenerator) inTestFunction(name string, body func()) {
    84  	g.emit("func %s(t *testing.T) {\n", g.testFuncName(name))
    85  	g.inIndent(body)
    86  	g.emit("}\n\n")
    87  }
    88  
    89  func (g *testGenerator) emitTestNonZeroSize() {
    90  	g.inTestFunction("TestSizeNonZero", func() {
    91  		g.emit("var x %v\n", g.typeName())
    92  		g.emit("if x.SizeBytes() == 0 {\n")
    93  		g.inIndent(func() {
    94  			g.emit("t.Fatal(\"Marshallable.SizeBytes() should not return zero\")\n")
    95  		})
    96  		g.emit("}\n")
    97  	})
    98  }
    99  
   100  func (g *testGenerator) emitTestSuspectAlignment() {
   101  	g.inTestFunction("TestSuspectAlignment", func() {
   102  		g.emit("var x %v\n", g.typeName())
   103  		g.emit("analysis.AlignmentCheck(t, reflect.TypeOf(x))\n")
   104  	})
   105  }
   106  
   107  func (g *testGenerator) emitTestMarshalUnmarshalPreservesData() {
   108  	g.inTestFunction("TestSafeMarshalUnmarshalPreservesData", func() {
   109  		g.emit("var x, y, z, yUnsafe, zUnsafe %s\n", g.typeName())
   110  		g.emit("analysis.RandomizeValue(&x)\n\n")
   111  
   112  		g.emit("buf := make([]byte, x.SizeBytes())\n")
   113  		g.emit("x.MarshalBytes(buf)\n")
   114  		g.emit("bufUnsafe := make([]byte, x.SizeBytes())\n")
   115  		g.emit("x.MarshalUnsafe(bufUnsafe)\n\n")
   116  
   117  		g.emit("y.UnmarshalBytes(buf)\n")
   118  		g.emit("if !reflect.DeepEqual(x, y) {\n")
   119  		g.inIndent(func() {
   120  			g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across MarshalBytes/UnmarshalBytes cycle:\\nBefore: %+v\\nAfter: %+v\\n\", x, y))\n")
   121  		})
   122  		g.emit("}\n")
   123  		g.emit("yUnsafe.UnmarshalBytes(bufUnsafe)\n")
   124  		g.emit("if !reflect.DeepEqual(x, yUnsafe) {\n")
   125  		g.inIndent(func() {
   126  			g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across MarshalUnsafe/UnmarshalBytes cycle:\\nBefore: %+v\\nAfter: %+v\\n\", x, yUnsafe))\n")
   127  		})
   128  		g.emit("}\n\n")
   129  
   130  		g.emit("z.UnmarshalUnsafe(buf)\n")
   131  		g.emit("if !reflect.DeepEqual(x, z) {\n")
   132  		g.inIndent(func() {
   133  			g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across MarshalBytes/UnmarshalUnsafe cycle:\\nBefore: %+v\\nAfter: %+v\\n\", x, z))\n")
   134  		})
   135  		g.emit("}\n")
   136  		g.emit("zUnsafe.UnmarshalUnsafe(bufUnsafe)\n")
   137  		g.emit("if !reflect.DeepEqual(x, zUnsafe) {\n")
   138  		g.inIndent(func() {
   139  			g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across MarshalUnsafe/UnmarshalUnsafe cycle:\\nBefore: %+v\\nAfter: %+v\\n\", x, zUnsafe))\n")
   140  		})
   141  		g.emit("}\n")
   142  	})
   143  }
   144  
   145  func (g *testGenerator) emitTestMarshalUnmarshalSlicePreservesData(slice *sliceAPI) {
   146  	for _, name := range []string{"binary", "hostarch"} {
   147  		if !g.imports.markUsed(name) {
   148  			panic(fmt.Sprintf("Generated test for '%s' referenced a non-existent import with local name '%s'", g.typeName(), name))
   149  		}
   150  	}
   151  
   152  	g.inTestFunction("TestSafeMarshalUnmarshalSlicePreservesData", func() {
   153  		g.emit("var x, y, yUnsafe [8]%s\n", g.typeName())
   154  		g.emit("analysis.RandomizeValue(&x)\n\n")
   155  		g.emit("size := (*%s)(nil).SizeBytes() * len(x)\n", g.typeName())
   156  		g.emit("buf := bytes.NewBuffer(make([]byte, size))\n")
   157  		g.emit("buf.Reset()\n")
   158  		g.emit("if err := binary.Write(buf, hostarch.ByteOrder, x[:]); err != nil {\n")
   159  		g.inIndent(func() {
   160  			g.emit("t.Fatal(fmt.Sprintf(\"binary.Write failed: %v\", err))\n")
   161  		})
   162  		g.emit("}\n")
   163  		g.emit("bufUnsafe := make([]byte, size)\n")
   164  		g.emit("MarshalUnsafe%s(x[:], bufUnsafe)\n\n", slice.ident)
   165  
   166  		g.emit("UnmarshalUnsafe%s(y[:], buf.Bytes())\n", slice.ident)
   167  		g.emit("if !reflect.DeepEqual(x, y) {\n")
   168  		g.inIndent(func() {
   169  			g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across binary.Write/UnmarshalUnsafeSlice cycle:\\nBefore: %+v\\nAfter: %+v\\n\", x, y))\n")
   170  		})
   171  		g.emit("}\n")
   172  		g.emit("UnmarshalUnsafe%s(yUnsafe[:], bufUnsafe)\n", slice.ident)
   173  		g.emit("if !reflect.DeepEqual(x, yUnsafe) {\n")
   174  		g.inIndent(func() {
   175  			g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across MarshalUnsafeSlice/UnmarshalUnsafeSlice cycle:\\nBefore: %+v\\nAfter: %+v\\n\", x, yUnsafe))\n")
   176  		})
   177  		g.emit("}\n\n")
   178  	})
   179  }
   180  
   181  func (g *testGenerator) emitTestWriteToUnmarshalPreservesData() {
   182  	g.inTestFunction("TestWriteToUnmarshalPreservesData", func() {
   183  		g.emit("var x, y, yUnsafe %s\n", g.typeName())
   184  		g.emit("analysis.RandomizeValue(&x)\n\n")
   185  
   186  		g.emit("var buf bytes.Buffer\n\n")
   187  
   188  		g.emit("x.WriteTo(&buf)\n")
   189  		g.emit("y.UnmarshalBytes(buf.Bytes())\n\n")
   190  		g.emit("yUnsafe.UnmarshalUnsafe(buf.Bytes())\n\n")
   191  
   192  		g.emit("if !reflect.DeepEqual(x, y) {\n")
   193  		g.inIndent(func() {
   194  			g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across WriteTo/UnmarshalBytes cycle:\\nBefore: %+v\\nAfter: %+v\\n\", x, y))\n")
   195  		})
   196  		g.emit("}\n")
   197  		g.emit("if !reflect.DeepEqual(x, yUnsafe) {\n")
   198  		g.inIndent(func() {
   199  			g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across WriteTo/UnmarshalUnsafe cycle:\\nBefore: %+v\\nAfter: %+v\\n\", x, yUnsafe))\n")
   200  		})
   201  		g.emit("}\n")
   202  	})
   203  }
   204  
   205  func (g *testGenerator) emitTestSizeBytesOnTypedNilPtr() {
   206  	g.inTestFunction("TestSizeBytesOnTypedNilPtr", func() {
   207  		g.emit("var x %s\n", g.typeName())
   208  		g.emit("sizeFromConcrete := x.SizeBytes()\n")
   209  		g.emit("sizeFromTypedNilPtr := (*%s)(nil).SizeBytes()\n\n", g.typeName())
   210  
   211  		g.emit("if sizeFromTypedNilPtr != sizeFromConcrete {\n")
   212  		g.inIndent(func() {
   213  			g.emit("t.Fatalf(\"SizeBytes() on typed nil pointer (%v) doesn't match size returned by a concrete object (%v).\\n\", sizeFromTypedNilPtr, sizeFromConcrete)\n")
   214  		})
   215  		g.emit("}\n")
   216  	})
   217  }
   218  
   219  func (g *testGenerator) emitTestBoundCheck() {
   220  	g.inTestFunction("TestCheckedMethods", func() {
   221  		g.emit("var x %s\n", g.typeName())
   222  		g.emit("size := x.SizeBytes()\n")
   223  		g.emit("b := make([]byte, size)\n\n")
   224  
   225  		g.emit("if _, ok := x.CheckedMarshal(b[:size-1]); ok {\n")
   226  		g.inIndent(func() {
   227  			g.emit("t.Errorf(\"CheckedMarshal should have failed because buffer is small\")\n")
   228  		})
   229  		g.emit("}\n")
   230  		g.emit("if _, ok := x.CheckedMarshal(b); !ok {\n")
   231  		g.inIndent(func() {
   232  			g.emit("t.Errorf(\"CheckedMarshal should have succeeded because buffer size is okay\")\n")
   233  		})
   234  		g.emit("}\n\n")
   235  
   236  		g.emit("if _, ok := x.CheckedUnmarshal(b[:size-1]); ok {\n")
   237  		g.inIndent(func() {
   238  			g.emit("t.Errorf(\"CheckedUnmarshal should have failed because buffer is small\")\n")
   239  		})
   240  		g.emit("}\n")
   241  		g.emit("if _, ok := x.CheckedUnmarshal(b); !ok {\n")
   242  		g.inIndent(func() {
   243  			g.emit("t.Errorf(\"CheckedUnmarshal should have succeeded because buffer size is okay\")\n")
   244  		})
   245  		g.emit("}\n")
   246  	})
   247  }
   248  
   249  func (g *testGenerator) emitTests(slice *sliceAPI, boundCheck bool) {
   250  	g.emitTestNonZeroSize()
   251  	g.emitTestSuspectAlignment()
   252  	g.emitTestMarshalUnmarshalPreservesData()
   253  	g.emitTestWriteToUnmarshalPreservesData()
   254  	g.emitTestSizeBytesOnTypedNilPtr()
   255  
   256  	if slice != nil {
   257  		g.emitTestMarshalUnmarshalSlicePreservesData(slice)
   258  	}
   259  	if boundCheck {
   260  		g.emitTestBoundCheck()
   261  	}
   262  }
   263  
   264  func (g *testGenerator) write(out io.Writer) error {
   265  	return g.sourceBuffer.write(out)
   266  }