syncthing/vendor/github.com/gogo/protobuf/plugin/testgen/testgen.go

607 lines
18 KiB
Go
Raw Normal View History

// Copyright (c) 2013, Vastech SA (PTY) LTD. All rights reserved.
// http://github.com/gogo/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/*
The testgen plugin generates Test and Benchmark functions for each message.
Tests are enabled using the following extensions:
- testgen
- testgen_all
Benchmarks are enabled using the following extensions:
- benchgen
- benchgen_all
Let us look at:
github.com/gogo/protobuf/test/example/example.proto
Btw all the output can be seen at:
github.com/gogo/protobuf/test/example/*
The following message:
option (gogoproto.testgen_all) = true;
option (gogoproto.benchgen_all) = true;
message A {
optional string Description = 1 [(gogoproto.nullable) = false];
optional int64 Number = 2 [(gogoproto.nullable) = false];
optional bytes Id = 3 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uuid", (gogoproto.nullable) = false];
}
given to the testgen plugin, will generate the following test code:
func TestAProto(t *testing.T) {
popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano()))
p := NewPopulatedA(popr, false)
data, err := github_com_gogo_protobuf_proto.Marshal(p)
if err != nil {
panic(err)
}
msg := &A{}
if err := github_com_gogo_protobuf_proto.Unmarshal(data, msg); err != nil {
panic(err)
}
for i := range data {
data[i] = byte(popr.Intn(256))
}
if err := p.VerboseEqual(msg); err != nil {
t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err)
}
if !p.Equal(msg) {
t.Fatalf("%#v !Proto %#v", msg, p)
}
}
func BenchmarkAProtoMarshal(b *testing.B) {
popr := math_rand.New(math_rand.NewSource(616))
total := 0
pops := make([]*A, 10000)
for i := 0; i < 10000; i++ {
pops[i] = NewPopulatedA(popr, false)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
data, err := github_com_gogo_protobuf_proto.Marshal(pops[i%10000])
if err != nil {
panic(err)
}
total += len(data)
}
b.SetBytes(int64(total / b.N))
}
func BenchmarkAProtoUnmarshal(b *testing.B) {
popr := math_rand.New(math_rand.NewSource(616))
total := 0
datas := make([][]byte, 10000)
for i := 0; i < 10000; i++ {
data, err := github_com_gogo_protobuf_proto.Marshal(NewPopulatedA(popr, false))
if err != nil {
panic(err)
}
datas[i] = data
}
msg := &A{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
total += len(datas[i%10000])
if err := github_com_gogo_protobuf_proto.Unmarshal(datas[i%10000], msg); err != nil {
panic(err)
}
}
b.SetBytes(int64(total / b.N))
}
func TestAJSON(t *testing1.T) {
popr := math_rand1.New(math_rand1.NewSource(time1.Now().UnixNano()))
p := NewPopulatedA(popr, true)
jsondata, err := encoding_json.Marshal(p)
if err != nil {
panic(err)
}
msg := &A{}
err = encoding_json.Unmarshal(jsondata, msg)
if err != nil {
panic(err)
}
if err := p.VerboseEqual(msg); err != nil {
t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err)
}
if !p.Equal(msg) {
t.Fatalf("%#v !Json Equal %#v", msg, p)
}
}
func TestAProtoText(t *testing2.T) {
popr := math_rand2.New(math_rand2.NewSource(time2.Now().UnixNano()))
p := NewPopulatedA(popr, true)
data := github_com_gogo_protobuf_proto1.MarshalTextString(p)
msg := &A{}
if err := github_com_gogo_protobuf_proto1.UnmarshalText(data, msg); err != nil {
panic(err)
}
if err := p.VerboseEqual(msg); err != nil {
t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err)
}
if !p.Equal(msg) {
t.Fatalf("%#v !Proto %#v", msg, p)
}
}
func TestAProtoCompactText(t *testing2.T) {
popr := math_rand2.New(math_rand2.NewSource(time2.Now().UnixNano()))
p := NewPopulatedA(popr, true)
data := github_com_gogo_protobuf_proto1.CompactTextString(p)
msg := &A{}
if err := github_com_gogo_protobuf_proto1.UnmarshalText(data, msg); err != nil {
panic(err)
}
if err := p.VerboseEqual(msg); err != nil {
t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err)
}
if !p.Equal(msg) {
t.Fatalf("%#v !Proto %#v", msg, p)
}
}
Other registered tests are also generated.
Tests are registered to this test plugin by calling the following function.
func RegisterTestPlugin(newFunc NewTestPlugin)
where NewTestPlugin is:
type NewTestPlugin func(g *generator.Generator) TestPlugin
and TestPlugin is an interface:
type TestPlugin interface {
Generate(imports generator.PluginImports, file *generator.FileDescriptor) (used bool)
}
Plugins that use this interface include:
- populate
- gostring
- equal
- union
- and more
Please look at these plugins as examples of how to create your own.
A good idea is to let each plugin generate its own tests.
*/
package testgen
import (
"github.com/gogo/protobuf/gogoproto"
"github.com/gogo/protobuf/protoc-gen-gogo/generator"
)
type TestPlugin interface {
Generate(imports generator.PluginImports, file *generator.FileDescriptor) (used bool)
}
type NewTestPlugin func(g *generator.Generator) TestPlugin
var testplugins = make([]NewTestPlugin, 0)
func RegisterTestPlugin(newFunc NewTestPlugin) {
testplugins = append(testplugins, newFunc)
}
type plugin struct {
*generator.Generator
generator.PluginImports
tests []TestPlugin
}
func NewPlugin() *plugin {
return &plugin{}
}
func (p *plugin) Name() string {
return "testgen"
}
func (p *plugin) Init(g *generator.Generator) {
p.Generator = g
p.tests = make([]TestPlugin, 0, len(testplugins))
for i := range testplugins {
p.tests = append(p.tests, testplugins[i](g))
}
}
func (p *plugin) Generate(file *generator.FileDescriptor) {
p.PluginImports = generator.NewPluginImports(p.Generator)
atLeastOne := false
for i := range p.tests {
used := p.tests[i].Generate(p.PluginImports, file)
if used {
atLeastOne = true
}
}
if atLeastOne {
p.P(`//These tests are generated by github.com/gogo/protobuf/plugin/testgen`)
}
}
type testProto struct {
*generator.Generator
}
func newProto(g *generator.Generator) TestPlugin {
return &testProto{g}
}
func (p *testProto) Generate(imports generator.PluginImports, file *generator.FileDescriptor) bool {
used := false
testingPkg := imports.NewImport("testing")
randPkg := imports.NewImport("math/rand")
timePkg := imports.NewImport("time")
protoPkg := imports.NewImport("github.com/gogo/protobuf/proto")
if !gogoproto.ImportsGoGoProto(file.FileDescriptorProto) {
protoPkg = imports.NewImport("github.com/golang/protobuf/proto")
}
for _, message := range file.Messages() {
ccTypeName := generator.CamelCaseSlice(message.TypeName())
if message.DescriptorProto.GetOptions().GetMapEntry() {
continue
}
if gogoproto.HasTestGen(file.FileDescriptorProto, message.DescriptorProto) {
used = true
p.P(`func Test`, ccTypeName, `Proto(t *`, testingPkg.Use(), `.T) {`)
p.In()
p.P(`seed := `, timePkg.Use(), `.Now().UnixNano()`)
p.P(`popr := `, randPkg.Use(), `.New(`, randPkg.Use(), `.NewSource(seed))`)
p.P(`p := NewPopulated`, ccTypeName, `(popr, false)`)
p.P(`data, err := `, protoPkg.Use(), `.Marshal(p)`)
p.P(`if err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, err = %v", seed, err)`)
p.Out()
p.P(`}`)
p.P(`msg := &`, ccTypeName, `{}`)
p.P(`if err := `, protoPkg.Use(), `.Unmarshal(data, msg); err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, err = %v", seed, err)`)
p.Out()
p.P(`}`)
p.P(`littlefuzz := make([]byte, len(data))`)
p.P(`copy(littlefuzz, data)`)
p.P(`for i := range data {`)
p.In()
p.P(`data[i] = byte(popr.Intn(256))`)
p.Out()
p.P(`}`)
if gogoproto.HasVerboseEqual(file.FileDescriptorProto, message.DescriptorProto) {
p.P(`if err := p.VerboseEqual(msg); err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !VerboseProto %#v, since %v", seed, msg, p, err)`)
p.Out()
p.P(`}`)
}
p.P(`if !p.Equal(msg) {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)`)
p.Out()
p.P(`}`)
p.P(`if len(littlefuzz) > 0 {`)
p.In()
p.P(`fuzzamount := 100`)
p.P(`for i := 0; i < fuzzamount; i++ {`)
p.In()
p.P(`littlefuzz[popr.Intn(len(littlefuzz))] = byte(popr.Intn(256))`)
p.P(`littlefuzz = append(littlefuzz, byte(popr.Intn(256)))`)
p.Out()
p.P(`}`)
p.P(`// shouldn't panic`)
p.P(`_ = `, protoPkg.Use(), `.Unmarshal(littlefuzz, msg)`)
p.Out()
p.P(`}`)
p.Out()
p.P(`}`)
p.P()
}
if gogoproto.HasTestGen(file.FileDescriptorProto, message.DescriptorProto) {
if gogoproto.IsMarshaler(file.FileDescriptorProto, message.DescriptorProto) || gogoproto.IsUnsafeMarshaler(file.FileDescriptorProto, message.DescriptorProto) {
p.P(`func Test`, ccTypeName, `MarshalTo(t *`, testingPkg.Use(), `.T) {`)
p.In()
p.P(`seed := `, timePkg.Use(), `.Now().UnixNano()`)
p.P(`popr := `, randPkg.Use(), `.New(`, randPkg.Use(), `.NewSource(seed))`)
p.P(`p := NewPopulated`, ccTypeName, `(popr, false)`)
if gogoproto.IsProtoSizer(file.FileDescriptorProto, message.DescriptorProto) {
p.P(`size := p.ProtoSize()`)
} else {
p.P(`size := p.Size()`)
}
p.P(`data := make([]byte, size)`)
p.P(`for i := range data {`)
p.In()
p.P(`data[i] = byte(popr.Intn(256))`)
p.Out()
p.P(`}`)
p.P(`_, err := p.MarshalTo(data)`)
p.P(`if err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, err = %v", seed, err)`)
p.Out()
p.P(`}`)
p.P(`msg := &`, ccTypeName, `{}`)
p.P(`if err := `, protoPkg.Use(), `.Unmarshal(data, msg); err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, err = %v", seed, err)`)
p.Out()
p.P(`}`)
p.P(`for i := range data {`)
p.In()
p.P(`data[i] = byte(popr.Intn(256))`)
p.Out()
p.P(`}`)
if gogoproto.HasVerboseEqual(file.FileDescriptorProto, message.DescriptorProto) {
p.P(`if err := p.VerboseEqual(msg); err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !VerboseProto %#v, since %v", seed, msg, p, err)`)
p.Out()
p.P(`}`)
}
p.P(`if !p.Equal(msg) {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)`)
p.Out()
p.P(`}`)
p.Out()
p.P(`}`)
p.P()
}
}
if gogoproto.HasBenchGen(file.FileDescriptorProto, message.DescriptorProto) {
used = true
p.P(`func Benchmark`, ccTypeName, `ProtoMarshal(b *`, testingPkg.Use(), `.B) {`)
p.In()
p.P(`popr := `, randPkg.Use(), `.New(`, randPkg.Use(), `.NewSource(616))`)
p.P(`total := 0`)
p.P(`pops := make([]*`, ccTypeName, `, 10000)`)
p.P(`for i := 0; i < 10000; i++ {`)
p.In()
p.P(`pops[i] = NewPopulated`, ccTypeName, `(popr, false)`)
p.Out()
p.P(`}`)
p.P(`b.ResetTimer()`)
p.P(`for i := 0; i < b.N; i++ {`)
p.In()
p.P(`data, err := `, protoPkg.Use(), `.Marshal(pops[i%10000])`)
p.P(`if err != nil {`)
p.In()
p.P(`panic(err)`)
p.Out()
p.P(`}`)
p.P(`total += len(data)`)
p.Out()
p.P(`}`)
p.P(`b.SetBytes(int64(total / b.N))`)
p.Out()
p.P(`}`)
p.P()
p.P(`func Benchmark`, ccTypeName, `ProtoUnmarshal(b *`, testingPkg.Use(), `.B) {`)
p.In()
p.P(`popr := `, randPkg.Use(), `.New(`, randPkg.Use(), `.NewSource(616))`)
p.P(`total := 0`)
p.P(`datas := make([][]byte, 10000)`)
p.P(`for i := 0; i < 10000; i++ {`)
p.In()
p.P(`data, err := `, protoPkg.Use(), `.Marshal(NewPopulated`, ccTypeName, `(popr, false))`)
p.P(`if err != nil {`)
p.In()
p.P(`panic(err)`)
p.Out()
p.P(`}`)
p.P(`datas[i] = data`)
p.Out()
p.P(`}`)
p.P(`msg := &`, ccTypeName, `{}`)
p.P(`b.ResetTimer()`)
p.P(`for i := 0; i < b.N; i++ {`)
p.In()
p.P(`total += len(datas[i%10000])`)
p.P(`if err := `, protoPkg.Use(), `.Unmarshal(datas[i%10000], msg); err != nil {`)
p.In()
p.P(`panic(err)`)
p.Out()
p.P(`}`)
p.Out()
p.P(`}`)
p.P(`b.SetBytes(int64(total / b.N))`)
p.Out()
p.P(`}`)
p.P()
}
}
return used
}
type testJson struct {
*generator.Generator
}
func newJson(g *generator.Generator) TestPlugin {
return &testJson{g}
}
func (p *testJson) Generate(imports generator.PluginImports, file *generator.FileDescriptor) bool {
used := false
testingPkg := imports.NewImport("testing")
randPkg := imports.NewImport("math/rand")
timePkg := imports.NewImport("time")
jsonPkg := imports.NewImport("github.com/gogo/protobuf/jsonpb")
for _, message := range file.Messages() {
ccTypeName := generator.CamelCaseSlice(message.TypeName())
if message.DescriptorProto.GetOptions().GetMapEntry() {
continue
}
if gogoproto.HasTestGen(file.FileDescriptorProto, message.DescriptorProto) {
used = true
p.P(`func Test`, ccTypeName, `JSON(t *`, testingPkg.Use(), `.T) {`)
p.In()
p.P(`seed := `, timePkg.Use(), `.Now().UnixNano()`)
p.P(`popr := `, randPkg.Use(), `.New(`, randPkg.Use(), `.NewSource(seed))`)
p.P(`p := NewPopulated`, ccTypeName, `(popr, true)`)
p.P(`marshaler := `, jsonPkg.Use(), `.Marshaler{}`)
p.P(`jsondata, err := marshaler.MarshalToString(p)`)
p.P(`if err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, err = %v", seed, err)`)
p.Out()
p.P(`}`)
p.P(`msg := &`, ccTypeName, `{}`)
p.P(`err = `, jsonPkg.Use(), `.UnmarshalString(jsondata, msg)`)
p.P(`if err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, err = %v", seed, err)`)
p.Out()
p.P(`}`)
if gogoproto.HasVerboseEqual(file.FileDescriptorProto, message.DescriptorProto) {
p.P(`if err := p.VerboseEqual(msg); err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !VerboseProto %#v, since %v", seed, msg, p, err)`)
p.Out()
p.P(`}`)
}
p.P(`if !p.Equal(msg) {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p)`)
p.Out()
p.P(`}`)
p.Out()
p.P(`}`)
}
}
return used
}
type testText struct {
*generator.Generator
}
func newText(g *generator.Generator) TestPlugin {
return &testText{g}
}
func (p *testText) Generate(imports generator.PluginImports, file *generator.FileDescriptor) bool {
used := false
testingPkg := imports.NewImport("testing")
randPkg := imports.NewImport("math/rand")
timePkg := imports.NewImport("time")
protoPkg := imports.NewImport("github.com/gogo/protobuf/proto")
if !gogoproto.ImportsGoGoProto(file.FileDescriptorProto) {
protoPkg = imports.NewImport("github.com/golang/protobuf/proto")
}
//fmtPkg := imports.NewImport("fmt")
for _, message := range file.Messages() {
ccTypeName := generator.CamelCaseSlice(message.TypeName())
if message.DescriptorProto.GetOptions().GetMapEntry() {
continue
}
if gogoproto.HasTestGen(file.FileDescriptorProto, message.DescriptorProto) {
used = true
p.P(`func Test`, ccTypeName, `ProtoText(t *`, testingPkg.Use(), `.T) {`)
p.In()
p.P(`seed := `, timePkg.Use(), `.Now().UnixNano()`)
p.P(`popr := `, randPkg.Use(), `.New(`, randPkg.Use(), `.NewSource(seed))`)
p.P(`p := NewPopulated`, ccTypeName, `(popr, true)`)
p.P(`data := `, protoPkg.Use(), `.MarshalTextString(p)`)
p.P(`msg := &`, ccTypeName, `{}`)
p.P(`if err := `, protoPkg.Use(), `.UnmarshalText(data, msg); err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, err = %v", seed, err)`)
p.Out()
p.P(`}`)
if gogoproto.HasVerboseEqual(file.FileDescriptorProto, message.DescriptorProto) {
p.P(`if err := p.VerboseEqual(msg); err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !VerboseProto %#v, since %v", seed, msg, p, err)`)
p.Out()
p.P(`}`)
}
p.P(`if !p.Equal(msg) {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)`)
p.Out()
p.P(`}`)
p.Out()
p.P(`}`)
p.P()
p.P(`func Test`, ccTypeName, `ProtoCompactText(t *`, testingPkg.Use(), `.T) {`)
p.In()
p.P(`seed := `, timePkg.Use(), `.Now().UnixNano()`)
p.P(`popr := `, randPkg.Use(), `.New(`, randPkg.Use(), `.NewSource(seed))`)
p.P(`p := NewPopulated`, ccTypeName, `(popr, true)`)
p.P(`data := `, protoPkg.Use(), `.CompactTextString(p)`)
p.P(`msg := &`, ccTypeName, `{}`)
p.P(`if err := `, protoPkg.Use(), `.UnmarshalText(data, msg); err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, err = %v", seed, err)`)
p.Out()
p.P(`}`)
if gogoproto.HasVerboseEqual(file.FileDescriptorProto, message.DescriptorProto) {
p.P(`if err := p.VerboseEqual(msg); err != nil {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !VerboseProto %#v, since %v", seed, msg, p, err)`)
p.Out()
p.P(`}`)
}
p.P(`if !p.Equal(msg) {`)
p.In()
p.P(`t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)`)
p.Out()
p.P(`}`)
p.Out()
p.P(`}`)
p.P()
}
}
return used
}
func init() {
RegisterTestPlugin(newProto)
RegisterTestPlugin(newJson)
RegisterTestPlugin(newText)
}