| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "flag" |
| "io" |
| "os" |
| "path/filepath" |
| |
| "testing" |
| |
| "golang.org/x/perf/benchfmt" |
| "golang.org/x/perf/benchseries" |
| ) |
| |
| var bo *benchseries.BuilderOptions |
| |
| var keepFiles bool |
| |
| func init() { |
| bo = benchseries.BentBuilderOptions() |
| bo.Filter = ".unit:ns/op" |
| } |
| |
| var keep = flag.Bool("keep", false, "write outputs to testdata for debugging") |
| var update = flag.Bool("update", false, "update reference files") |
| |
| // The two tests here work with two subdirectories of testdata, one containing |
| // benchmarks that were started on an even second, the other containing benchmarks |
| // started on an odd second. Each subdirectory contains a file "reference.json" |
| // that in theory contains the reference values for computer summaries (for |
| // an artifcially small bootstrap, to save time). |
| |
| var oddReference = filepath.Join("odd", "reference.json") |
| var evenReference = filepath.Join("even", "reference.json") |
| |
| // TestReference ensures that each subdirectory generates its |
| // reference file. |
| func TestReference(t *testing.T) { |
| if testing.Short() { |
| t.Skipf("Skip test, for golang/go#53745") |
| } |
| evens, err := filepath.Glob("testdata/even/*-opt.*") |
| if err != nil { |
| t.Log(err) |
| t.Fail() |
| } |
| odds, err := filepath.Glob("testdata/odd/*-opt.*") |
| if err != nil { |
| t.Log(err) |
| t.Fail() |
| } |
| |
| evenComparisons := combineFilesAndJson(t, evens, "") |
| oddComparisons := combineFilesAndJson(t, odds, "") |
| |
| if *keep { |
| writeJsonFile(t, "evens.json", evenComparisons) |
| writeJsonFile(t, "odds.json", oddComparisons) |
| } |
| |
| if *update { |
| writeJsonFile(t, evenReference, evenComparisons) |
| writeJsonFile(t, oddReference, oddComparisons) |
| return |
| } |
| |
| be := writeJsonBytes(evenComparisons) |
| bo := writeJsonBytes(oddComparisons) |
| |
| eref := filepath.Join("testdata", evenReference) |
| re, err := os.ReadFile(eref) |
| if err != nil { |
| t.Log(err) |
| t.Fail() |
| } |
| compareBytes(t, be, re, "calculated even bytes", eref) |
| |
| oref := filepath.Join("testdata", oddReference) |
| ro, err := os.ReadFile(oref) |
| if err != nil { |
| t.Log(err) |
| t.Fail() |
| } |
| compareBytes(t, bo, ro, "calculated odd bytes", oref) |
| |
| } |
| |
| // TestReordered checks the ability of benchseries to combine existing summaries |
| // with new results, in either order; the newly computed answers should be identical. |
| func TestReordered(t *testing.T) { |
| evens, err := filepath.Glob("testdata/even/*-opt.*") |
| if err != nil { |
| t.Log(err) |
| t.Fail() |
| } |
| odds, err := filepath.Glob("testdata/odd/*-opt.*") |
| if err != nil { |
| t.Log(err) |
| t.Fail() |
| } |
| |
| oddEvens := combineFilesAndJson(t, evens, oddReference) |
| evenOdds := combineFilesAndJson(t, odds, evenReference) |
| |
| if *keep { |
| writeJsonFile(t, "evenOdds.json", evenOdds) |
| writeJsonFile(t, "oddEvens.json", oddEvens) |
| } |
| |
| beo := writeJsonBytes(evenOdds) |
| boe := writeJsonBytes(oddEvens) |
| |
| compareBytes(t, beo, boe, "evenOdds", "oddEvens") |
| } |
| |
| func combineFilesAndJson(t *testing.T, files []string, jsonFile string) []*benchseries.ComparisonSeries { |
| benchFiles := benchfmt.Files{Paths: files, AllowStdin: false, AllowLabels: true} |
| builder, err := benchseries.NewBuilder(bo) |
| |
| err = builder.AddFiles(benchFiles) |
| if err != nil { |
| t.Log(err) |
| t.Fail() |
| } |
| var comparisons []*benchseries.ComparisonSeries |
| |
| if jsonFile != "" { |
| f, err := os.Open(filepath.Join("testdata", jsonFile)) |
| if err != nil { |
| t.Log(err) |
| t.Fail() |
| } |
| decoder := json.NewDecoder(f) |
| decoder.Decode(&comparisons) |
| f.Close() |
| } |
| |
| comparisons, err = builder.AllComparisonSeries(comparisons, benchseries.DUPE_REPLACE) |
| if err != nil { |
| t.Fatalf("AllComparisonSeries(%+v) got err %v want nil", comparisons, err) |
| } |
| |
| for _, c := range comparisons { |
| c.AddSummaries(0.95, 250) // 250 is faster than 1000. |
| } |
| |
| return comparisons |
| } |
| |
| func compareBytes(t *testing.T, a, b []byte, nameA, nameB string) { |
| // Editors have Opinions about trailing whitespace. Get rid of those. |
| // Editors that reformat json can also cause problems here. |
| a = bytes.TrimSpace(a) |
| b = bytes.TrimSpace(b) |
| if la, lb := len(a), len(b); la != lb { |
| t.Logf("JSON outputs have different lengths, %s=%d and %s=%d", nameA, la, nameB, lb) |
| t.Fail() |
| } |
| |
| // Keep going after a length mismatch, the first diffence can be useful. |
| if bytes.Compare(a, b) != 0 { |
| line := 1 |
| lbegin := 0 |
| l := len(a) |
| if l > len(b) { |
| l = len(b) |
| } |
| for i := 0; i < l; i++ { |
| if a[i] != b[i] { |
| // TODO ought to deal with runes. Benchmark names could contain multibyte runes. |
| t.Logf("JSON outputs differ at line %d, character %d, byte %d", line, i-lbegin, i) |
| t.Fail() |
| break |
| } |
| if a[i] == '\n' { |
| line++ |
| lbegin = i |
| } |
| } |
| } |
| |
| } |
| |
| func writeJsonBytes(cs []*benchseries.ComparisonSeries) []byte { |
| buf := new(bytes.Buffer) |
| writeJson(buf, cs) |
| return buf.Bytes() |
| } |
| |
| func writeJsonFile(t *testing.T, name string, cs []*benchseries.ComparisonSeries) { |
| weo, err := os.Create(filepath.Join("testdata", name)) |
| if err != nil { |
| t.Log(err) |
| t.Fail() |
| } |
| writeJson(weo, cs) |
| } |
| |
| func writeJson(w io.Writer, cs []*benchseries.ComparisonSeries) { |
| encoder := json.NewEncoder(w) |
| encoder.SetIndent("", "\t") |
| encoder.Encode(cs) |
| if wc, ok := w.(io.Closer); ok { |
| wc.Close() |
| } |
| } |