| // Copyright 2012 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 flate |
| |
| import ( |
| "archive/zip" |
| "bytes" |
| "compress/flate" |
| "fmt" |
| "io" |
| "math" |
| "math/rand" |
| "os" |
| "runtime" |
| "strconv" |
| "strings" |
| "testing" |
| ) |
| |
| func TestWriterMemUsage(t *testing.T) { |
| testMem := func(t *testing.T, fn func()) { |
| var before, after runtime.MemStats |
| runtime.GC() |
| runtime.ReadMemStats(&before) |
| fn() |
| runtime.GC() |
| runtime.ReadMemStats(&after) |
| t.Logf("%s: Memory Used: %dKB, %d allocs", t.Name(), (after.HeapInuse-before.HeapInuse)/1024, after.HeapObjects-before.HeapObjects) |
| } |
| data := make([]byte, 100000) |
| t.Run(fmt.Sprint("stateless"), func(t *testing.T) { |
| testMem(t, func() { |
| StatelessDeflate(io.Discard, data, false, nil) |
| }) |
| }) |
| for level := HuffmanOnly; level <= BestCompression; level++ { |
| t.Run(fmt.Sprint("level-", level), func(t *testing.T) { |
| var zr *Writer |
| var err error |
| testMem(t, func() { |
| zr, err = NewWriter(io.Discard, level) |
| if err != nil { |
| t.Fatal(err) |
| } |
| zr.Write(data) |
| }) |
| zr.Close() |
| }) |
| } |
| for level := HuffmanOnly; level <= BestCompression; level++ { |
| t.Run(fmt.Sprint("stdlib-", level), func(t *testing.T) { |
| var zr *flate.Writer |
| var err error |
| testMem(t, func() { |
| zr, err = flate.NewWriter(io.Discard, level) |
| if err != nil { |
| t.Fatal(err) |
| } |
| zr.Write(data) |
| }) |
| zr.Close() |
| }) |
| } |
| } |
| |
| func TestWriterRegression(t *testing.T) { |
| data, err := os.ReadFile("testdata/regression.zip") |
| if err != nil { |
| t.Fatal(err) |
| } |
| for level := HuffmanOnly; level <= BestCompression; level++ { |
| t.Run(fmt.Sprint("level_", level), func(t *testing.T) { |
| zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| for _, tt := range zr.File { |
| if !strings.HasSuffix(t.Name(), "") { |
| continue |
| } |
| |
| t.Run(tt.Name, func(t *testing.T) { |
| if testing.Short() && tt.FileInfo().Size() > 10000 { |
| t.SkipNow() |
| } |
| r, err := tt.Open() |
| if err != nil { |
| t.Error(err) |
| return |
| } |
| in, err := io.ReadAll(r) |
| if err != nil { |
| t.Error(err) |
| } |
| msg := "level " + strconv.Itoa(level) + ":" |
| buf := new(bytes.Buffer) |
| fw, err := NewWriter(buf, level) |
| if err != nil { |
| t.Fatal(msg + err.Error()) |
| } |
| n, err := fw.Write(in) |
| if n != len(in) { |
| t.Fatal(msg + "short write") |
| } |
| if err != nil { |
| t.Fatal(msg + err.Error()) |
| } |
| err = fw.Close() |
| if err != nil { |
| t.Fatal(msg + err.Error()) |
| } |
| fr1 := NewReader(buf) |
| data2, err := io.ReadAll(fr1) |
| if err != nil { |
| t.Fatal(msg + err.Error()) |
| } |
| if !bytes.Equal(in, data2) { |
| t.Fatal(msg + "not equal") |
| } |
| // Do it again... |
| msg = "level " + strconv.Itoa(level) + " (reset):" |
| buf.Reset() |
| fw.Reset(buf) |
| n, err = fw.Write(in) |
| if n != len(in) { |
| t.Fatal(msg + "short write") |
| } |
| if err != nil { |
| t.Fatal(msg + err.Error()) |
| } |
| err = fw.Close() |
| if err != nil { |
| t.Fatal(msg + err.Error()) |
| } |
| fr1 = NewReader(buf) |
| data2, err = io.ReadAll(fr1) |
| if err != nil { |
| t.Fatal(msg + err.Error()) |
| } |
| if !bytes.Equal(in, data2) { |
| t.Fatal(msg + "not equal") |
| } |
| }) |
| } |
| }) |
| } |
| } |
| |
| func benchmarkEncoder(b *testing.B, testfile, level, n int) { |
| b.SetBytes(int64(n)) |
| buf0, err := os.ReadFile(testfiles[testfile]) |
| if err != nil { |
| b.Fatal(err) |
| } |
| if len(buf0) == 0 { |
| b.Fatalf("test file %q has no data", testfiles[testfile]) |
| } |
| buf1 := make([]byte, n) |
| for i := 0; i < n; i += len(buf0) { |
| if len(buf0) > n-i { |
| buf0 = buf0[:n-i] |
| } |
| copy(buf1[i:], buf0) |
| } |
| buf0 = nil |
| runtime.GC() |
| w, err := NewWriter(io.Discard, level) |
| if err != nil { |
| b.Fatal(err) |
| } |
| b.ResetTimer() |
| b.ReportAllocs() |
| for i := 0; i < b.N; i++ { |
| w.Reset(io.Discard) |
| _, err = w.Write(buf1) |
| if err != nil { |
| b.Fatal(err) |
| } |
| err = w.Close() |
| if err != nil { |
| b.Fatal(err) |
| } |
| } |
| } |
| |
| func BenchmarkEncodeDigitsConstant1e4(b *testing.B) { benchmarkEncoder(b, digits, constant, 1e4) } |
| func BenchmarkEncodeDigitsConstant1e5(b *testing.B) { benchmarkEncoder(b, digits, constant, 1e5) } |
| func BenchmarkEncodeDigitsConstant1e6(b *testing.B) { benchmarkEncoder(b, digits, constant, 1e6) } |
| func BenchmarkEncodeDigitsSpeed1e4(b *testing.B) { benchmarkEncoder(b, digits, speed, 1e4) } |
| func BenchmarkEncodeDigitsSpeed1e5(b *testing.B) { benchmarkEncoder(b, digits, speed, 1e5) } |
| func BenchmarkEncodeDigitsSpeed1e6(b *testing.B) { benchmarkEncoder(b, digits, speed, 1e6) } |
| func BenchmarkEncodeDigitsDefault1e4(b *testing.B) { benchmarkEncoder(b, digits, default_, 1e4) } |
| func BenchmarkEncodeDigitsDefault1e5(b *testing.B) { benchmarkEncoder(b, digits, default_, 1e5) } |
| func BenchmarkEncodeDigitsDefault1e6(b *testing.B) { benchmarkEncoder(b, digits, default_, 1e6) } |
| func BenchmarkEncodeDigitsCompress1e4(b *testing.B) { benchmarkEncoder(b, digits, compress, 1e4) } |
| func BenchmarkEncodeDigitsCompress1e5(b *testing.B) { benchmarkEncoder(b, digits, compress, 1e5) } |
| func BenchmarkEncodeDigitsCompress1e6(b *testing.B) { benchmarkEncoder(b, digits, compress, 1e6) } |
| func BenchmarkEncodeDigitsSL1e4(b *testing.B) { benchmarkStatelessEncoder(b, digits, 1e4) } |
| func BenchmarkEncodeDigitsSL1e5(b *testing.B) { benchmarkStatelessEncoder(b, digits, 1e5) } |
| func BenchmarkEncodeDigitsSL1e6(b *testing.B) { benchmarkStatelessEncoder(b, digits, 1e6) } |
| func BenchmarkEncodeTwainConstant1e4(b *testing.B) { benchmarkEncoder(b, twain, constant, 1e4) } |
| func BenchmarkEncodeTwainConstant1e5(b *testing.B) { benchmarkEncoder(b, twain, constant, 1e5) } |
| func BenchmarkEncodeTwainConstant1e6(b *testing.B) { benchmarkEncoder(b, twain, constant, 1e6) } |
| func BenchmarkEncodeTwainSpeed1e4(b *testing.B) { benchmarkEncoder(b, twain, speed, 1e4) } |
| func BenchmarkEncodeTwainSpeed1e5(b *testing.B) { benchmarkEncoder(b, twain, speed, 1e5) } |
| func BenchmarkEncodeTwainSpeed1e6(b *testing.B) { benchmarkEncoder(b, twain, speed, 1e6) } |
| func BenchmarkEncodeTwainDefault1e4(b *testing.B) { benchmarkEncoder(b, twain, default_, 1e4) } |
| func BenchmarkEncodeTwainDefault1e5(b *testing.B) { benchmarkEncoder(b, twain, default_, 1e5) } |
| func BenchmarkEncodeTwainDefault1e6(b *testing.B) { benchmarkEncoder(b, twain, default_, 1e6) } |
| func BenchmarkEncodeTwainCompress1e4(b *testing.B) { benchmarkEncoder(b, twain, compress, 1e4) } |
| func BenchmarkEncodeTwainCompress1e5(b *testing.B) { benchmarkEncoder(b, twain, compress, 1e5) } |
| func BenchmarkEncodeTwainCompress1e6(b *testing.B) { benchmarkEncoder(b, twain, compress, 1e6) } |
| func BenchmarkEncodeTwainSL1e4(b *testing.B) { benchmarkStatelessEncoder(b, twain, 1e4) } |
| func BenchmarkEncodeTwainSL1e5(b *testing.B) { benchmarkStatelessEncoder(b, twain, 1e5) } |
| func BenchmarkEncodeTwainSL1e6(b *testing.B) { benchmarkStatelessEncoder(b, twain, 1e6) } |
| |
| func benchmarkStatelessEncoder(b *testing.B, testfile, n int) { |
| b.SetBytes(int64(n)) |
| buf0, err := os.ReadFile(testfiles[testfile]) |
| if err != nil { |
| b.Fatal(err) |
| } |
| if len(buf0) == 0 { |
| b.Fatalf("test file %q has no data", testfiles[testfile]) |
| } |
| buf1 := make([]byte, n) |
| for i := 0; i < n; i += len(buf0) { |
| if len(buf0) > n-i { |
| buf0 = buf0[:n-i] |
| } |
| copy(buf1[i:], buf0) |
| } |
| buf0 = nil |
| runtime.GC() |
| b.ResetTimer() |
| b.ReportAllocs() |
| for i := 0; i < b.N; i++ { |
| w := NewStatelessWriter(io.Discard) |
| _, err = w.Write(buf1) |
| if err != nil { |
| b.Fatal(err) |
| } |
| err = w.Close() |
| if err != nil { |
| b.Fatal(err) |
| } |
| } |
| } |
| |
| // A writer that fails after N writes. |
| type errorWriter struct { |
| N int |
| } |
| |
| func (e *errorWriter) Write(b []byte) (int, error) { |
| if e.N <= 0 { |
| return 0, io.ErrClosedPipe |
| } |
| e.N-- |
| return len(b), nil |
| } |
| |
| // Test if errors from the underlying writer is passed upwards. |
| func TestWriteError(t *testing.T) { |
| buf := new(bytes.Buffer) |
| n := 65536 |
| if !testing.Short() { |
| n *= 4 |
| } |
| for i := 0; i < n; i++ { |
| fmt.Fprintf(buf, "asdasfasf%d%dfghfgujyut%dyutyu\n", i, i, i) |
| } |
| in := buf.Bytes() |
| // We create our own buffer to control number of writes. |
| copyBuf := make([]byte, 128) |
| for l := 0; l < 10; l++ { |
| for fail := 1; fail <= 256; fail *= 2 { |
| // Fail after 'fail' writes |
| ew := &errorWriter{N: fail} |
| w, err := NewWriter(ew, l) |
| if err != nil { |
| t.Fatalf("NewWriter: level %d: %v", l, err) |
| } |
| n, err := copyBuffer(w, bytes.NewBuffer(in), copyBuf) |
| if err == nil { |
| t.Fatalf("Level %d: Expected an error, writer was %#v", l, ew) |
| } |
| n2, err := w.Write([]byte{1, 2, 2, 3, 4, 5}) |
| if n2 != 0 { |
| t.Fatal("Level", l, "Expected 0 length write, got", n) |
| } |
| if err == nil { |
| t.Fatal("Level", l, "Expected an error") |
| } |
| err = w.Flush() |
| if err == nil { |
| t.Fatal("Level", l, "Expected an error on flush") |
| } |
| err = w.Close() |
| if err == nil { |
| t.Fatal("Level", l, "Expected an error on close") |
| } |
| |
| w.Reset(io.Discard) |
| n2, err = w.Write([]byte{1, 2, 3, 4, 5, 6}) |
| if err != nil { |
| t.Fatal("Level", l, "Got unexpected error after reset:", err) |
| } |
| if n2 == 0 { |
| t.Fatal("Level", l, "Got 0 length write, expected > 0") |
| } |
| if testing.Short() { |
| return |
| } |
| } |
| } |
| } |
| |
| // Test if errors from the underlying writer is passed upwards. |
| func TestWriter_Reset(t *testing.T) { |
| buf := new(bytes.Buffer) |
| n := 65536 |
| if !testing.Short() { |
| n *= 4 |
| } |
| for i := 0; i < n; i++ { |
| fmt.Fprintf(buf, "asdasfasf%d%dfghfgujyut%dyutyu\n", i, i, i) |
| } |
| in := buf.Bytes() |
| for l := 0; l < 10; l++ { |
| l := l |
| if testing.Short() && l > 1 { |
| continue |
| } |
| t.Run(fmt.Sprintf("level-%d", l), func(t *testing.T) { |
| t.Parallel() |
| offset := 1 |
| if testing.Short() { |
| offset = 256 |
| } |
| for ; offset <= 256; offset *= 2 { |
| // Fail after 'fail' writes |
| w, err := NewWriter(io.Discard, l) |
| if err != nil { |
| t.Fatalf("NewWriter: level %d: %v", l, err) |
| } |
| if w.d.fast == nil { |
| t.Skip("Not Fast...") |
| return |
| } |
| for i := 0; i < (bufferReset-len(in)-offset-maxMatchOffset)/maxMatchOffset; i++ { |
| // skip ahead to where we are close to wrap around... |
| w.d.fast.Reset() |
| } |
| w.d.fast.Reset() |
| _, err = w.Write(in) |
| if err != nil { |
| t.Fatal(err) |
| } |
| for i := 0; i < 50; i++ { |
| // skip ahead again... This should wrap around... |
| w.d.fast.Reset() |
| } |
| w.d.fast.Reset() |
| |
| _, err = w.Write(in) |
| if err != nil { |
| t.Fatal(err) |
| } |
| for i := 0; i < (math.MaxUint32-bufferReset)/maxMatchOffset; i++ { |
| // skip ahead to where we are close to wrap around... |
| w.d.fast.Reset() |
| } |
| |
| _, err = w.Write(in) |
| if err != nil { |
| t.Fatal(err) |
| } |
| err = w.Close() |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestDeterministicL1(t *testing.T) { testDeterministic(1, t) } |
| func TestDeterministicL2(t *testing.T) { testDeterministic(2, t) } |
| func TestDeterministicL3(t *testing.T) { testDeterministic(3, t) } |
| func TestDeterministicL4(t *testing.T) { testDeterministic(4, t) } |
| func TestDeterministicL5(t *testing.T) { testDeterministic(5, t) } |
| func TestDeterministicL6(t *testing.T) { testDeterministic(6, t) } |
| func TestDeterministicL7(t *testing.T) { testDeterministic(7, t) } |
| func TestDeterministicL8(t *testing.T) { testDeterministic(8, t) } |
| func TestDeterministicL9(t *testing.T) { testDeterministic(9, t) } |
| func TestDeterministicL0(t *testing.T) { testDeterministic(0, t) } |
| func TestDeterministicLM2(t *testing.T) { testDeterministic(-2, t) } |
| |
| func testDeterministic(i int, t *testing.T) { |
| // Test so much we cross a good number of block boundaries. |
| var length = maxStoreBlockSize*30 + 500 |
| if testing.Short() { |
| length /= 10 |
| } |
| |
| // Create a random, but compressible stream. |
| rng := rand.New(rand.NewSource(1)) |
| t1 := make([]byte, length) |
| for i := range t1 { |
| t1[i] = byte(rng.Int63() & 7) |
| } |
| |
| // Do our first encode. |
| var b1 bytes.Buffer |
| br := bytes.NewBuffer(t1) |
| w, err := NewWriter(&b1, i) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // Use a very small prime sized buffer. |
| cbuf := make([]byte, 787) |
| _, err = copyBuffer(w, br, cbuf) |
| if err != nil { |
| t.Fatal(err) |
| } |
| w.Close() |
| |
| // We choose a different buffer size, |
| // bigger than a maximum block, and also a prime. |
| var b2 bytes.Buffer |
| cbuf = make([]byte, 81761) |
| br2 := bytes.NewBuffer(t1) |
| w2, err := NewWriter(&b2, i) |
| if err != nil { |
| t.Fatal(err) |
| } |
| _, err = copyBuffer(w2, br2, cbuf) |
| if err != nil { |
| t.Fatal(err) |
| } |
| w2.Close() |
| |
| b1b := b1.Bytes() |
| b2b := b2.Bytes() |
| |
| if !bytes.Equal(b1b, b2b) { |
| t.Errorf("level %d did not produce deterministic result, result mismatch, len(a) = %d, len(b) = %d", i, len(b1b), len(b2b)) |
| } |
| |
| // Test using io.WriterTo interface. |
| var b3 bytes.Buffer |
| br = bytes.NewBuffer(t1) |
| w, err = NewWriter(&b3, i) |
| if err != nil { |
| t.Fatal(err) |
| } |
| _, err = br.WriteTo(w) |
| if err != nil { |
| t.Fatal(err) |
| } |
| w.Close() |
| |
| b3b := b3.Bytes() |
| if !bytes.Equal(b1b, b3b) { |
| t.Errorf("level %d (io.WriterTo) did not produce deterministic result, result mismatch, len(a) = %d, len(b) = %d", i, len(b1b), len(b3b)) |
| } |
| } |
| |
| // copyBuffer is a copy of io.CopyBuffer, since we want to support older go versions. |
| // This is modified to never use io.WriterTo or io.ReaderFrom interfaces. |
| func copyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) { |
| if buf == nil { |
| buf = make([]byte, 32*1024) |
| } |
| for { |
| nr, er := src.Read(buf) |
| if nr > 0 { |
| nw, ew := dst.Write(buf[0:nr]) |
| if nw > 0 { |
| written += int64(nw) |
| } |
| if ew != nil { |
| err = ew |
| break |
| } |
| if nr != nw { |
| err = io.ErrShortWrite |
| break |
| } |
| } |
| if er == io.EOF { |
| break |
| } |
| if er != nil { |
| err = er |
| break |
| } |
| } |
| return written, err |
| } |
| |
| func BenchmarkCompressAllocations(b *testing.B) { |
| payload := []byte(strings.Repeat("Tiny payload", 20)) |
| for j := -2; j <= 9; j++ { |
| b.Run("level("+strconv.Itoa(j)+")", func(b *testing.B) { |
| b.Run("flate", func(b *testing.B) { |
| b.ReportAllocs() |
| |
| for i := 0; i < b.N; i++ { |
| w, err := NewWriter(io.Discard, j) |
| if err != nil { |
| b.Fatal(err) |
| } |
| w.Write(payload) |
| w.Close() |
| } |
| }) |
| }) |
| } |
| } |
| |
| func BenchmarkCompressAllocationsSingle(b *testing.B) { |
| payload := []byte(strings.Repeat("Tiny payload", 20)) |
| const level = 2 |
| b.Run("flate", func(b *testing.B) { |
| b.ReportAllocs() |
| |
| for i := 0; i < b.N; i++ { |
| w, err := NewWriter(io.Discard, level) |
| if err != nil { |
| b.Fatal(err) |
| } |
| w.Write(payload) |
| w.Close() |
| } |
| }) |
| } |