blob: 2cd3e4769a5aa6efa2d925f776815e9ab7ee7dbc [file] [log] [blame]
// 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"
"io/ioutil"
"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 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) {
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 := ioutil.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 := ioutil.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 = builder.AllComparisonSeries(comparisons, benchseries.DUPE_REPLACE)
for _, c := range comparisons {
c.AddSummaries(os.Stdout, 0.95, 500) // 500 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()
}
}