| // 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 ( |
| "bytes" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "math/rand" |
| "runtime" |
| "testing" |
| ) |
| |
| func BenchmarkEncode(b *testing.B) { |
| doBench(b, func(b *testing.B, buf0 []byte, level, n int) { |
| b.StopTimer() |
| b.SetBytes(int64(n)) |
| |
| 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 |
| w, err := NewWriter(ioutil.Discard, level) |
| if err != nil { |
| b.Fatal(err) |
| } |
| runtime.GC() |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| w.Reset(ioutil.Discard) |
| w.Write(buf1) |
| w.Close() |
| } |
| }) |
| } |
| |
| // errorWriter is 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) { |
| t.Parallel() |
| 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. |
| copyBuffer := 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 := io.CopyBuffer(w, struct{ io.Reader }{bytes.NewBuffer(in)}, copyBuffer) |
| 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(ioutil.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 two runs produce identical results |
| // even when writing different sizes to the Writer. |
| func TestDeterministic(t *testing.T) { |
| t.Parallel() |
| for i := 0; i <= 9; i++ { |
| t.Run(fmt.Sprint("L", i), func(t *testing.T) { testDeterministic(i, t) }) |
| } |
| t.Run("LM2", func(t *testing.T) { testDeterministic(-2, t) }) |
| } |
| |
| func testDeterministic(i int, t *testing.T) { |
| t.Parallel() |
| // 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 = io.CopyBuffer(w, struct{ io.Reader }{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 = io.CopyBuffer(w2, struct{ io.Reader }{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)) |
| } |
| } |
| |
| // TestDeflateFast_Reset will test that encoding is consistent |
| // across a warparound of the table offset. |
| // See https://github.com/golang/go/issues/34121 |
| func TestDeflateFast_Reset(t *testing.T) { |
| buf := new(bytes.Buffer) |
| n := 65536 |
| |
| for i := 0; i < n; i++ { |
| fmt.Fprintf(buf, "asdfasdfasdfasdf%d%dfghfgujyut%dyutyu\n", i, i, i) |
| } |
| // This is specific to level 1. |
| const level = 1 |
| in := buf.Bytes() |
| offset := 1 |
| if testing.Short() { |
| offset = 256 |
| } |
| |
| // We do an encode with a clean buffer to compare. |
| var want bytes.Buffer |
| w, err := NewWriter(&want, level) |
| if err != nil { |
| t.Fatalf("NewWriter: level %d: %v", level, err) |
| } |
| |
| // Output written 3 times. |
| w.Write(in) |
| w.Write(in) |
| w.Write(in) |
| w.Close() |
| |
| for ; offset <= 256; offset *= 2 { |
| w, err := NewWriter(ioutil.Discard, level) |
| if err != nil { |
| t.Fatalf("NewWriter: level %d: %v", level, err) |
| } |
| |
| // Reset until we are right before the wraparound. |
| // Each reset adds maxMatchOffset to the offset. |
| for i := 0; i < (bufferReset-len(in)-offset-maxMatchOffset)/maxMatchOffset; i++ { |
| // skip ahead to where we are close to wrap around... |
| w.d.reset(nil) |
| } |
| var got bytes.Buffer |
| w.Reset(&got) |
| |
| // Write 3 times, close. |
| for i := 0; i < 3; i++ { |
| _, err = w.Write(in) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| err = w.Close() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !bytes.Equal(got.Bytes(), want.Bytes()) { |
| t.Fatalf("output did not match at wraparound, len(want) = %d, len(got) = %d", want.Len(), got.Len()) |
| } |
| } |
| } |