| // Copyright 2016 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Command vomforever is a tool that searches for bugs in vom. |
| // It vom encodes and decodes in a loop and reports the errors that are found. |
| package main |
| |
| import ( |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "math/rand" |
| "os" |
| "os/exec" |
| "path" |
| "strings" |
| "v.io/x/lib/textutil" |
| |
| "v.io/v23/vdl" |
| "v.io/v23/vdl/vdltest" |
| "v.io/v23/vom" |
| "v.io/x/ref/lib/vdl/codegen/vdlgen" |
| ) |
| |
| const ( |
| numValuesPerTypeList = 100 |
| countBeforeIncreasingDepth = 1000 |
| ) |
| |
| var ( |
| verboseFlag = flag.Bool("v", false, "verbose output") |
| modeFlag = flag.String("mode", "generated", "test mode") |
| ) |
| |
| type mode int |
| |
| const ( |
| vdlValue mode = iota |
| reflect |
| generated |
| ) |
| |
| const vdlPackageName = "v.io/v23/vom" |
| |
| type entry struct { |
| Value *vdl.Value |
| Types []*vdl.Type |
| } |
| |
| func genEntries() chan entry { |
| typegen := vdltest.NewTypeGenerator() |
| out := make(chan entry, 1) |
| go func() { |
| depth := 0 |
| for i := 0; ; i++ { |
| if i%countBeforeIncreasingDepth == 0 { |
| depth++ |
| } |
| types := typegen.Gen(depth) |
| valgen := vdltest.NewValueGenerator(types) |
| modes := []vdltest.GenMode{vdltest.GenFull, vdltest.GenPosMax, vdltest.GenNegMax, vdltest.GenPosMin, vdltest.GenNegMin, vdltest.GenRandom} |
| for i := 0; i < numValuesPerTypeList; i++ { |
| out <- entry{ |
| Value: valgen.Gen(types[rand.Intn(len(types))], modes[rand.Intn(len(modes))]), |
| Types: types, |
| } |
| } |
| } |
| }() |
| return out |
| } |
| |
| func main() { |
| flag.Parse() |
| var mode mode |
| switch *modeFlag { |
| case "reflect": |
| mode = reflect |
| case "generated": |
| mode = generated |
| case "vdl-value": |
| mode = vdlValue |
| default: |
| fmt.Printf(`invalid mode %q. expected "reflect", "vdl-value" or "generated"`, *modeFlag) |
| } |
| for vv := range genEntries() { |
| if *verboseFlag { |
| fmt.Printf("testing %v\n", vv) |
| } |
| run(vv, mode) |
| |
| } |
| } |
| |
| func run(entry entry, mode mode) { |
| fmt.Printf("Testing %v\n", entry.Value) |
| switch mode { |
| case reflect: |
| buildAndRunFromVdlFile(entry.Value, entry.Types, []string{"-skip-gen-old-target", "-skip-gen-write-read"}) |
| case generated: |
| buildAndRunFromVdlFile(entry.Value, entry.Types, []string{"-skip-gen-old-target"}) |
| case vdlValue: |
| runVdlValue(entry.Value) |
| } |
| } |
| |
| func writef(w io.Writer, format string, args ...interface{}) { |
| _, err := fmt.Fprintf(w, format, args...) |
| panicOnError(err) |
| } |
| |
| func createFile(name string) (*os.File, func()) { |
| file, err := os.Create(name) |
| panicOnError(err) |
| return file, func() { panicOnError(file.Close()) } |
| } |
| |
| func panicOnError(err error) { |
| if err != nil { |
| panic(err) |
| } |
| } |
| |
| func buildAndRunFromVdlFile(vv *vdl.Value, types []*vdl.Type, vdlFlags []string) { |
| dir, err := ioutil.TempDir("", "vomforever") |
| defer os.RemoveAll(dir) |
| panicOnError(err) |
| |
| // Write the type and value vdl files and the go test file. |
| gopath := path.Join(dir, "go") |
| pkgpath := path.Join("v.io", "v23", "vom", "vomtest", "vomforever", "tmp") |
| pkgdir := path.Join(gopath, "src", pkgpath) |
| panicOnError(os.MkdirAll(pkgdir, os.ModePerm)) |
| writeTypeFile(path.Join(pkgdir, "types.vdl"), types, vv) |
| writeValueFile(path.Join(pkgdir, "value.vdl"), vv) |
| writeTestFile(path.Join(pkgdir, "tmp_test.go"), vv) |
| |
| // Run vdl generate. |
| vdlFlags = append(vdlFlags, pkgpath) |
| vdlFlags = append([]string{"generate"}, vdlFlags...) |
| cmd := exec.Command("vdl", vdlFlags...) |
| cmd.Env = []string{ |
| "VDLPATH=" + path.Join(gopath, "src"), |
| } |
| stderr, err := cmd.StderrPipe() |
| panicOnError(err) |
| stdout, err := cmd.StdoutPipe() |
| panicOnError(err) |
| go func() { |
| io.Copy(os.Stderr, stderr) |
| }() |
| go func() { |
| io.Copy(os.Stdout, stdout) |
| }() |
| panicOnError(cmd.Run()) |
| |
| // Run the test. |
| cmd = exec.Command("jiri", "run", "go", "test", pkgpath) |
| cmd.Env = []string{ |
| "GOPATH=" + gopath, |
| "PATH=" + os.Getenv("PATH"), |
| "JIRI_ROOT=" + os.Getenv("JIRI_ROOT"), |
| } |
| stderr, err = cmd.StderrPipe() |
| panicOnError(err) |
| stdout, err = cmd.StdoutPipe() |
| panicOnError(err) |
| go func() { |
| io.Copy(os.Stderr, stderr) |
| }() |
| go func() { |
| io.Copy(os.Stdout, stdout) |
| }() |
| panicOnError(cmd.Run()) |
| } |
| |
| func writeValueFile(filename string, vv *vdl.Value) { |
| file, cleanup := createFile(filename) |
| defer cleanup() |
| writef(file, `package tmp |
| |
| `) |
| switch vv.Kind() { |
| case vdl.Any, vdl.TypeObject: |
| writef(file, "const X = %[1]s", vdlgen.TypedConst(vv, vdlPackageName, nil)) |
| case vdl.Optional: |
| writef(file, "const X = XType{%[1]s}", vdlgen.TypedConst(vv, vdlPackageName, nil)) |
| default: |
| writef(file, "const X = XType(%[1]s)", vdlgen.TypedConst(vv, vdlPackageName, nil)) |
| } |
| } |
| |
| func writeTypeFile(filename string, types []*vdl.Type, vv *vdl.Value) { |
| file, cleanup := createFile(filename) |
| defer cleanup() |
| writef(file, "package tmp\n\n") |
| comment := textutil.PrefixLineWriter(file, "// ") |
| panicOnError(vdltest.PrintTypeStats(comment, types...)) |
| writef(comment, "\nOnly named types appear below, by definition.\n") |
| panicOnError(comment.Flush()) |
| writef(file, "\ntype (\n") |
| for _, tt := range types { |
| if tt.Name() != "" { |
| base := vdlgen.BaseType(tt, vdlPackageName, nil) |
| base = strings.Replace(base, "\n", "\n\t", -1) |
| writef(file, "\t%[1]s %[2]s\n", tt.Name(), base) |
| } |
| } |
| switch vv.Kind() { |
| case vdl.Any, vdl.TypeObject: |
| case vdl.Optional: |
| base := vdlgen.BaseType(vv.Type(), vdlPackageName, nil) |
| base = strings.Replace(base, "\n", "\n\t", -1) |
| writef(file, "\tXType struct{\n\t\tValue %[1]s\n\t}\n", base) |
| default: |
| base := vdlgen.BaseType(vv.Type(), vdlPackageName, nil) |
| base = strings.Replace(base, "\n", "\n\t", -1) |
| writef(file, "\tXType %[1]s\n", base) |
| } |
| writef(file, ")\n") |
| } |
| |
| func writeTestFile(filename string, vv *vdl.Value) { |
| file, cleanup := createFile(filename) |
| defer cleanup() |
| var typename = "XType" |
| switch vv.Kind() { |
| case vdl.Any: |
| typename = "*vom.RawBytes" |
| case vdl.TypeObject: |
| typename = "*vdl.Type" |
| } |
| writef(file, `package tmp |
| |
| import ( |
| "testing" |
| |
| "v.io/v23/vdl" |
| "v.io/v23/vom" |
| ) |
| |
| func TestEncodeDecode(t *testing.T) { |
| bytes, err := vom.Encode(X) |
| if err != nil { |
| t.Errorf("error in encode: %%v", err) |
| } |
| var out %[1]s |
| if err := vom.Decode(bytes, &out); err != nil { |
| t.Fatalf("error in decode: %%v", err) |
| } |
| if !vdl.DeepEqual(out, X) { |
| t.Errorf("got: %%#v, want: %%#v", out, X) |
| } |
| }`, typename) |
| } |
| |
| func runVdlValue(vv *vdl.Value) { |
| bytes, err := vom.Encode(vv) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "%v: error on encode %v\n", vv, err) |
| return |
| } |
| var vvOut *vdl.Value |
| if err := vom.Decode(bytes, &vvOut); err != nil { |
| fmt.Fprintf(os.Stderr, "%v: error on decode %v\n", vv, err) |
| return |
| } |
| if !vdl.EqualValue(vv, vvOut) { |
| fmt.Fprintf(os.Stderr, "%v: decoded value %v did not match input\n", vv, vvOut) |
| } |
| } |