blob: 2457c2d67fee1410fece2b5108f0834f0944f75d [file] [log] [blame]
/*
* Copyright 2018 The Kythe Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package riegeli_test
import (
"encoding/hex"
"io"
"io/ioutil"
"math/rand"
"os"
"strings"
"testing"
"kythe.io/kythe/go/storage/stream"
"kythe.io/kythe/go/util/compare"
"kythe.io/kythe/go/util/riegeli"
"github.com/golang/protobuf/proto"
spb "kythe.io/kythe/proto/storage_go_proto"
rmpb "kythe.io/third_party/riegeli/records_metadata_go_proto"
)
var (
goldenJSONFile = "testdata/golden.entries.json"
goldenMetadataFile = "testdata/golden.records_metadata.textproto"
goldenRiegeliFilePrefix = "testdata/golden.entries"
goldenRiegeliFileVariants = []string{
"uncompressed",
"uncompressed_transpose",
"brotli",
"brotli_transpose",
"zstd",
}
)
func BenchmarkGoldenTestData(b *testing.B) {
for _, variant := range goldenRiegeliFileVariants {
file := strings.Join([]string{goldenRiegeliFilePrefix, variant, "riegeli"}, ".")
b.Run(variant, func(b *testing.B) { benchGoldenData(b, file) })
}
}
// benchGoldenData benchmarks the sequential reading of a Riegeli file. MB/s is
// measured by the size of each record read.
func benchGoldenData(b *testing.B, goldenRiegeliFile string) {
f, err := os.Open(goldenRiegeliFile)
if err != nil {
b.Fatal(err)
}
defer f.Close()
b.ReportAllocs()
rd := riegeli.NewReader(f)
for {
rec, err := rd.Next()
if err == io.EOF {
break
} else if err != nil {
b.Fatal(err)
}
b.SetBytes(int64(len(rec)))
}
}
type jsonReader struct{ ch <-chan *spb.Entry }
func (j *jsonReader) Next() (*spb.Entry, error) {
e, ok := <-j.ch
if !ok {
return nil, io.EOF
}
return e, nil
}
func TestGoldenTestData(t *testing.T) {
for _, variant := range goldenRiegeliFileVariants {
file := strings.Join([]string{goldenRiegeliFilePrefix, variant, "riegeli"}, ".")
opts := strings.Replace(variant, "_", ",", -1)
t.Run(variant, func(t *testing.T) { checkGoldenData(t, file, opts) })
}
}
// checkGoldenData ensures that the given Riegeli file contains the exact
// same records as the goldenJSONFile. It also checks the RecordsMetadata
// against goldenMetadataFile and the given expectedOptions. RecordsMetadata
// options are the same format as the C++ strings options defined at:
// https://github.com/google/riegeli/blob/master/doc/record_writer_options.md
func checkGoldenData(t *testing.T, goldenRiegeliFile, expectedOptions string) {
jsonFile, err := os.Open(goldenJSONFile)
if err != nil {
t.Fatal(err)
}
defer jsonFile.Close()
riegeliFile, err := os.Open(goldenRiegeliFile)
if err != nil {
t.Fatal(err)
}
defer riegeliFile.Close()
jsonReader := &jsonReader{stream.ReadJSONEntries(jsonFile)}
riegeliReader := riegeli.NewReadSeeker(riegeliFile)
mdTextProto, err := ioutil.ReadFile(goldenMetadataFile)
if err != nil {
t.Fatalf("Error reading %s: %v", goldenMetadataFile, err)
}
var expectedMetadata rmpb.RecordsMetadata
if err := proto.UnmarshalText(string(mdTextProto), &expectedMetadata); err != nil {
t.Fatalf("Error unmarshaling %s: %v", goldenMetadataFile, err)
}
expectedMetadata.RecordWriterOptions = proto.String(expectedOptions)
md, err := riegeliReader.RecordsMetadata()
if err != nil {
t.Fatalf("Error reading RecordsMetadata: %v", err)
} else if diff := compare.ProtoDiff(md, &expectedMetadata); diff != "" {
t.Errorf("Bad RecordsMetadata: (-: found; +: expected)\n%s", diff)
}
var records []*spb.Entry
var positions []riegeli.RecordPosition
for {
expected, err := jsonReader.Next()
if err == io.EOF {
if rec, err := riegeliReader.Next(); err != io.EOF {
t.Fatalf("Unexpected error/record at end of Riegeli file: %q %v", hex.EncodeToString(rec), err)
}
break
} else if err != nil {
t.Fatalf("Error reading JSON golden data: %v", err)
}
pos, err := riegeliReader.Position()
if err != nil {
t.Fatalf("Error getting Riegeli position: %v", err)
}
records = append(records, expected)
positions = append(positions, pos)
found := &spb.Entry{}
if err := riegeliReader.NextProto(found); err != nil {
t.Fatalf("Error reading Riegeli golden data: %v", err)
}
if diff := compare.ProtoDiff(expected, found); diff != "" {
t.Errorf("Unexpected record: (-: found; +: expected)\n%s", diff)
}
}
if rec, err := riegeliReader.Next(); err != io.EOF {
t.Errorf("Found extra Riegeli record/error: %v %v", rec, err)
}
rand.New(rand.NewSource(0)).Shuffle(len(records), func(i, j int) {
records[i], records[j] = records[j], records[i]
positions[i], positions[j] = positions[j], positions[i]
})
// Seek by RecordPosition
for i, expected := range records {
pos := positions[i]
if err := riegeliReader.SeekToRecord(pos); err != nil {
t.Fatalf("Error seeking to %v: %v", pos, err)
}
var found spb.Entry
if err := riegeliReader.NextProto(&found); err != nil {
t.Fatalf("Error reading record at %v: %v", pos, err)
} else if diff := compare.ProtoDiff(expected, &found); diff != "" {
t.Errorf("Unexpected record: (-: found; +: expected)\n%s", diff)
}
}
}