From f142b1c22f6296ea4099362704c7618e9d538057 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 May 2017 21:12:29 +0200 Subject: [PATCH 01/12] tests: Add benchmarks to test suite --- src/restic/backend/test/funcs.go | 5 ++ src/restic/backend/test/generate_test_list.go | 51 ++++++++++++------- src/restic/backend/test/tests.go | 28 ++++++++++ 3 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/restic/backend/test/funcs.go b/src/restic/backend/test/funcs.go index c27d23f3b..9cfbbe707 100644 --- a/src/restic/backend/test/funcs.go +++ b/src/restic/backend/test/funcs.go @@ -19,3 +19,8 @@ var testFunctions = []struct { {"Backend", BackendTestBackend}, {"Delete", BackendTestDelete}, } + +var benchmarkFunctions = []struct { + Name string + Fn func(t testing.TB, suite *Suite) +}{} diff --git a/src/restic/backend/test/generate_test_list.go b/src/restic/backend/test/generate_test_list.go index 30b310e73..5bdd43515 100644 --- a/src/restic/backend/test/generate_test_list.go +++ b/src/restic/backend/test/generate_test_list.go @@ -4,12 +4,13 @@ package main import ( "bufio" + "bytes" "flag" "fmt" + "go/format" "io" "log" "os" - "os/exec" "path/filepath" "regexp" "text/template" @@ -18,8 +19,9 @@ import ( ) var data struct { - Package string - Funcs []string + Package string + TestFuncs []string + BenchmarkFuncs []string } var testTemplate = ` @@ -35,10 +37,19 @@ var testFunctions = []struct { Name string Fn func(t testing.TB, suite *Suite) }{ -{{ range $f := .Funcs -}} +{{ range $f := .TestFuncs -}} {"{{ $f }}", BackendTest{{ $f }},}, {{ end }} } + +var benchmarkFunctions = []struct { + Name string + Fn func(t testing.TB, suite *Suite) +}{ +{{ range $f := .BenchmarkFuncs -}} + {"{{ $f }}", BackendBenchmark{{ $f }},}, +{{ end }} +} ` var testFile = flag.String("testfile", "tests.go", "file to search test functions in") @@ -56,17 +67,23 @@ func errx(err error) { os.Exit(1) } -var funcRegex = regexp.MustCompile(`^func\s+BackendTest(.+)\s*\(`) +var testFuncRegex = regexp.MustCompile(`^func\s+BackendTest(.+)\s*\(`) +var benchmarkFuncRegex = regexp.MustCompile(`^func\s+BackendBenchmark(.+)\s*\(`) -func findTestFunctions() (funcs []string) { +func findFunctions() (testFuncs, benchmarkFuncs []string) { f, err := os.Open(*testFile) errx(err) sc := bufio.NewScanner(f) for sc.Scan() { - match := funcRegex.FindStringSubmatch(sc.Text()) + match := testFuncRegex.FindStringSubmatch(sc.Text()) if len(match) > 0 { - funcs = append(funcs, match[1]) + testFuncs = append(testFuncs, match[1]) + } + + match = benchmarkFuncRegex.FindStringSubmatch(sc.Text()) + if len(match) > 0 { + benchmarkFuncs = append(benchmarkFuncs, match[1]) } } @@ -75,20 +92,20 @@ func findTestFunctions() (funcs []string) { } errx(f.Close()) - return funcs + return testFuncs, benchmarkFuncs } func generateOutput(wr io.Writer, data interface{}) { t := template.Must(template.New("backendtest").Parse(testTemplate)) - cmd := exec.Command("gofmt") - cmd.Stdout = wr - in, err := cmd.StdinPipe() + buf := bytes.NewBuffer(nil) + errx(t.Execute(buf, data)) + + source, err := format.Source(buf.Bytes()) + errx(err) + + _, err = wr.Write(source) errx(err) - errx(cmd.Start()) - errx(t.Execute(in, data)) - errx(in.Close()) - errx(cmd.Wait()) } func packageTestFunctionPrefix(pkg string) string { @@ -120,7 +137,7 @@ func main() { errx(err) data.Package = pkg - data.Funcs = findTestFunctions() + data.TestFuncs, data.BenchmarkFuncs = findFunctions() generateOutput(f, data) errx(f.Close()) diff --git a/src/restic/backend/test/tests.go b/src/restic/backend/test/tests.go index 65b7d8978..51d7ec215 100644 --- a/src/restic/backend/test/tests.go +++ b/src/restic/backend/test/tests.go @@ -68,6 +68,34 @@ func (s *Suite) RunTests(t *testing.T) { } } +// RunBenchmarks executes all defined benchmarks as subtests of b. +func (s *Suite) RunBenchmarks(b *testing.B) { + var err error + s.Config, err = s.NewConfig() + if err != nil { + b.Fatal(err) + } + + // test create/open functions first + be := s.create(b) + s.close(b, be) + + for _, test := range benchmarkFunctions { + b.Run(test.Name, func(b *testing.B) { + test.Fn(b, s) + }) + } + + if !test.TestCleanupTempDirs { + b.Logf("not cleaning up backend") + return + } + + if err = s.Cleanup(s.Config); err != nil { + b.Fatal(err) + } +} + func (s *Suite) create(t testing.TB) restic.Backend { be, err := s.Create(s.Config) if err != nil { From 77ebb95d3dc852409295dad2df2c19465326f2f6 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 May 2017 21:37:07 +0200 Subject: [PATCH 02/12] tests: Add BenchmarkLoadFile --- src/restic/backend/test/benchmarks.go | 59 ++++++++++ src/restic/backend/test/funcs.go | 8 +- src/restic/backend/test/generate_test_list.go | 40 ++++--- src/restic/backend/test/suite.go | 106 ++++++++++++++++++ src/restic/backend/test/tests.go | 99 ---------------- src/restic/backend/test/tests_test.go | 14 ++- 6 files changed, 202 insertions(+), 124 deletions(-) create mode 100644 src/restic/backend/test/benchmarks.go create mode 100644 src/restic/backend/test/suite.go diff --git a/src/restic/backend/test/benchmarks.go b/src/restic/backend/test/benchmarks.go new file mode 100644 index 000000000..19cfc468f --- /dev/null +++ b/src/restic/backend/test/benchmarks.go @@ -0,0 +1,59 @@ +package test + +import ( + "bytes" + "io" + "restic" + "restic/test" + "testing" +) + +// BackendBenchmarkLoad benchmarks the backend's Load function. +func BackendBenchmarkLoadFile(t *testing.B, s *Suite) { + be := s.open(t) + defer s.close(t, be) + + length := 1<<24 + 2123 + data := test.Random(23, length) + id := restic.Hash(data) + handle := restic.Handle{Type: restic.DataFile, Name: id.String()} + if err := be.Save(handle, bytes.NewReader(data)); err != nil { + t.Fatalf("Save() error: %+v", err) + } + + defer func() { + if err := be.Remove(handle); err != nil { + t.Fatalf("Remove() returned error: %v", err) + } + }() + + buf := make([]byte, length) + + t.SetBytes(int64(length)) + t.ResetTimer() + + for i := 0; i < t.N; i++ { + rd, err := be.Load(handle, 0, 0) + if err != nil { + t.Fatal(err) + } + + n, err := io.ReadFull(rd, buf) + if err != nil { + t.Fatal(err) + } + + if err = rd.Close(); err != nil { + t.Fatalf("Close() returned error: %v", err) + } + + if n != length { + t.Fatalf("wrong number of bytes read: want %v, got %v", length, n) + } + + if !bytes.Equal(data, buf) { + t.Fatalf("wrong bytes returned") + } + + } +} diff --git a/src/restic/backend/test/funcs.go b/src/restic/backend/test/funcs.go index 9cfbbe707..b3f2f7bd3 100644 --- a/src/restic/backend/test/funcs.go +++ b/src/restic/backend/test/funcs.go @@ -8,7 +8,7 @@ import ( var testFunctions = []struct { Name string - Fn func(t testing.TB, suite *Suite) + Fn func(testing.TB, *Suite) }{ {"CreateWithConfig", BackendTestCreateWithConfig}, {"Location", BackendTestLocation}, @@ -22,5 +22,7 @@ var testFunctions = []struct { var benchmarkFunctions = []struct { Name string - Fn func(t testing.TB, suite *Suite) -}{} + Fn func(*testing.B, *Suite) +}{ + {"LoadFile", BackendBenchmarkLoadFile}, +} diff --git a/src/restic/backend/test/generate_test_list.go b/src/restic/backend/test/generate_test_list.go index 5bdd43515..98ae06fe4 100644 --- a/src/restic/backend/test/generate_test_list.go +++ b/src/restic/backend/test/generate_test_list.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "regexp" + "strings" "text/template" "unicode" "unicode/utf8" @@ -35,7 +36,7 @@ import ( var testFunctions = []struct { Name string - Fn func(t testing.TB, suite *Suite) + Fn func(testing.TB, *Suite) }{ {{ range $f := .TestFuncs -}} {"{{ $f }}", BackendTest{{ $f }},}, @@ -44,7 +45,7 @@ var testFunctions = []struct { var benchmarkFunctions = []struct { Name string - Fn func(t testing.TB, suite *Suite) + Fn func(*testing.B, *Suite) }{ {{ range $f := .BenchmarkFuncs -}} {"{{ $f }}", BackendBenchmark{{ $f }},}, @@ -52,7 +53,7 @@ var benchmarkFunctions = []struct { } ` -var testFile = flag.String("testfile", "tests.go", "file to search test functions in") +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") @@ -71,27 +72,30 @@ var testFuncRegex = regexp.MustCompile(`^func\s+BackendTest(.+)\s*\(`) var benchmarkFuncRegex = regexp.MustCompile(`^func\s+BackendBenchmark(.+)\s*\(`) func findFunctions() (testFuncs, benchmarkFuncs []string) { - f, err := os.Open(*testFile) - errx(err) + 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]) + 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]) + } } - 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()) } - if err := sc.Err(); err != nil { - log.Fatalf("Error scanning file: %v", err) - } - - errx(f.Close()) return testFuncs, benchmarkFuncs } diff --git a/src/restic/backend/test/suite.go b/src/restic/backend/test/suite.go new file mode 100644 index 000000000..f7409aa4c --- /dev/null +++ b/src/restic/backend/test/suite.go @@ -0,0 +1,106 @@ +package test + +import ( + "restic" + "restic/test" + "testing" +) + +// Suite implements a test suite for restic backends. +type Suite struct { + Config interface{} + + // NewConfig returns a config for a new temporary backend that will be used in tests. + NewConfig func() (interface{}, error) + + // CreateFn is a function that creates a temporary repository for the tests. + Create func(cfg interface{}) (restic.Backend, error) + + // OpenFn is a function that opens a previously created temporary repository. + Open func(cfg interface{}) (restic.Backend, error) + + // CleanupFn removes data created during the tests. + Cleanup func(cfg interface{}) error + + // MinimalData instructs the tests to not use excessive data. + MinimalData bool +} + +// RunTests executes all defined tests as subtests of t. +func (s *Suite) RunTests(t *testing.T) { + var err error + s.Config, err = s.NewConfig() + if err != nil { + t.Fatal(err) + } + + // test create/open functions first + be := s.create(t) + s.close(t, be) + + for _, test := range testFunctions { + t.Run(test.Name, func(t *testing.T) { + test.Fn(t, s) + }) + } + + if !test.TestCleanupTempDirs { + t.Logf("not cleaning up backend") + return + } + + if err = s.Cleanup(s.Config); err != nil { + t.Fatal(err) + } +} + +// RunBenchmarks executes all defined benchmarks as subtests of b. +func (s *Suite) RunBenchmarks(b *testing.B) { + var err error + s.Config, err = s.NewConfig() + if err != nil { + b.Fatal(err) + } + + // test create/open functions first + be := s.create(b) + s.close(b, be) + + for _, test := range benchmarkFunctions { + b.Run(test.Name, func(b *testing.B) { + test.Fn(b, s) + }) + } + + if !test.TestCleanupTempDirs { + b.Logf("not cleaning up backend") + return + } + + if err = s.Cleanup(s.Config); err != nil { + b.Fatal(err) + } +} + +func (s *Suite) create(t testing.TB) restic.Backend { + be, err := s.Create(s.Config) + if err != nil { + t.Fatal(err) + } + return be +} + +func (s *Suite) open(t testing.TB) restic.Backend { + be, err := s.Open(s.Config) + if err != nil { + t.Fatal(err) + } + return be +} + +func (s *Suite) close(t testing.TB, be restic.Backend) { + err := be.Close() + if err != nil { + t.Fatal(err) + } +} diff --git a/src/restic/backend/test/tests.go b/src/restic/backend/test/tests.go index 51d7ec215..ff7d77549 100644 --- a/src/restic/backend/test/tests.go +++ b/src/restic/backend/test/tests.go @@ -20,105 +20,6 @@ import ( "restic/backend" ) -// Suite implements a test suite for restic backends. -type Suite struct { - Config interface{} - - // NewConfig returns a config for a new temporary backend that will be used in tests. - NewConfig func() (interface{}, error) - - // CreateFn is a function that creates a temporary repository for the tests. - Create func(cfg interface{}) (restic.Backend, error) - - // OpenFn is a function that opens a previously created temporary repository. - Open func(cfg interface{}) (restic.Backend, error) - - // CleanupFn removes data created during the tests. - Cleanup func(cfg interface{}) error - - // MinimalData instructs the tests to not use excessive data. - MinimalData bool -} - -// RunTests executes all defined tests as subtests of t. -func (s *Suite) RunTests(t *testing.T) { - var err error - s.Config, err = s.NewConfig() - if err != nil { - t.Fatal(err) - } - - // test create/open functions first - be := s.create(t) - s.close(t, be) - - for _, test := range testFunctions { - t.Run(test.Name, func(t *testing.T) { - test.Fn(t, s) - }) - } - - if !test.TestCleanupTempDirs { - t.Logf("not cleaning up backend") - return - } - - if err = s.Cleanup(s.Config); err != nil { - t.Fatal(err) - } -} - -// RunBenchmarks executes all defined benchmarks as subtests of b. -func (s *Suite) RunBenchmarks(b *testing.B) { - var err error - s.Config, err = s.NewConfig() - if err != nil { - b.Fatal(err) - } - - // test create/open functions first - be := s.create(b) - s.close(b, be) - - for _, test := range benchmarkFunctions { - b.Run(test.Name, func(b *testing.B) { - test.Fn(b, s) - }) - } - - if !test.TestCleanupTempDirs { - b.Logf("not cleaning up backend") - return - } - - if err = s.Cleanup(s.Config); err != nil { - b.Fatal(err) - } -} - -func (s *Suite) create(t testing.TB) restic.Backend { - be, err := s.Create(s.Config) - if err != nil { - t.Fatal(err) - } - return be -} - -func (s *Suite) open(t testing.TB) restic.Backend { - be, err := s.Open(s.Config) - if err != nil { - t.Fatal(err) - } - return be -} - -func (s *Suite) close(t testing.TB, be restic.Backend) { - err := be.Close() - if err != nil { - t.Fatal(err) - } -} - // BackendTestCreateWithConfig tests that creating a backend in a location which already // has a config file fails. func BackendTestCreateWithConfig(t testing.TB, s *Suite) { diff --git a/src/restic/backend/test/tests_test.go b/src/restic/backend/test/tests_test.go index 514cd9ef9..010fcfe6e 100644 --- a/src/restic/backend/test/tests_test.go +++ b/src/restic/backend/test/tests_test.go @@ -15,8 +15,8 @@ type memConfig struct { be restic.Backend } -func TestSuiteBackendMem(t *testing.T) { - suite := test.Suite{ +func newTestSuite(t testing.TB) *test.Suite { + return &test.Suite{ // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { return &memConfig{}, nil @@ -55,6 +55,12 @@ func TestSuiteBackendMem(t *testing.T) { return nil }, } - - suite.RunTests(t) +} + +func TestSuiteBackendMem(t *testing.T) { + newTestSuite(t).RunTests(t) +} + +func BenchmarkSuiteBackendMem(b *testing.B) { + newTestSuite(b).RunBenchmarks(b) } From 8fc25cc567ab25b84cb0059bb471a1b45c7d87f8 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 May 2017 22:24:15 +0200 Subject: [PATCH 03/12] tests: Add benchmarks for partial file (+offset) --- src/restic/backend/test/benchmarks.go | 109 +++++++++++++++++++++++--- src/restic/backend/test/funcs.go | 2 + 2 files changed, 100 insertions(+), 11 deletions(-) diff --git a/src/restic/backend/test/benchmarks.go b/src/restic/backend/test/benchmarks.go index 19cfc468f..42e94cfea 100644 --- a/src/restic/backend/test/benchmarks.go +++ b/src/restic/backend/test/benchmarks.go @@ -8,24 +8,29 @@ import ( "testing" ) -// BackendBenchmarkLoad benchmarks the backend's Load function. -func BackendBenchmarkLoadFile(t *testing.B, s *Suite) { - be := s.open(t) - defer s.close(t, be) - - length := 1<<24 + 2123 +func saveRandomFile(t testing.TB, be restic.Backend, length int) ([]byte, restic.Handle) { data := test.Random(23, length) id := restic.Hash(data) handle := restic.Handle{Type: restic.DataFile, Name: id.String()} if err := be.Save(handle, bytes.NewReader(data)); err != nil { t.Fatalf("Save() error: %+v", err) } + return data, handle +} - defer func() { - if err := be.Remove(handle); err != nil { - t.Fatalf("Remove() returned error: %v", err) - } - }() +func remove(t testing.TB, be restic.Backend, h restic.Handle) { + if err := be.Remove(h); err != nil { + t.Fatalf("Remove() returned error: %v", err) + } +} + +func BackendBenchmarkLoadFile(t *testing.B, s *Suite) { + be := s.open(t) + defer s.close(t, be) + + length := 1<<24 + 2123 + data, handle := saveRandomFile(t, be, length) + defer remove(t, be, handle) buf := make([]byte, length) @@ -54,6 +59,88 @@ func BackendBenchmarkLoadFile(t *testing.B, s *Suite) { if !bytes.Equal(data, buf) { t.Fatalf("wrong bytes returned") } + } +} + +func BackendBenchmarkLoadPartialFile(t *testing.B, s *Suite) { + be := s.open(t) + defer s.close(t, be) + + datalength := 1<<24 + 2123 + data, handle := saveRandomFile(t, be, datalength) + defer remove(t, be, handle) + + testLength := datalength/4 + 555 + + buf := make([]byte, testLength) + + t.SetBytes(int64(testLength)) + t.ResetTimer() + + for i := 0; i < t.N; i++ { + rd, err := be.Load(handle, testLength, 0) + if err != nil { + t.Fatal(err) + } + + n, err := io.ReadFull(rd, buf) + if err != nil { + t.Fatal(err) + } + + if err = rd.Close(); err != nil { + t.Fatalf("Close() returned error: %v", err) + } + + if n != testLength { + t.Fatalf("wrong number of bytes read: want %v, got %v", testLength, n) + } + + if !bytes.Equal(data[:testLength], buf) { + t.Fatalf("wrong bytes returned") + } + + } +} + +func BackendBenchmarkLoadPartialFileOffset(t *testing.B, s *Suite) { + be := s.open(t) + defer s.close(t, be) + + datalength := 1<<24 + 2123 + data, handle := saveRandomFile(t, be, datalength) + defer remove(t, be, handle) + + testLength := datalength/4 + 555 + testOffset := 8273 + + buf := make([]byte, testLength) + + t.SetBytes(int64(testLength)) + t.ResetTimer() + + for i := 0; i < t.N; i++ { + rd, err := be.Load(handle, testLength, int64(testOffset)) + if err != nil { + t.Fatal(err) + } + + n, err := io.ReadFull(rd, buf) + if err != nil { + t.Fatal(err) + } + + if err = rd.Close(); err != nil { + t.Fatalf("Close() returned error: %v", err) + } + + if n != testLength { + t.Fatalf("wrong number of bytes read: want %v, got %v", testLength, n) + } + + if !bytes.Equal(data[testOffset:testOffset+testLength], buf) { + t.Fatalf("wrong bytes returned") + } } } diff --git a/src/restic/backend/test/funcs.go b/src/restic/backend/test/funcs.go index b3f2f7bd3..5058d1088 100644 --- a/src/restic/backend/test/funcs.go +++ b/src/restic/backend/test/funcs.go @@ -25,4 +25,6 @@ var benchmarkFunctions = []struct { Fn func(*testing.B, *Suite) }{ {"LoadFile", BackendBenchmarkLoadFile}, + {"LoadPartialFile", BackendBenchmarkLoadPartialFile}, + {"LoadPartialFileOffset", BackendBenchmarkLoadPartialFileOffset}, } From 5b8131e2d3096038c7d5e35b59ab410dd1121212 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 May 2017 23:04:30 +0200 Subject: [PATCH 04/12] tests: Add benchmark for Save --- src/restic/backend/test/benchmarks.go | 29 +++++++++++++++++++++++++++ src/restic/backend/test/funcs.go | 1 + 2 files changed, 30 insertions(+) diff --git a/src/restic/backend/test/benchmarks.go b/src/restic/backend/test/benchmarks.go index 42e94cfea..19831d96d 100644 --- a/src/restic/backend/test/benchmarks.go +++ b/src/restic/backend/test/benchmarks.go @@ -144,3 +144,32 @@ func BackendBenchmarkLoadPartialFileOffset(t *testing.B, s *Suite) { } } + +func BackendBenchmarkSave(t *testing.B, s *Suite) { + be := s.open(t) + defer s.close(t, be) + + length := 1<<24 + 2123 + data := test.Random(23, length) + id := restic.Hash(data) + handle := restic.Handle{Type: restic.DataFile, Name: id.String()} + + rd := bytes.NewReader(data) + + t.SetBytes(int64(length)) + t.ResetTimer() + + for i := 0; i < t.N; i++ { + if _, err := rd.Seek(0, 0); err != nil { + t.Fatal(err) + } + + if err := be.Save(handle, rd); err != nil { + t.Fatal(err) + } + + if err := be.Remove(handle); err != nil { + t.Fatal(err) + } + } +} diff --git a/src/restic/backend/test/funcs.go b/src/restic/backend/test/funcs.go index 5058d1088..1f8a12a77 100644 --- a/src/restic/backend/test/funcs.go +++ b/src/restic/backend/test/funcs.go @@ -27,4 +27,5 @@ var benchmarkFunctions = []struct { {"LoadFile", BackendBenchmarkLoadFile}, {"LoadPartialFile", BackendBenchmarkLoadPartialFile}, {"LoadPartialFileOffset", BackendBenchmarkLoadPartialFileOffset}, + {"Save", BackendBenchmarkSave}, } From d24e0cc6ccf020ea052ab5e527465dd21225bd7c Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 May 2017 21:44:19 +0200 Subject: [PATCH 05/12] s3: Add benchmarks --- src/restic/backend/s3/s3_test.go | 126 ++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 45 deletions(-) diff --git a/src/restic/backend/s3/s3_test.go b/src/restic/backend/s3/s3_test.go index b6859bcd4..787166994 100644 --- a/src/restic/backend/s3/s3_test.go +++ b/src/restic/backend/s3/s3_test.go @@ -79,7 +79,7 @@ func runMinio(ctx context.Context, t testing.TB, dir, key, secret string) func() } } -func newCredentials(t testing.TB) (key, secret string) { +func newRandomCredentials(t testing.TB) (key, secret string) { buf := make([]byte, 10) _, err := io.ReadFull(rand.Reader, buf) if err != nil { @@ -96,38 +96,22 @@ func newCredentials(t testing.TB) (key, secret string) { return key, secret } -func TestBackendMinio(t *testing.T) { - defer func() { - if t.Skipped() { - SkipDisallowed(t, "restic/backend/s3.TestBackendMinio") - } - }() +type MinioTestConfig struct { + s3.Config - // try to find a minio binary - _, err := exec.LookPath("minio") - if err != nil { - t.Skip(err) - return - } + tempdir string + removeTempdir func() + stopServer func() +} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - type Config struct { - s3.Config - - tempdir string - removeTempdir func() - stopServer func() - } - - suite := test.Suite{ +func newMinioTestSuite(ctx context.Context, t testing.TB) *test.Suite { + return &test.Suite{ // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { - cfg := Config{} + cfg := MinioTestConfig{} cfg.tempdir, cfg.removeTempdir = TempDir(t) - key, secret := newCredentials(t) + key, secret := newRandomCredentials(t) cfg.stopServer = runMinio(ctx, t, cfg.tempdir, key, secret) cfg.Config = s3.Config{ @@ -143,7 +127,7 @@ func TestBackendMinio(t *testing.T) { // CreateFn is a function that creates a temporary repository for the tests. Create: func(config interface{}) (restic.Backend, error) { - cfg := config.(Config) + cfg := config.(MinioTestConfig) be, err := s3.Open(cfg.Config) if err != nil { @@ -164,13 +148,13 @@ func TestBackendMinio(t *testing.T) { // OpenFn is a function that opens a previously created temporary repository. Open: func(config interface{}) (restic.Backend, error) { - cfg := config.(Config) + cfg := config.(MinioTestConfig) return s3.Open(cfg.Config) }, // CleanupFn removes data created during the tests. Cleanup: func(config interface{}) error { - cfg := config.(Config) + cfg := config.(MinioTestConfig) if cfg.stopServer != nil { cfg.stopServer() } @@ -180,31 +164,44 @@ func TestBackendMinio(t *testing.T) { return nil }, } - - suite.RunTests(t) } -func TestBackendS3(t *testing.T) { +func TestBackendMinio(t *testing.T) { defer func() { if t.Skipped() { - SkipDisallowed(t, "restic/backend/s3.TestBackendS3") + SkipDisallowed(t, "restic/backend/s3.TestBackendMinio") } }() - vars := []string{ - "RESTIC_TEST_S3_KEY", - "RESTIC_TEST_S3_SECRET", - "RESTIC_TEST_S3_REPOSITORY", + // try to find a minio binary + _, err := exec.LookPath("minio") + if err != nil { + t.Skip(err) + return } - for _, v := range vars { - if os.Getenv(v) == "" { - t.Skipf("environment variable %v not set", v) - return - } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + newMinioTestSuite(ctx, t).RunTests(t) +} + +func BenchmarkBackendMinio(t *testing.B) { + // try to find a minio binary + _, err := exec.LookPath("minio") + if err != nil { + t.Skip(err) + return } - suite := test.Suite{ + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + newMinioTestSuite(ctx, t).RunBenchmarks(t) +} + +func newS3TestSuite(t testing.TB) *test.Suite { + return &test.Suite{ // do not use excessive data MinimalData: true, @@ -265,7 +262,46 @@ func TestBackendS3(t *testing.T) { return nil }, } +} + +func TestBackendS3(t *testing.T) { + defer func() { + if t.Skipped() { + SkipDisallowed(t, "restic/backend/s3.TestBackendS3") + } + }() + + vars := []string{ + "RESTIC_TEST_S3_KEY", + "RESTIC_TEST_S3_SECRET", + "RESTIC_TEST_S3_REPOSITORY", + } + + for _, v := range vars { + if os.Getenv(v) == "" { + t.Skipf("environment variable %v not set", v) + return + } + } t.Logf("run tests") - suite.RunTests(t) + newS3TestSuite(t).RunTests(t) +} + +func BenchmarkBackendS3(t *testing.B) { + vars := []string{ + "RESTIC_TEST_S3_KEY", + "RESTIC_TEST_S3_SECRET", + "RESTIC_TEST_S3_REPOSITORY", + } + + for _, v := range vars { + if os.Getenv(v) == "" { + t.Skipf("environment variable %v not set", v) + return + } + } + + t.Logf("run tests") + newS3TestSuite(t).RunBenchmarks(t) } From e009c002ba0206023cf3b8fb50f86877f923870e Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 May 2017 21:45:41 +0200 Subject: [PATCH 06/12] local: Add benchmark --- src/restic/backend/local/local_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/restic/backend/local/local_test.go b/src/restic/backend/local/local_test.go index ad0d626cb..d2bed6e13 100644 --- a/src/restic/backend/local/local_test.go +++ b/src/restic/backend/local/local_test.go @@ -10,8 +10,8 @@ import ( . "restic/test" ) -func TestBackend(t *testing.T) { - suite := test.Suite{ +func newTestSuite(t testing.TB) *test.Suite { + return &test.Suite{ // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { dir, err := ioutil.TempDir(TestTempDir, "restic-test-local-") @@ -50,6 +50,12 @@ func TestBackend(t *testing.T) { return nil }, } - - suite.RunTests(t) +} + +func TestBackend(t *testing.T) { + newTestSuite(t).RunTests(t) +} + +func BenchmarkBackend(t *testing.B) { + newTestSuite(t).RunBenchmarks(t) } From 13946e7db763ed25710ba6335b685e0b7839a0d2 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 May 2017 21:47:01 +0200 Subject: [PATCH 07/12] mem: Add benchmarks --- src/restic/backend/mem/mem_backend_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/restic/backend/mem/mem_backend_test.go b/src/restic/backend/mem/mem_backend_test.go index 2f39787a9..06da32661 100644 --- a/src/restic/backend/mem/mem_backend_test.go +++ b/src/restic/backend/mem/mem_backend_test.go @@ -14,8 +14,8 @@ type memConfig struct { be restic.Backend } -func TestSuiteBackendMem(t *testing.T) { - suite := test.Suite{ +func newTestSuite() *test.Suite { + return &test.Suite{ // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { return &memConfig{}, nil @@ -54,6 +54,12 @@ func TestSuiteBackendMem(t *testing.T) { return nil }, } - - suite.RunTests(t) +} + +func TestSuiteBackendMem(t *testing.T) { + newTestSuite().RunTests(t) +} + +func BenchmarkSuiteBackendMem(t *testing.B) { + newTestSuite().RunBenchmarks(t) } From 7a516402624ccb838c1a63594a94cf1948d2df61 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 May 2017 21:56:18 +0200 Subject: [PATCH 08/12] rest: Add benchmarks --- src/restic/backend/rest/rest_test.go | 53 ++++++++++++++++++---------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/src/restic/backend/rest/rest_test.go b/src/restic/backend/rest/rest_test.go index e8ed57646..d951eea05 100644 --- a/src/restic/backend/rest/rest_test.go +++ b/src/restic/backend/rest/rest_test.go @@ -60,23 +60,8 @@ func runRESTServer(ctx context.Context, t testing.TB, dir string) func() { } } -func TestBackendREST(t *testing.T) { - defer func() { - if t.Skipped() { - SkipDisallowed(t, "restic/backend/rest.TestBackendREST") - } - }() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - dir, cleanup := TempDir(t) - defer cleanup() - - cleanup = runRESTServer(ctx, t, dir) - defer cleanup() - - suite := test.Suite{ +func newTestSuite(ctx context.Context, t testing.TB) *test.Suite { + return &test.Suite{ // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { dir, err := ioutil.TempDir(TestTempDir, "restic-test-rest-") @@ -114,6 +99,36 @@ func TestBackendREST(t *testing.T) { return nil }, } - - suite.RunTests(t) +} + +func TestBackendREST(t *testing.T) { + defer func() { + if t.Skipped() { + SkipDisallowed(t, "restic/backend/rest.TestBackendREST") + } + }() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + dir, cleanup := TempDir(t) + defer cleanup() + + cleanup = runRESTServer(ctx, t, dir) + defer cleanup() + + newTestSuite(ctx, t).RunTests(t) +} + +func BenchmarkBackendREST(t *testing.B) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + dir, cleanup := TempDir(t) + defer cleanup() + + cleanup = runRESTServer(ctx, t, dir) + defer cleanup() + + newTestSuite(ctx, t).RunBenchmarks(t) } From 90c1608d880fa54c13b94ec02b8f8f46580feb5a Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 May 2017 21:59:00 +0200 Subject: [PATCH 09/12] sftp: Add Benchmarks --- src/restic/backend/sftp/sftp_test.go | 38 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/restic/backend/sftp/sftp_test.go b/src/restic/backend/sftp/sftp_test.go index 8fb46d2a1..8f52b57a5 100644 --- a/src/restic/backend/sftp/sftp_test.go +++ b/src/restic/backend/sftp/sftp_test.go @@ -29,18 +29,8 @@ func findSFTPServerBinary() string { var sftpServer = findSFTPServerBinary() -func TestBackendSFTP(t *testing.T) { - defer func() { - if t.Skipped() { - SkipDisallowed(t, "restic/backend/sftp.TestBackendSFTP") - } - }() - - if sftpServer == "" { - t.Skip("sftp server binary not found") - } - - suite := test.Suite{ +func newTestSuite(t testing.TB) *test.Suite { + return &test.Suite{ // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { dir, err := ioutil.TempDir(TestTempDir, "restic-test-sftp-") @@ -80,6 +70,26 @@ func TestBackendSFTP(t *testing.T) { return nil }, } - - suite.RunTests(t) +} + +func TestBackendSFTP(t *testing.T) { + defer func() { + if t.Skipped() { + SkipDisallowed(t, "restic/backend/sftp.TestBackendSFTP") + } + }() + + if sftpServer == "" { + t.Skip("sftp server binary not found") + } + + newTestSuite(t).RunTests(t) +} + +func BenchmarkBackendSFTP(t *testing.B) { + if sftpServer == "" { + t.Skip("sftp server binary not found") + } + + newTestSuite(t).RunBenchmarks(t) } From 77a55fbe5c1a37ce67cdcd8e6364189d7b73ebaa Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 14 May 2017 10:58:09 +0200 Subject: [PATCH 10/12] tests: Add documentation --- src/restic/backend/test/benchmarks.go | 7 +++++ src/restic/backend/test/doc.go | 38 +++++++++++++++++++++++++++ src/restic/backend/test/tests.go | 1 - 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/restic/backend/test/doc.go diff --git a/src/restic/backend/test/benchmarks.go b/src/restic/backend/test/benchmarks.go index 19831d96d..c9b5691f6 100644 --- a/src/restic/backend/test/benchmarks.go +++ b/src/restic/backend/test/benchmarks.go @@ -24,6 +24,8 @@ func remove(t testing.TB, be restic.Backend, h restic.Handle) { } } +// BackendBenchmarkLoadFile benchmarks the Load() method of a backend by +// loading a complete file. func BackendBenchmarkLoadFile(t *testing.B, s *Suite) { be := s.open(t) defer s.close(t, be) @@ -62,6 +64,8 @@ func BackendBenchmarkLoadFile(t *testing.B, s *Suite) { } } +// BackendBenchmarkLoadPartialFile 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) { be := s.open(t) defer s.close(t, be) @@ -103,6 +107,8 @@ func BackendBenchmarkLoadPartialFile(t *testing.B, s *Suite) { } } +// BackendBenchmarkLoadPartialFileOffset 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) { be := s.open(t) defer s.close(t, be) @@ -145,6 +151,7 @@ func BackendBenchmarkLoadPartialFileOffset(t *testing.B, s *Suite) { } } +// BackendBenchmarkSave benchmarks the Save() method of a backend. func BackendBenchmarkSave(t *testing.B, s *Suite) { be := s.open(t) defer s.close(t, be) diff --git a/src/restic/backend/test/doc.go b/src/restic/backend/test/doc.go new file mode 100644 index 000000000..61cb835a7 --- /dev/null +++ b/src/restic/backend/test/doc.go @@ -0,0 +1,38 @@ +// Package test contains a test suite with benchmarks for restic backends. +// +// Overview +// +// For the test suite to work a few functions need to be implemented to create +// new config, create a backend, open it and run cleanup tasks afterwards. The +// Suite struct has fields for each function. +// +// So for a new backend, a Suite needs to be built with callback functions, +// then the methods RunTests() and RunBenchmarks() can be used to run the +// individual tests and benchmarks as subtests/subbenchmarks. +// +// Example +// +// Assuming a *Suite is returned by newTestSuite(), the tests and benchmarks +// can be run like this: +// +// func TestSuiteBackendMem(t *testing.T) { +// newTestSuite(t).RunTests(t) +// } +// +// func BenchmarkSuiteBackendMem(b *testing.B) { +// newTestSuite(b).RunBenchmarks(b) +// } +// +// 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. +package test diff --git a/src/restic/backend/test/tests.go b/src/restic/backend/test/tests.go index ff7d77549..aa24db970 100644 --- a/src/restic/backend/test/tests.go +++ b/src/restic/backend/test/tests.go @@ -1,4 +1,3 @@ -// Package test contains a test suite for restic backends. package test import ( From 403e201e1a0f5c9c875f589abf67cae2a6ce8f56 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 14 May 2017 12:50:20 +0200 Subject: [PATCH 11/12] tests: Improve robustness of config tests --- src/restic/backend/test/tests.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/restic/backend/test/tests.go b/src/restic/backend/test/tests.go index aa24db970..9110b0cf9 100644 --- a/src/restic/backend/test/tests.go +++ b/src/restic/backend/test/tests.go @@ -25,11 +25,22 @@ func BackendTestCreateWithConfig(t testing.TB, s *Suite) { b := s.open(t) defer s.close(t, b) + // remove a config if present + cfgHandle := restic.Handle{Type: restic.ConfigFile} + cfgPresent, err := b.Test(cfgHandle) + if err != nil { + t.Fatalf("unable to test for config: %+v", err) + } + + if cfgPresent { + remove(t, b, cfgHandle) + } + // save a config store(t, b, restic.ConfigFile, []byte("test config")) // now create the backend again, this must fail - _, err := s.Create(s.Config) + _, err = s.Create(s.Config) if err == nil { t.Fatalf("expected error not found for creating a backend with an existing config file") } @@ -83,6 +94,9 @@ func BackendTestConfig(t testing.TB, s *Suite) { t.Fatalf("wrong data returned, want %q, got %q", testString, string(buf)) } } + + // remove the config + remove(t, b, restic.Handle{Type: restic.ConfigFile}) } // BackendTestLoad tests the backend's Load function. From 66b4999765d22285bc80c597b0799952de2346fc Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 14 May 2017 12:56:10 +0200 Subject: [PATCH 12/12] tests: Remove code generation, use reflection This simplifies the code. --- src/restic/backend/test/benchmarks.go | 16 +- src/restic/backend/test/doc.go | 24 +-- src/restic/backend/test/funcs.go | 31 ---- src/restic/backend/test/generate_test_list.go | 152 ------------------ src/restic/backend/test/suite.go | 84 +++++++++- src/restic/backend/test/tests.go | 32 ++-- 6 files changed, 114 insertions(+), 225 deletions(-) delete mode 100644 src/restic/backend/test/funcs.go delete mode 100644 src/restic/backend/test/generate_test_list.go diff --git a/src/restic/backend/test/benchmarks.go b/src/restic/backend/test/benchmarks.go index c9b5691f6..2b2b0666d 100644 --- a/src/restic/backend/test/benchmarks.go +++ b/src/restic/backend/test/benchmarks.go @@ -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) diff --git a/src/restic/backend/test/doc.go b/src/restic/backend/test/doc.go index 61cb835a7..c1704d2c9 100644 --- a/src/restic/backend/test/doc.go +++ b/src/restic/backend/test/doc.go @@ -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 diff --git a/src/restic/backend/test/funcs.go b/src/restic/backend/test/funcs.go deleted file mode 100644 index 1f8a12a77..000000000 --- a/src/restic/backend/test/funcs.go +++ /dev/null @@ -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}, -} diff --git a/src/restic/backend/test/generate_test_list.go b/src/restic/backend/test/generate_test_list.go deleted file mode 100644 index 98ae06fe4..000000000 --- a/src/restic/backend/test/generate_test_list.go +++ /dev/null @@ -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) - } -} diff --git a/src/restic/backend/test/suite.go b/src/restic/backend/test/suite.go index f7409aa4c..0b9b78a88 100644 --- a/src/restic/backend/test/suite.go +++ b/src/restic/backend/test/suite.go @@ -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 { diff --git a/src/restic/backend/test/tests.go b/src/restic/backend/test/tests.go index 9110b0cf9..7c1031099 100644 --- a/src/restic/backend/test/tests.go +++ b/src/restic/backend/test/tests.go @@ -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") }