| // Copyright 2009 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" |
| "errors" |
| "fmt" |
| "internal/testenv" |
| "io" |
| "io/ioutil" |
| "math/rand" |
| "reflect" |
| "runtime/debug" |
| "sync" |
| "testing" |
| ) |
| |
| type deflateTest struct { |
| in []byte |
| level int |
| out []byte |
| } |
| |
| type deflateInflateTest struct { |
| in []byte |
| } |
| |
| type reverseBitsTest struct { |
| in uint16 |
| bitCount uint8 |
| out uint16 |
| } |
| |
| var deflateTests = []*deflateTest{ |
| {[]byte{}, 0, []byte{1, 0, 0, 255, 255}}, |
| {[]byte{0x11}, -1, []byte{18, 4, 4, 0, 0, 255, 255}}, |
| {[]byte{0x11}, DefaultCompression, []byte{18, 4, 4, 0, 0, 255, 255}}, |
| {[]byte{0x11}, 4, []byte{18, 4, 4, 0, 0, 255, 255}}, |
| |
| {[]byte{0x11}, 0, []byte{0, 1, 0, 254, 255, 17, 1, 0, 0, 255, 255}}, |
| {[]byte{0x11, 0x12}, 0, []byte{0, 2, 0, 253, 255, 17, 18, 1, 0, 0, 255, 255}}, |
| {[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 0, |
| []byte{0, 8, 0, 247, 255, 17, 17, 17, 17, 17, 17, 17, 17, 1, 0, 0, 255, 255}, |
| }, |
| {[]byte{}, 2, []byte{1, 0, 0, 255, 255}}, |
| {[]byte{0x11}, 2, []byte{18, 4, 4, 0, 0, 255, 255}}, |
| {[]byte{0x11, 0x12}, 2, []byte{18, 20, 2, 4, 0, 0, 255, 255}}, |
| {[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 2, []byte{18, 132, 2, 64, 0, 0, 0, 255, 255}}, |
| {[]byte{}, 9, []byte{1, 0, 0, 255, 255}}, |
| {[]byte{0x11}, 9, []byte{18, 4, 4, 0, 0, 255, 255}}, |
| {[]byte{0x11, 0x12}, 9, []byte{18, 20, 2, 4, 0, 0, 255, 255}}, |
| {[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 9, []byte{18, 132, 2, 64, 0, 0, 0, 255, 255}}, |
| } |
| |
| var deflateInflateTests = []*deflateInflateTest{ |
| {[]byte{}}, |
| {[]byte{0x11}}, |
| {[]byte{0x11, 0x12}}, |
| {[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}}, |
| {[]byte{0x11, 0x10, 0x13, 0x41, 0x21, 0x21, 0x41, 0x13, 0x87, 0x78, 0x13}}, |
| {largeDataChunk()}, |
| } |
| |
| var reverseBitsTests = []*reverseBitsTest{ |
| {1, 1, 1}, |
| {1, 2, 2}, |
| {1, 3, 4}, |
| {1, 4, 8}, |
| {1, 5, 16}, |
| {17, 5, 17}, |
| {257, 9, 257}, |
| {29, 5, 23}, |
| } |
| |
| func largeDataChunk() []byte { |
| result := make([]byte, 100000) |
| for i := range result { |
| result[i] = byte(i * i & 0xFF) |
| } |
| return result |
| } |
| |
| func TestBulkHash4(t *testing.T) { |
| for _, x := range deflateTests { |
| y := x.out |
| if len(y) < minMatchLength { |
| continue |
| } |
| y = append(y, y...) |
| for j := 4; j < len(y); j++ { |
| y := y[:j] |
| dst := make([]uint32, len(y)-minMatchLength+1) |
| for i := range dst { |
| dst[i] = uint32(i + 100) |
| } |
| bulkHash4(y, dst) |
| for i, got := range dst { |
| want := hash4(y[i:]) |
| if got != want && got == uint32(i)+100 { |
| t.Errorf("Len:%d Index:%d, want 0x%08x but not modified", len(y), i, want) |
| } else if got != want { |
| t.Errorf("Len:%d Index:%d, got 0x%08x want:0x%08x", len(y), i, got, want) |
| } |
| } |
| } |
| } |
| } |
| |
| func TestDeflate(t *testing.T) { |
| for _, h := range deflateTests { |
| var buf bytes.Buffer |
| w, err := NewWriter(&buf, h.level) |
| if err != nil { |
| t.Errorf("NewWriter: %v", err) |
| continue |
| } |
| w.Write(h.in) |
| w.Close() |
| if !bytes.Equal(buf.Bytes(), h.out) { |
| t.Errorf("Deflate(%d, %x) = \n%#v, want \n%#v", h.level, h.in, buf.Bytes(), h.out) |
| } |
| } |
| } |
| |
| // A sparseReader returns a stream consisting of 0s followed by 1<<16 1s. |
| // This tests missing hash references in a very large input. |
| type sparseReader struct { |
| l int64 |
| cur int64 |
| } |
| |
| func (r *sparseReader) Read(b []byte) (n int, err error) { |
| if r.cur >= r.l { |
| return 0, io.EOF |
| } |
| n = len(b) |
| cur := r.cur + int64(n) |
| if cur > r.l { |
| n -= int(cur - r.l) |
| cur = r.l |
| } |
| for i := range b[0:n] { |
| if r.cur+int64(i) >= r.l-1<<16 { |
| b[i] = 1 |
| } else { |
| b[i] = 0 |
| } |
| } |
| r.cur = cur |
| return |
| } |
| |
| func TestVeryLongSparseChunk(t *testing.T) { |
| if testing.Short() { |
| t.Skip("skipping sparse chunk during short test") |
| } |
| w, err := NewWriter(io.Discard, 1) |
| if err != nil { |
| t.Errorf("NewWriter: %v", err) |
| return |
| } |
| if _, err = io.Copy(w, &sparseReader{l: 23e8}); err != nil { |
| t.Errorf("Compress failed: %v", err) |
| return |
| } |
| } |
| |
| type syncBuffer struct { |
| buf bytes.Buffer |
| mu sync.RWMutex |
| closed bool |
| ready chan bool |
| } |
| |
| func newSyncBuffer() *syncBuffer { |
| return &syncBuffer{ready: make(chan bool, 1)} |
| } |
| |
| func (b *syncBuffer) Read(p []byte) (n int, err error) { |
| for { |
| b.mu.RLock() |
| n, err = b.buf.Read(p) |
| b.mu.RUnlock() |
| if n > 0 || b.closed { |
| return |
| } |
| <-b.ready |
| } |
| } |
| |
| func (b *syncBuffer) signal() { |
| select { |
| case b.ready <- true: |
| default: |
| } |
| } |
| |
| func (b *syncBuffer) Write(p []byte) (n int, err error) { |
| n, err = b.buf.Write(p) |
| b.signal() |
| return |
| } |
| |
| func (b *syncBuffer) WriteMode() { |
| b.mu.Lock() |
| } |
| |
| func (b *syncBuffer) ReadMode() { |
| b.mu.Unlock() |
| b.signal() |
| } |
| |
| func (b *syncBuffer) Close() error { |
| b.closed = true |
| b.signal() |
| return nil |
| } |
| |
| func testSync(t *testing.T, level int, input []byte, name string) { |
| if len(input) == 0 { |
| return |
| } |
| |
| t.Logf("--testSync %d, %d, %s", level, len(input), name) |
| buf := newSyncBuffer() |
| buf1 := new(bytes.Buffer) |
| buf.WriteMode() |
| w, err := NewWriter(io.MultiWriter(buf, buf1), level) |
| if err != nil { |
| t.Errorf("NewWriter: %v", err) |
| return |
| } |
| r := NewReader(buf) |
| |
| // Write half the input and read back. |
| for i := 0; i < 2; i++ { |
| var lo, hi int |
| if i == 0 { |
| lo, hi = 0, (len(input)+1)/2 |
| } else { |
| lo, hi = (len(input)+1)/2, len(input) |
| } |
| t.Logf("#%d: write %d-%d", i, lo, hi) |
| if _, err := w.Write(input[lo:hi]); err != nil { |
| t.Errorf("testSync: write: %v", err) |
| return |
| } |
| if i == 0 { |
| if err := w.Flush(); err != nil { |
| t.Errorf("testSync: flush: %v", err) |
| return |
| } |
| } else { |
| if err := w.Close(); err != nil { |
| t.Errorf("testSync: close: %v", err) |
| } |
| } |
| buf.ReadMode() |
| out := make([]byte, hi-lo+1) |
| m, err := io.ReadAtLeast(r, out, hi-lo) |
| t.Logf("#%d: read %d", i, m) |
| if m != hi-lo || err != nil { |
| t.Errorf("testSync/%d (%d, %d, %s): read %d: %d, %v (%d left)", i, level, len(input), name, hi-lo, m, err, buf.buf.Len()) |
| return |
| } |
| if !bytes.Equal(input[lo:hi], out[:hi-lo]) { |
| t.Errorf("testSync/%d: read wrong bytes: %x vs %x", i, input[lo:hi], out[:hi-lo]) |
| return |
| } |
| // This test originally checked that after reading |
| // the first half of the input, there was nothing left |
| // in the read buffer (buf.buf.Len() != 0) but that is |
| // not necessarily the case: the write Flush may emit |
| // some extra framing bits that are not necessary |
| // to process to obtain the first half of the uncompressed |
| // data. The test ran correctly most of the time, because |
| // the background goroutine had usually read even |
| // those extra bits by now, but it's not a useful thing to |
| // check. |
| buf.WriteMode() |
| } |
| buf.ReadMode() |
| out := make([]byte, 10) |
| if n, err := r.Read(out); n > 0 || err != io.EOF { |
| t.Errorf("testSync (%d, %d, %s): final Read: %d, %v (hex: %x)", level, len(input), name, n, err, out[0:n]) |
| } |
| if buf.buf.Len() != 0 { |
| t.Errorf("testSync (%d, %d, %s): extra data at end", level, len(input), name) |
| } |
| r.Close() |
| |
| // stream should work for ordinary reader too |
| r = NewReader(buf1) |
| out, err = io.ReadAll(r) |
| if err != nil { |
| t.Errorf("testSync: read: %s", err) |
| return |
| } |
| r.Close() |
| if !bytes.Equal(input, out) { |
| t.Errorf("testSync: decompress(compress(data)) != data: level=%d input=%s", level, name) |
| } |
| } |
| |
| func testToFromWithLevelAndLimit(t *testing.T, level int, input []byte, name string, limit int) { |
| var buffer bytes.Buffer |
| w, err := NewWriter(&buffer, level) |
| if err != nil { |
| t.Errorf("NewWriter: %v", err) |
| return |
| } |
| w.Write(input) |
| w.Close() |
| if limit > 0 && buffer.Len() > limit { |
| t.Errorf("level: %d, len(compress(data)) = %d > limit = %d", level, buffer.Len(), limit) |
| return |
| } |
| if limit > 0 { |
| t.Logf("level: %d, size:%.2f%%, %d b\n", level, float64(buffer.Len()*100)/float64(limit), buffer.Len()) |
| } |
| r := NewReader(&buffer) |
| out, err := io.ReadAll(r) |
| if err != nil { |
| t.Errorf("read: %s", err) |
| return |
| } |
| r.Close() |
| if !bytes.Equal(input, out) { |
| t.Errorf("decompress(compress(data)) != data: level=%d input=%s", level, name) |
| return |
| } |
| testSync(t, level, input, name) |
| } |
| |
| func testToFromWithLimit(t *testing.T, input []byte, name string, limit [11]int) { |
| for i := 0; i < 10; i++ { |
| testToFromWithLevelAndLimit(t, i, input, name, limit[i]) |
| } |
| // Test HuffmanCompression |
| testToFromWithLevelAndLimit(t, -2, input, name, limit[10]) |
| } |
| |
| func TestDeflateInflate(t *testing.T) { |
| t.Parallel() |
| for i, h := range deflateInflateTests { |
| if testing.Short() && len(h.in) > 10000 { |
| continue |
| } |
| testToFromWithLimit(t, h.in, fmt.Sprintf("#%d", i), [11]int{}) |
| } |
| } |
| |
| func TestReverseBits(t *testing.T) { |
| for _, h := range reverseBitsTests { |
| if v := reverseBits(h.in, h.bitCount); v != h.out { |
| t.Errorf("reverseBits(%v,%v) = %v, want %v", |
| h.in, h.bitCount, v, h.out) |
| } |
| } |
| } |
| |
| type deflateInflateStringTest struct { |
| filename string |
| label string |
| limit [11]int |
| } |
| |
| var deflateInflateStringTests = []deflateInflateStringTest{ |
| { |
| "../testdata/e.txt", |
| "2.718281828...", |
| [...]int{100018, 50650, 50960, 51150, 50930, 50790, 50790, 50790, 50790, 50790, 43683}, |
| }, |
| { |
| "../../testdata/Isaac.Newton-Opticks.txt", |
| "Isaac.Newton-Opticks", |
| [...]int{567248, 218338, 198211, 193152, 181100, 175427, 175427, 173597, 173422, 173422, 325240}, |
| }, |
| } |
| |
| func TestDeflateInflateString(t *testing.T) { |
| t.Parallel() |
| if testing.Short() && testenv.Builder() == "" { |
| t.Skip("skipping in short mode") |
| } |
| for _, test := range deflateInflateStringTests { |
| gold, err := ioutil.ReadFile(test.filename) |
| if err != nil { |
| t.Error(err) |
| } |
| testToFromWithLimit(t, gold, test.label, test.limit) |
| if testing.Short() { |
| break |
| } |
| } |
| } |
| |
| func TestReaderDict(t *testing.T) { |
| const ( |
| dict = "hello world" |
| text = "hello again world" |
| ) |
| var b bytes.Buffer |
| w, err := NewWriter(&b, 5) |
| if err != nil { |
| t.Fatalf("NewWriter: %v", err) |
| } |
| w.Write([]byte(dict)) |
| w.Flush() |
| b.Reset() |
| w.Write([]byte(text)) |
| w.Close() |
| |
| r := NewReaderDict(&b, []byte(dict)) |
| data, err := io.ReadAll(r) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if string(data) != "hello again world" { |
| t.Fatalf("read returned %q want %q", string(data), text) |
| } |
| } |
| |
| func TestWriterDict(t *testing.T) { |
| const ( |
| dict = "hello world" |
| text = "hello again world" |
| ) |
| var b bytes.Buffer |
| w, err := NewWriter(&b, 5) |
| if err != nil { |
| t.Fatalf("NewWriter: %v", err) |
| } |
| w.Write([]byte(dict)) |
| w.Flush() |
| b.Reset() |
| w.Write([]byte(text)) |
| w.Close() |
| |
| var b1 bytes.Buffer |
| w, _ = NewWriterDict(&b1, 5, []byte(dict)) |
| w.Write([]byte(text)) |
| w.Close() |
| |
| if !bytes.Equal(b1.Bytes(), b.Bytes()) { |
| t.Fatalf("writer wrote %q want %q", b1.Bytes(), b.Bytes()) |
| } |
| } |
| |
| // See https://golang.org/issue/2508 |
| func TestRegression2508(t *testing.T) { |
| if testing.Short() { |
| t.Logf("test disabled with -short") |
| return |
| } |
| w, err := NewWriter(io.Discard, 1) |
| if err != nil { |
| t.Fatalf("NewWriter: %v", err) |
| } |
| buf := make([]byte, 1024) |
| for i := 0; i < 131072; i++ { |
| if _, err := w.Write(buf); err != nil { |
| t.Fatalf("writer failed: %v", err) |
| } |
| } |
| w.Close() |
| } |
| |
| func TestWriterReset(t *testing.T) { |
| t.Parallel() |
| for level := 0; level <= 9; level++ { |
| if testing.Short() && level > 1 { |
| break |
| } |
| w, err := NewWriter(io.Discard, level) |
| if err != nil { |
| t.Fatalf("NewWriter: %v", err) |
| } |
| buf := []byte("hello world") |
| n := 1024 |
| if testing.Short() { |
| n = 10 |
| } |
| for i := 0; i < n; i++ { |
| w.Write(buf) |
| } |
| w.Reset(io.Discard) |
| |
| wref, err := NewWriter(io.Discard, level) |
| if err != nil { |
| t.Fatalf("NewWriter: %v", err) |
| } |
| |
| // DeepEqual doesn't compare functions. |
| w.d.fill, wref.d.fill = nil, nil |
| w.d.step, wref.d.step = nil, nil |
| w.d.bulkHasher, wref.d.bulkHasher = nil, nil |
| w.d.bestSpeed, wref.d.bestSpeed = nil, nil |
| // hashMatch is always overwritten when used. |
| copy(w.d.hashMatch[:], wref.d.hashMatch[:]) |
| if len(w.d.tokens) != 0 { |
| t.Errorf("level %d Writer not reset after Reset. %d tokens were present", level, len(w.d.tokens)) |
| } |
| // As long as the length is 0, we don't care about the content. |
| w.d.tokens = wref.d.tokens |
| |
| // We don't care if there are values in the window, as long as it is at d.index is 0 |
| w.d.window = wref.d.window |
| if !reflect.DeepEqual(w, wref) { |
| t.Errorf("level %d Writer not reset after Reset", level) |
| } |
| } |
| |
| levels := []int{0, 1, 2, 5, 9} |
| for _, level := range levels { |
| t.Run(fmt.Sprint(level), func(t *testing.T) { |
| testResetOutput(t, level, nil) |
| }) |
| } |
| |
| t.Run("dict", func(t *testing.T) { |
| for _, level := range levels { |
| t.Run(fmt.Sprint(level), func(t *testing.T) { |
| testResetOutput(t, level, nil) |
| }) |
| } |
| }) |
| } |
| |
| func testResetOutput(t *testing.T, level int, dict []byte) { |
| writeData := func(w *Writer) { |
| msg := []byte("now is the time for all good gophers") |
| w.Write(msg) |
| w.Flush() |
| |
| hello := []byte("hello world") |
| for i := 0; i < 1024; i++ { |
| w.Write(hello) |
| } |
| |
| fill := bytes.Repeat([]byte("x"), 65000) |
| w.Write(fill) |
| } |
| |
| buf := new(bytes.Buffer) |
| var w *Writer |
| var err error |
| if dict == nil { |
| w, err = NewWriter(buf, level) |
| } else { |
| w, err = NewWriterDict(buf, level, dict) |
| } |
| if err != nil { |
| t.Fatalf("NewWriter: %v", err) |
| } |
| |
| writeData(w) |
| w.Close() |
| out1 := buf.Bytes() |
| |
| buf2 := new(bytes.Buffer) |
| w.Reset(buf2) |
| writeData(w) |
| w.Close() |
| out2 := buf2.Bytes() |
| |
| if len(out1) != len(out2) { |
| t.Errorf("got %d, expected %d bytes", len(out2), len(out1)) |
| return |
| } |
| if !bytes.Equal(out1, out2) { |
| mm := 0 |
| for i, b := range out1[:len(out2)] { |
| if b != out2[i] { |
| t.Errorf("mismatch index %d: %#02x, expected %#02x", i, out2[i], b) |
| } |
| mm++ |
| if mm == 10 { |
| t.Fatal("Stopping") |
| } |
| } |
| } |
| t.Logf("got %d bytes", len(out1)) |
| } |
| |
| // TestBestSpeed tests that round-tripping through deflate and then inflate |
| // recovers the original input. The Write sizes are near the thresholds in the |
| // compressor.encSpeed method (0, 16, 128), as well as near maxStoreBlockSize |
| // (65535). |
| func TestBestSpeed(t *testing.T) { |
| t.Parallel() |
| abc := make([]byte, 128) |
| for i := range abc { |
| abc[i] = byte(i) |
| } |
| abcabc := bytes.Repeat(abc, 131072/len(abc)) |
| var want []byte |
| |
| testCases := [][]int{ |
| {65536, 0}, |
| {65536, 1}, |
| {65536, 1, 256}, |
| {65536, 1, 65536}, |
| {65536, 14}, |
| {65536, 15}, |
| {65536, 16}, |
| {65536, 16, 256}, |
| {65536, 16, 65536}, |
| {65536, 127}, |
| {65536, 128}, |
| {65536, 128, 256}, |
| {65536, 128, 65536}, |
| {65536, 129}, |
| {65536, 65536, 256}, |
| {65536, 65536, 65536}, |
| } |
| |
| for i, tc := range testCases { |
| if i >= 3 && testing.Short() { |
| break |
| } |
| for _, firstN := range []int{1, 65534, 65535, 65536, 65537, 131072} { |
| tc[0] = firstN |
| outer: |
| for _, flush := range []bool{false, true} { |
| buf := new(bytes.Buffer) |
| want = want[:0] |
| |
| w, err := NewWriter(buf, BestSpeed) |
| if err != nil { |
| t.Errorf("i=%d, firstN=%d, flush=%t: NewWriter: %v", i, firstN, flush, err) |
| continue |
| } |
| for _, n := range tc { |
| want = append(want, abcabc[:n]...) |
| if _, err := w.Write(abcabc[:n]); err != nil { |
| t.Errorf("i=%d, firstN=%d, flush=%t: Write: %v", i, firstN, flush, err) |
| continue outer |
| } |
| if !flush { |
| continue |
| } |
| if err := w.Flush(); err != nil { |
| t.Errorf("i=%d, firstN=%d, flush=%t: Flush: %v", i, firstN, flush, err) |
| continue outer |
| } |
| } |
| if err := w.Close(); err != nil { |
| t.Errorf("i=%d, firstN=%d, flush=%t: Close: %v", i, firstN, flush, err) |
| continue |
| } |
| |
| r := NewReader(buf) |
| got, err := io.ReadAll(r) |
| if err != nil { |
| t.Errorf("i=%d, firstN=%d, flush=%t: ReadAll: %v", i, firstN, flush, err) |
| continue |
| } |
| r.Close() |
| |
| if !bytes.Equal(got, want) { |
| t.Errorf("i=%d, firstN=%d, flush=%t: corruption during deflate-then-inflate", i, firstN, flush) |
| continue |
| } |
| } |
| } |
| } |
| } |
| |
| var errIO = errors.New("IO error") |
| |
| // failWriter fails with errIO exactly at the nth call to Write. |
| type failWriter struct{ n int } |
| |
| func (w *failWriter) Write(b []byte) (int, error) { |
| w.n-- |
| if w.n == -1 { |
| return 0, errIO |
| } |
| return len(b), nil |
| } |
| |
| func TestWriterPersistentError(t *testing.T) { |
| t.Parallel() |
| d, err := ioutil.ReadFile("../../testdata/Isaac.Newton-Opticks.txt") |
| if err != nil { |
| t.Fatalf("ReadFile: %v", err) |
| } |
| d = d[:10000] // Keep this test short |
| |
| zw, err := NewWriter(nil, DefaultCompression) |
| if err != nil { |
| t.Fatalf("NewWriter: %v", err) |
| } |
| |
| // Sweep over the threshold at which an error is returned. |
| // The variable i makes it such that the ith call to failWriter.Write will |
| // return errIO. Since failWriter errors are not persistent, we must ensure |
| // that flate.Writer errors are persistent. |
| for i := 0; i < 1000; i++ { |
| fw := &failWriter{i} |
| zw.Reset(fw) |
| |
| _, werr := zw.Write(d) |
| cerr := zw.Close() |
| if werr != errIO && werr != nil { |
| t.Errorf("test %d, mismatching Write error: got %v, want %v", i, werr, errIO) |
| } |
| if cerr != errIO && fw.n < 0 { |
| t.Errorf("test %d, mismatching Close error: got %v, want %v", i, cerr, errIO) |
| } |
| if fw.n >= 0 { |
| // At this point, the failure threshold was sufficiently high enough |
| // that we wrote the whole stream without any errors. |
| return |
| } |
| } |
| } |
| |
| func TestBestSpeedMatch(t *testing.T) { |
| t.Parallel() |
| cases := []struct { |
| previous, current []byte |
| t, s, want int32 |
| }{{ |
| previous: []byte{0, 0, 0, 1, 2}, |
| current: []byte{3, 4, 5, 0, 1, 2, 3, 4, 5}, |
| t: -3, |
| s: 3, |
| want: 6, |
| }, { |
| previous: []byte{0, 0, 0, 1, 2}, |
| current: []byte{2, 4, 5, 0, 1, 2, 3, 4, 5}, |
| t: -3, |
| s: 3, |
| want: 3, |
| }, { |
| previous: []byte{0, 0, 0, 1, 1}, |
| current: []byte{3, 4, 5, 0, 1, 2, 3, 4, 5}, |
| t: -3, |
| s: 3, |
| want: 2, |
| }, { |
| previous: []byte{0, 0, 0, 1, 2}, |
| current: []byte{2, 2, 2, 2, 1, 2, 3, 4, 5}, |
| t: -1, |
| s: 0, |
| want: 4, |
| }, { |
| previous: []byte{0, 0, 0, 1, 2, 3, 4, 5, 2, 2}, |
| current: []byte{2, 2, 2, 2, 1, 2, 3, 4, 5}, |
| t: -7, |
| s: 4, |
| want: 5, |
| }, { |
| previous: []byte{9, 9, 9, 9, 9}, |
| current: []byte{2, 2, 2, 2, 1, 2, 3, 4, 5}, |
| t: -1, |
| s: 0, |
| want: 0, |
| }, { |
| previous: []byte{9, 9, 9, 9, 9}, |
| current: []byte{9, 2, 2, 2, 1, 2, 3, 4, 5}, |
| t: 0, |
| s: 1, |
| want: 0, |
| }, { |
| previous: []byte{}, |
| current: []byte{9, 2, 2, 2, 1, 2, 3, 4, 5}, |
| t: -5, |
| s: 1, |
| want: 0, |
| }, { |
| previous: []byte{}, |
| current: []byte{9, 2, 2, 2, 1, 2, 3, 4, 5}, |
| t: -1, |
| s: 1, |
| want: 0, |
| }, { |
| previous: []byte{}, |
| current: []byte{2, 2, 2, 2, 1, 2, 3, 4, 5}, |
| t: 0, |
| s: 1, |
| want: 3, |
| }, { |
| previous: []byte{3, 4, 5}, |
| current: []byte{3, 4, 5}, |
| t: -3, |
| s: 0, |
| want: 3, |
| }, { |
| previous: make([]byte, 1000), |
| current: make([]byte, 1000), |
| t: -1000, |
| s: 0, |
| want: maxMatchLength - 4, |
| }, { |
| previous: make([]byte, 200), |
| current: make([]byte, 500), |
| t: -200, |
| s: 0, |
| want: maxMatchLength - 4, |
| }, { |
| previous: make([]byte, 200), |
| current: make([]byte, 500), |
| t: 0, |
| s: 1, |
| want: maxMatchLength - 4, |
| }, { |
| previous: make([]byte, maxMatchLength-4), |
| current: make([]byte, 500), |
| t: -(maxMatchLength - 4), |
| s: 0, |
| want: maxMatchLength - 4, |
| }, { |
| previous: make([]byte, 200), |
| current: make([]byte, 500), |
| t: -200, |
| s: 400, |
| want: 100, |
| }, { |
| previous: make([]byte, 10), |
| current: make([]byte, 500), |
| t: 200, |
| s: 400, |
| want: 100, |
| }} |
| for i, c := range cases { |
| e := deflateFast{prev: c.previous} |
| got := e.matchLen(c.s, c.t, c.current) |
| if got != c.want { |
| t.Errorf("Test %d: match length, want %d, got %d", i, c.want, got) |
| } |
| } |
| } |
| |
| func TestBestSpeedMaxMatchOffset(t *testing.T) { |
| t.Parallel() |
| const abc, xyz = "abcdefgh", "stuvwxyz" |
| for _, matchBefore := range []bool{false, true} { |
| for _, extra := range []int{0, inputMargin - 1, inputMargin, inputMargin + 1, 2 * inputMargin} { |
| for offsetAdj := -5; offsetAdj <= +5; offsetAdj++ { |
| report := func(desc string, err error) { |
| t.Errorf("matchBefore=%t, extra=%d, offsetAdj=%d: %s%v", |
| matchBefore, extra, offsetAdj, desc, err) |
| } |
| |
| offset := maxMatchOffset + offsetAdj |
| |
| // Make src to be a []byte of the form |
| // "%s%s%s%s%s" % (abc, zeros0, xyzMaybe, abc, zeros1) |
| // where: |
| // zeros0 is approximately maxMatchOffset zeros. |
| // xyzMaybe is either xyz or the empty string. |
| // zeros1 is between 0 and 30 zeros. |
| // The difference between the two abc's will be offset, which |
| // is maxMatchOffset plus or minus a small adjustment. |
| src := make([]byte, offset+len(abc)+extra) |
| copy(src, abc) |
| if !matchBefore { |
| copy(src[offset-len(xyz):], xyz) |
| } |
| copy(src[offset:], abc) |
| |
| buf := new(bytes.Buffer) |
| w, err := NewWriter(buf, BestSpeed) |
| if err != nil { |
| report("NewWriter: ", err) |
| continue |
| } |
| if _, err := w.Write(src); err != nil { |
| report("Write: ", err) |
| continue |
| } |
| if err := w.Close(); err != nil { |
| report("Writer.Close: ", err) |
| continue |
| } |
| |
| r := NewReader(buf) |
| dst, err := io.ReadAll(r) |
| r.Close() |
| if err != nil { |
| report("ReadAll: ", err) |
| continue |
| } |
| |
| if !bytes.Equal(dst, src) { |
| report("", fmt.Errorf("bytes differ after round-tripping")) |
| continue |
| } |
| } |
| } |
| } |
| } |
| |
| func TestBestSpeedShiftOffsets(t *testing.T) { |
| // Test if shiftoffsets properly preserves matches and resets out-of-range matches |
| // seen in https://github.com/golang/go/issues/4142 |
| enc := newDeflateFast() |
| |
| // testData may not generate internal matches. |
| testData := make([]byte, 32) |
| rng := rand.New(rand.NewSource(0)) |
| for i := range testData { |
| testData[i] = byte(rng.Uint32()) |
| } |
| |
| // Encode the testdata with clean state. |
| // Second part should pick up matches from the first block. |
| wantFirstTokens := len(enc.encode(nil, testData)) |
| wantSecondTokens := len(enc.encode(nil, testData)) |
| |
| if wantFirstTokens <= wantSecondTokens { |
| t.Fatalf("test needs matches between inputs to be generated") |
| } |
| // Forward the current indicator to before wraparound. |
| enc.cur = bufferReset - int32(len(testData)) |
| |
| // Part 1 before wrap, should match clean state. |
| got := len(enc.encode(nil, testData)) |
| if wantFirstTokens != got { |
| t.Errorf("got %d, want %d tokens", got, wantFirstTokens) |
| } |
| |
| // Verify we are about to wrap. |
| if enc.cur != bufferReset { |
| t.Errorf("got %d, want e.cur to be at bufferReset (%d)", enc.cur, bufferReset) |
| } |
| |
| // Part 2 should match clean state as well even if wrapped. |
| got = len(enc.encode(nil, testData)) |
| if wantSecondTokens != got { |
| t.Errorf("got %d, want %d token", got, wantSecondTokens) |
| } |
| |
| // Verify that we wrapped. |
| if enc.cur >= bufferReset { |
| t.Errorf("want e.cur to be < bufferReset (%d), got %d", bufferReset, enc.cur) |
| } |
| |
| // Forward the current buffer, leaving the matches at the bottom. |
| enc.cur = bufferReset |
| enc.shiftOffsets() |
| |
| // Ensure that no matches were picked up. |
| got = len(enc.encode(nil, testData)) |
| if wantFirstTokens != got { |
| t.Errorf("got %d, want %d tokens", got, wantFirstTokens) |
| } |
| } |
| |
| func TestMaxStackSize(t *testing.T) { |
| // This test must not run in parallel with other tests as debug.SetMaxStack |
| // affects all goroutines. |
| n := debug.SetMaxStack(1 << 16) |
| defer debug.SetMaxStack(n) |
| |
| var wg sync.WaitGroup |
| defer wg.Wait() |
| |
| b := make([]byte, 1<<20) |
| for level := HuffmanOnly; level <= BestCompression; level++ { |
| // Run in separate goroutine to increase probability of stack regrowth. |
| wg.Add(1) |
| go func(level int) { |
| defer wg.Done() |
| zw, err := NewWriter(io.Discard, level) |
| if err != nil { |
| t.Errorf("level %d, NewWriter() = %v, want nil", level, err) |
| } |
| if n, err := zw.Write(b); n != len(b) || err != nil { |
| t.Errorf("level %d, Write() = (%d, %v), want (%d, nil)", level, n, err, len(b)) |
| } |
| if err := zw.Close(); err != nil { |
| t.Errorf("level %d, Close() = %v, want nil", level, err) |
| } |
| zw.Reset(io.Discard) |
| }(level) |
| } |
| } |