// Copyright 2016, Joe Tsai. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE.md file. package main import ( "fmt" "go/build" "io" "regexp" "sort" "strings" "github.com/dsnet/golib/unitconv" ) // Section: Format and Test enumerations type Format int const ( FormatFlate Format = iota FormatBrotli FormatBZ2 FormatLZMA2 FormatZstd ) type Test int const ( TestEncodeRate Test = iota TestDecodeRate TestCompressRatio ) var ( fmtToEnum = map[string]Format{ "fl": FormatFlate, "br": FormatBrotli, "bz2": FormatBZ2, "lzma": FormatLZMA2, "zstd": FormatZstd, } enumToFmt = map[Format]string{ FormatFlate: "fl", FormatBrotli: "br", FormatBZ2: "bz2", FormatLZMA2: "lzma", FormatZstd: "zstd", } testToEnum = map[string]Test{ "encRate": TestEncodeRate, "decRate": TestDecodeRate, "ratio": TestCompressRatio, } enumToTest = map[Test]string{ TestEncodeRate: "encRate", TestDecodeRate: "decRate", TestCompressRatio: "ratio", } ) // Section: Encoders and Decoders // // In order for new encoders and decoders (also called codecs) to be added, // the RegisterEncoder and RegisterDecoder functions can be used to inform // the tool about the existence of a given implementation for some format. // Source files (protected by build tags) can use an init function to register // a codec for testing and benchmarking purposes. type Encoder func(io.Writer, int) io.WriteCloser type Decoder func(io.Reader) io.ReadCloser var ( encoders map[Format]map[string]Encoder decoders map[Format]map[string]Decoder ) func RegisterEncoder(ft Format, name string, enc Encoder) { if encoders == nil { encoders = make(map[Format]map[string]Encoder) } if encoders[ft] == nil { encoders[ft] = make(map[string]Encoder) } encoders[ft][name] = enc } func RegisterDecoder(ft Format, name string, dec Decoder) { if decoders == nil { decoders = make(map[Format]map[string]Decoder) } if decoders[ft] == nil { decoders[ft] = make(map[string]Decoder) } decoders[ft][name] = dec } // Section: Configuration variables // // These are list of global configuration variables that can be set to alter // the behavior of tests and benchmarks. Setting of these variables is done // by setting flags on the command line. var ( formats varFormats tests varTests codecs varStrings paths varStrings globs varStrings levels varInts sizes varInts ) // setDefaults configures the top-level parameters with default values. // This function method must be called after all init functions have executed // since they register the various codecs. func setDefaults() { formats = defaultFormats() tests = defaultTests() codecs = defaultCodecs() paths = defaultPaths() globs = []string{"*.txt", "*.bin"} levels = []int{1, 6, 9} sizes = []int{1e4, 1e5, 1e6} } func defaultFormats() []Format { m := make(map[Format]bool) for k := range encoders { m[k] = true } for k := range decoders { m[k] = true } var d []int for k := range m { d = append(d, int(k)) } sort.Ints(d) var fs []Format for _, f := range d { fs = append(fs, Format(f)) } return fs } func defaultTests() []Test { var d []int for k := range enumToTest { d = append(d, int(k)) } sort.Ints(d) var ts []Test for _, t := range d { ts = append(ts, Test(t)) } return ts } func defaultCodecs() []string { m := make(map[string]bool) for _, v := range encoders { for k := range v { m[k] = true } } for _, v := range decoders { for k := range v { m[k] = true } } hasDS := m["std"] delete(m, "std") var cs []string for k := range m { cs = append(cs, k) } sort.Strings(cs) if hasDS { cs = append([]string{"std"}, cs...) // Ensure "std" always appears first } return cs } func defaultPaths() []string { const testdataPkg = "github.com/dsnet/compress/testdata" pkg, err := build.Import(testdataPkg, "", build.FindOnly) if err != nil { return nil } return []string{pkg.Dir} } var reDelim = regexp.MustCompile("[,:]") type ( varFormats []Format varTests []Test varStrings []string varInts []int ) func (fs *varFormats) String() string { var ss []string for _, f := range *fs { ss = append(ss, enumToFmt[f]) } return strings.Join(ss, ",") } func (fs *varFormats) Set(ss string) error { *fs = nil for _, s := range reDelim.Split(ss, -1) { f, ok := fmtToEnum[s] if !ok { return fmt.Errorf("unknown format: %s", s) } *fs = append(*fs, f) } return nil } func (ts *varTests) String() string { var ss []string for _, t := range *ts { ss = append(ss, enumToTest[t]) } return strings.Join(ss, ",") } func (ts *varTests) Set(ss string) error { *ts = nil for _, s := range reDelim.Split(ss, -1) { t, ok := testToEnum[s] if !ok { return fmt.Errorf("unknown test: %s", s) } *ts = append(*ts, t) } return nil } func (vs *varStrings) String() string { return strings.Join(*vs, ",") } func (vs *varStrings) Set(ss string) error { *vs = reDelim.Split(ss, -1) return nil } func (ds *varInts) String() string { var ss []string for _, d := range *ds { ss = append(ss, intName(int64(d))) } return strings.Join(ss, ",") } func (ds *varInts) Set(ss string) error { *ds = nil for _, s := range reDelim.Split(ss, -1) { d, err := unitconv.ParsePrefix(s, unitconv.AutoParse) if err != nil { return err } *ds = append(*ds, int(d)) } return nil } // intName returns a shorter representation of the input integer. // It uses scientific notation for exact powers of 10. // It uses SI suffixes for powers of 1024. // If the number is small enough, it will be printed as is. func intName(n int64) string { switch n { case 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12: s := fmt.Sprintf("%e", float64(n)) re := regexp.MustCompile("\\.0*e\\+0*") return re.ReplaceAllString(s, "e") default: s := unitconv.FormatPrefix(float64(n), unitconv.Base1024, 2) return strings.Replace(s, ".00", "", -1) } }