tests: Remove code generation, use reflection

This simplifies the code.
This commit is contained in:
Alexander Neumann 2017-05-14 12:56:10 +02:00
parent 403e201e1a
commit 66b4999765
6 changed files with 114 additions and 225 deletions

View file

@ -24,9 +24,9 @@ func remove(t testing.TB, be restic.Backend, h restic.Handle) {
}
}
// BackendBenchmarkLoadFile benchmarks the Load() method of a backend by
// BenchmarkLoadFile benchmarks the Load() method of a backend by
// loading a complete file.
func BackendBenchmarkLoadFile(t *testing.B, s *Suite) {
func (s *Suite) BenchmarkLoadFile(t *testing.B) {
be := s.open(t)
defer s.close(t, be)
@ -64,9 +64,9 @@ func BackendBenchmarkLoadFile(t *testing.B, s *Suite) {
}
}
// BackendBenchmarkLoadPartialFile benchmarks the Load() method of a backend by
// BenchmarkLoadPartialFile benchmarks the Load() method of a backend by
// loading the remainder of a file starting at a given offset.
func BackendBenchmarkLoadPartialFile(t *testing.B, s *Suite) {
func (s *Suite) BenchmarkLoadPartialFile(t *testing.B) {
be := s.open(t)
defer s.close(t, be)
@ -107,9 +107,9 @@ func BackendBenchmarkLoadPartialFile(t *testing.B, s *Suite) {
}
}
// BackendBenchmarkLoadPartialFileOffset benchmarks the Load() method of a
// BenchmarkLoadPartialFileOffset benchmarks the Load() method of a
// backend by loading a number of bytes of a file starting at a given offset.
func BackendBenchmarkLoadPartialFileOffset(t *testing.B, s *Suite) {
func (s *Suite) BenchmarkLoadPartialFileOffset(t *testing.B) {
be := s.open(t)
defer s.close(t, be)
@ -151,8 +151,8 @@ func BackendBenchmarkLoadPartialFileOffset(t *testing.B, s *Suite) {
}
}
// BackendBenchmarkSave benchmarks the Save() method of a backend.
func BackendBenchmarkSave(t *testing.B, s *Suite) {
// BenchmarkSave benchmarks the Save() method of a backend.
func (s *Suite) BenchmarkSave(t *testing.B) {
be := s.open(t)
defer s.close(t, be)

View file

@ -14,6 +14,14 @@
//
// Assuming a *Suite is returned by newTestSuite(), the tests and benchmarks
// can be run like this:
// func newTestSuite(t testing.TB) *test.Suite {
// return &test.Suite{
// Create: func(cfg interface{}) (restic.Backend, error) {
// [...]
// },
// [...]
// }
// }
//
// func TestSuiteBackendMem(t *testing.T) {
// newTestSuite(t).RunTests(t)
@ -23,16 +31,12 @@
// newTestSuite(b).RunBenchmarks(b)
// }
//
// The functions are run in alphabetical order.
//
// Add new tests
//
// A new test or benchmark can be added by implementing a function with a name
// starting with BackendTest or BackendBenchmark. The first parameter is either
// a testing.TB or *testing.T (for tests), or a *testing.B (for benchmarks).
// The second parameter is a pointer to the test suite currently running
// (*Suite).
//
// The list of functions to run is contained in the file funcs.go, which is
// generated by generate_test_list.go. To regenerate that file, use go generate
// restic/backend/test. Test/benchmark functions are run in the order they are
// defined.
// A new test or benchmark can be added by implementing a method on *Suite
// with the name starting with "Test" and a single *testing.T parameter for
// test. For benchmarks, the name must start with "Benchmark" and the parameter
// is a *testing.B
package test

View file

@ -1,31 +0,0 @@
// DO NOT EDIT, AUTOMATICALLY GENERATED
package test
import (
"testing"
)
var testFunctions = []struct {
Name string
Fn func(testing.TB, *Suite)
}{
{"CreateWithConfig", BackendTestCreateWithConfig},
{"Location", BackendTestLocation},
{"Config", BackendTestConfig},
{"Load", BackendTestLoad},
{"Save", BackendTestSave},
{"SaveFilenames", BackendTestSaveFilenames},
{"Backend", BackendTestBackend},
{"Delete", BackendTestDelete},
}
var benchmarkFunctions = []struct {
Name string
Fn func(*testing.B, *Suite)
}{
{"LoadFile", BackendBenchmarkLoadFile},
{"LoadPartialFile", BackendBenchmarkLoadPartialFile},
{"LoadPartialFileOffset", BackendBenchmarkLoadPartialFileOffset},
{"Save", BackendBenchmarkSave},
}

View file

@ -1,152 +0,0 @@
// +build ignore
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"go/format"
"io"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
"unicode"
"unicode/utf8"
)
var data struct {
Package string
TestFuncs []string
BenchmarkFuncs []string
}
var testTemplate = `
// DO NOT EDIT, AUTOMATICALLY GENERATED
package {{ .Package }}
import (
"testing"
)
var testFunctions = []struct {
Name string
Fn func(testing.TB, *Suite)
}{
{{ range $f := .TestFuncs -}}
{"{{ $f }}", BackendTest{{ $f }},},
{{ end }}
}
var benchmarkFunctions = []struct {
Name string
Fn func(*testing.B, *Suite)
}{
{{ range $f := .BenchmarkFuncs -}}
{"{{ $f }}", BackendBenchmark{{ $f }},},
{{ end }}
}
`
var testFiles = flag.String("testfiles", "tests.go,benchmarks.go", "files to search test functions in (comma separated)")
var outputFile = flag.String("output", "funcs.go", "output file to write generated code to")
var packageName = flag.String("package", "", "the package name to use")
var prefix = flag.String("prefix", "", "test function prefix")
var quiet = flag.Bool("quiet", false, "be quiet")
func errx(err error) {
if err == nil {
return
}
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
var testFuncRegex = regexp.MustCompile(`^func\s+BackendTest(.+)\s*\(`)
var benchmarkFuncRegex = regexp.MustCompile(`^func\s+BackendBenchmark(.+)\s*\(`)
func findFunctions() (testFuncs, benchmarkFuncs []string) {
for _, filename := range strings.Split(*testFiles, ",") {
f, err := os.Open(filename)
errx(err)
sc := bufio.NewScanner(f)
for sc.Scan() {
match := testFuncRegex.FindStringSubmatch(sc.Text())
if len(match) > 0 {
testFuncs = append(testFuncs, match[1])
}
match = benchmarkFuncRegex.FindStringSubmatch(sc.Text())
if len(match) > 0 {
benchmarkFuncs = append(benchmarkFuncs, match[1])
}
}
if err := sc.Err(); err != nil {
log.Fatalf("Error scanning file: %v", err)
}
errx(f.Close())
}
return testFuncs, benchmarkFuncs
}
func generateOutput(wr io.Writer, data interface{}) {
t := template.Must(template.New("backendtest").Parse(testTemplate))
buf := bytes.NewBuffer(nil)
errx(t.Execute(buf, data))
source, err := format.Source(buf.Bytes())
errx(err)
_, err = wr.Write(source)
errx(err)
}
func packageTestFunctionPrefix(pkg string) string {
if pkg == "" {
return ""
}
r, n := utf8.DecodeRuneInString(pkg)
return string(unicode.ToUpper(r)) + pkg[n:]
}
func init() {
flag.Parse()
}
func main() {
dir, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Getwd() %v\n", err)
os.Exit(1)
}
pkg := *packageName
if pkg == "" {
pkg = filepath.Base(dir)
}
f, err := os.Create(*outputFile)
errx(err)
data.Package = pkg
data.TestFuncs, data.BenchmarkFuncs = findFunctions()
generateOutput(f, data)
errx(f.Close())
if !*quiet {
fmt.Printf("wrote backend tests for package %v to %v\n", data.Package, *outputFile)
}
}

View file

@ -1,8 +1,10 @@
package test
import (
"reflect"
"restic"
"restic/test"
"strings"
"testing"
)
@ -38,10 +40,8 @@ func (s *Suite) RunTests(t *testing.T) {
be := s.create(t)
s.close(t, be)
for _, test := range testFunctions {
t.Run(test.Name, func(t *testing.T) {
test.Fn(t, s)
})
for _, test := range s.testFuncs(t) {
t.Run(test.Name, test.Fn)
}
if !test.TestCleanupTempDirs {
@ -54,6 +54,76 @@ func (s *Suite) RunTests(t *testing.T) {
}
}
type testFunction struct {
Name string
Fn func(*testing.T)
}
func (s *Suite) testFuncs(t testing.TB) (funcs []testFunction) {
tpe := reflect.TypeOf(s)
v := reflect.ValueOf(s)
for i := 0; i < tpe.NumMethod(); i++ {
methodType := tpe.Method(i)
name := methodType.Name
// discard functions which do not have the right name
if !strings.HasPrefix(name, "Test") {
continue
}
iface := v.Method(i).Interface()
f, ok := iface.(func(*testing.T))
if !ok {
t.Logf("warning: function %v of *Suite has the wrong signature for a test function\nwant: func(*testing.T),\nhave: %T",
name, iface)
continue
}
funcs = append(funcs, testFunction{
Name: name,
Fn: f,
})
}
return funcs
}
type benchmarkFunction struct {
Name string
Fn func(*testing.B)
}
func (s *Suite) benchmarkFuncs(t testing.TB) (funcs []benchmarkFunction) {
tpe := reflect.TypeOf(s)
v := reflect.ValueOf(s)
for i := 0; i < tpe.NumMethod(); i++ {
methodType := tpe.Method(i)
name := methodType.Name
// discard functions which do not have the right name
if !strings.HasPrefix(name, "Benchmark") {
continue
}
iface := v.Method(i).Interface()
f, ok := iface.(func(*testing.B))
if !ok {
t.Logf("warning: function %v of *Suite has the wrong signature for a test function\nwant: func(*testing.T),\nhave: %T",
name, iface)
continue
}
funcs = append(funcs, benchmarkFunction{
Name: name,
Fn: f,
})
}
return funcs
}
// RunBenchmarks executes all defined benchmarks as subtests of b.
func (s *Suite) RunBenchmarks(b *testing.B) {
var err error
@ -66,10 +136,8 @@ func (s *Suite) RunBenchmarks(b *testing.B) {
be := s.create(b)
s.close(b, be)
for _, test := range benchmarkFunctions {
b.Run(test.Name, func(b *testing.B) {
test.Fn(b, s)
})
for _, test := range s.benchmarkFuncs(b) {
b.Run(test.Name, test.Fn)
}
if !test.TestCleanupTempDirs {

View file

@ -19,9 +19,9 @@ import (
"restic/backend"
)
// BackendTestCreateWithConfig tests that creating a backend in a location which already
// TestCreateWithConfig tests that creating a backend in a location which already
// has a config file fails.
func BackendTestCreateWithConfig(t testing.TB, s *Suite) {
func (s *Suite) TestCreateWithConfig(t *testing.T) {
b := s.open(t)
defer s.close(t, b)
@ -52,8 +52,8 @@ func BackendTestCreateWithConfig(t testing.TB, s *Suite) {
}
}
// BackendTestLocation tests that a location string is returned.
func BackendTestLocation(t testing.TB, s *Suite) {
// TestLocation tests that a location string is returned.
func (s *Suite) TestLocation(t *testing.T) {
b := s.open(t)
defer s.close(t, b)
@ -63,8 +63,8 @@ func BackendTestLocation(t testing.TB, s *Suite) {
}
}
// BackendTestConfig saves and loads a config from the backend.
func BackendTestConfig(t testing.TB, s *Suite) {
// TestConfig saves and loads a config from the backend.
func (s *Suite) TestConfig(t *testing.T) {
b := s.open(t)
defer s.close(t, b)
@ -99,8 +99,8 @@ func BackendTestConfig(t testing.TB, s *Suite) {
remove(t, b, restic.Handle{Type: restic.ConfigFile})
}
// BackendTestLoad tests the backend's Load function.
func BackendTestLoad(t testing.TB, s *Suite) {
// TestLoad tests the backend's Load function.
func (s *Suite) TestLoad(t *testing.T) {
b := s.open(t)
defer s.close(t, b)
@ -232,8 +232,8 @@ func (ec errorCloser) Size() int64 {
return ec.size
}
// BackendTestSave tests saving data in the backend.
func BackendTestSave(t testing.TB, s *Suite) {
// TestSave tests saving data in the backend.
func (s *Suite) TestSave(t *testing.T) {
b := s.open(t)
defer s.close(t, b)
var id restic.ID
@ -332,8 +332,8 @@ var filenameTests = []struct {
},
}
// BackendTestSaveFilenames tests saving data with various file names in the backend.
func BackendTestSaveFilenames(t testing.TB, s *Suite) {
// TestSaveFilenames tests saving data with various file names in the backend.
func (s *Suite) TestSaveFilenames(t *testing.T) {
b := s.open(t)
defer s.close(t, b)
@ -381,8 +381,8 @@ func store(t testing.TB, b restic.Backend, tpe restic.FileType, data []byte) res
return h
}
// BackendTestBackend tests all functions of the backend.
func BackendTestBackend(t testing.TB, s *Suite) {
// TestBackend tests all functions of the backend.
func (s *Suite) TestBackend(t *testing.T) {
b := s.open(t)
defer s.close(t, b)
@ -515,8 +515,8 @@ func BackendTestBackend(t testing.TB, s *Suite) {
}
}
// BackendTestDelete tests the Delete function.
func BackendTestDelete(t testing.TB, s *Suite) {
// TestDelete tests the Delete function.
func (s *Suite) TestDelete(t *testing.T) {
if !test.TestCleanupTempDirs {
t.Skipf("not removing backend, TestCleanupTempDirs is false")
}