diff options
| author | 2026-03-05 17:36:48 +0800 | |
|---|---|---|
| committer | 2026-03-05 18:38:29 +0800 | |
| commit | beabb6085d42cbb961e3a5dc217fdd840fee4b0d (patch) | |
| tree | 64ea334e74925284228254631bd4e8bea89001d2 /internal/compress/zlib | |
| parent | internal/zlib: Unexport Reset (diff) | |
| signature | No signature | |
internal/compress: Import flate and such from klauspost/compress
Diffstat (limited to 'internal/compress/zlib')
| -rw-r--r-- | internal/compress/zlib/reader.go | 205 | ||||
| -rw-r--r-- | internal/compress/zlib/reader_reset.go | 92 | ||||
| -rw-r--r-- | internal/compress/zlib/reader_test.go | 193 | ||||
| -rw-r--r-- | internal/compress/zlib/writer.go | 204 | ||||
| -rw-r--r-- | internal/compress/zlib/writer_header.go | 71 | ||||
| -rw-r--r-- | internal/compress/zlib/writer_test.go | 185 |
6 files changed, 950 insertions, 0 deletions
diff --git a/internal/compress/zlib/reader.go b/internal/compress/zlib/reader.go new file mode 100644 index 00000000..2d009887 --- /dev/null +++ b/internal/compress/zlib/reader.go @@ -0,0 +1,205 @@ +// 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 zlib implements reading and writing of zlib format compressed data, +as specified in RFC 1950. + +This package differs from the standard library's compress/zlib package +in that it pools readers and writers to reduce allocations. + +Note that closing a reader or writer causes it to be returned to a pool +for reuse. Therefore, the caller must not retain references to a +reader or writer after closing it; in the standard library's +compress/zlib package, it is legal to Reset a closed reader or writer +and continue using it; that is not allowed here, so there is simply no +Resetter interface. + +The implementation provides filters that uncompress during reading +and compress during writing. For example, to write compressed data +to a buffer: + + var b bytes.Buffer + w := zlib.NewWriter(&b) + w.Write([]byte("hello, world\n")) + w.Close() + +and to read that data back: + + r, err := zlib.NewReader(&b) + io.Copy(os.Stdout, r) + r.Close() +*/ +package zlib + +import ( + "encoding/binary" + "errors" + "hash" + "io" + "sync" + + "codeberg.org/lindenii/furgit/internal/compress/flate" +) + +const ( + zlibDeflate = 8 + zlibMaxWindow = 7 +) + +var ( + // ErrChecksum is returned when reading ZLIB data that has an invalid checksum. + ErrChecksum = errors.New("zlib: invalid checksum") + // ErrDictionary is returned when reading ZLIB data that has an invalid dictionary. + ErrDictionary = errors.New("zlib: invalid dictionary") + // ErrHeader is returned when reading ZLIB data that has an invalid header. + ErrHeader = errors.New("zlib: invalid header") +) + +var readerPool = sync.Pool{ + New: func() any { + r := new(Reader) + + return r + }, +} + +// Reader reads and verifies one zlib stream. +// +// Reader implements io.ReadCloser. +type Reader struct { + r flate.Reader + decompressor io.ReadCloser + digest hash.Hash32 + counter *countingFlateReader + err error + scratch [4]byte +} + +// countingFlateReader wraps flate input and tracks consumed bytes. +type countingFlateReader struct { + inner flate.Reader + read uint64 +} + +// Read implements io.Reader. +func (reader *countingFlateReader) Read(dst []byte) (int, error) { + n, err := reader.inner.Read(dst) + reader.read += uint64(n) + + return n, err +} + +// ReadByte implements io.ByteReader. +func (reader *countingFlateReader) ReadByte() (byte, error) { + b, err := reader.inner.ReadByte() + if err == nil { + reader.read++ + } + + return b, err +} + +// NewReader creates a new ReadCloser. +// Reads from the returned ReadCloser read and decompress data from r. +// If r does not implement [io.ByteReader], the decompressor may read more +// data than necessary from r. +// It is the caller's responsibility to call Close on the ReadCloser when done. +func NewReader(r io.Reader) (*Reader, error) { + return NewReaderDict(r, nil) +} + +// NewReaderDict is like [NewReader] but uses a preset dictionary. +// NewReaderDict ignores the dictionary if the compressed data does not refer to it. +// If the compressed data refers to a different dictionary, NewReaderDict returns [ErrDictionary]. +func NewReaderDict(r io.Reader, dict []byte) (*Reader, error) { + v := readerPool.Get() + + z, ok := v.(*Reader) + if !ok { + panic("zlib: pool returned unexpected type") + } + + err := z.reset(r, dict) + if err != nil { + return nil, err + } + + return z, nil +} + +// Read decompresses bytes from receiver into p. +func (z *Reader) Read(p []byte) (int, error) { + if z.err != nil { + return 0, z.err + } + + var n int + + n, z.err = z.decompressor.Read(p) + + _, err := z.digest.Write(p[0:n]) + if err != nil { + z.err = err + + return n, z.err + } + + if !errors.Is(z.err, io.EOF) { + // In the normal case we return here. + return n, z.err + } + + // Finished file; check checksum. + _, err = io.ReadFull(z.r, z.scratch[0:4]) + if err != nil { + if errors.Is(err, io.EOF) { + err = io.ErrUnexpectedEOF + } + + z.err = err + + return n, z.err + } + // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952). + checksum := binary.BigEndian.Uint32(z.scratch[:4]) + if checksum != z.digest.Sum32() { + z.err = ErrChecksum + + return n, z.err + } + + return n, io.EOF +} + +// InputConsumed returns compressed bytes consumed from stream input. +// +// This count includes the zlib header, deflate payload, and zlib checksum +// trailer bytes read by the reader. +func (z *Reader) InputConsumed() uint64 { + if z.counter == nil { + return 0 + } + + return z.counter.read +} + +// Close does not close the wrapped [io.Reader] originally passed to [NewReader]. +// In order for the ZLIB checksum to be verified, the reader must be +// fully consumed until the [io.EOF]. +// Close returns the instance to a global pool; you MUST NOT keep references after Close. +func (z *Reader) Close() error { + if z.err != nil && !errors.Is(z.err, io.EOF) { + return z.err + } + + z.err = z.decompressor.Close() + if z.err != nil { + return z.err + } + + readerPool.Put(z) + + return nil +} diff --git a/internal/compress/zlib/reader_reset.go b/internal/compress/zlib/reader_reset.go new file mode 100644 index 00000000..f374111c --- /dev/null +++ b/internal/compress/zlib/reader_reset.go @@ -0,0 +1,92 @@ +// 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 zlib + +import ( + "bufio" + "encoding/binary" + "errors" + "io" + + "codeberg.org/lindenii/furgit/internal/adler32" + "codeberg.org/lindenii/furgit/internal/compress/flate" +) + +// reset resets receiver to read a new zlib stream. +func (z *Reader) reset(r io.Reader, dict []byte) error { + *z = Reader{decompressor: z.decompressor} + + var input flate.Reader + if fr, ok := r.(flate.Reader); ok { + input = fr + } else { + input = bufio.NewReader(r) + } + + z.counter = &countingFlateReader{inner: input} + z.r = z.counter + + // Read the header (RFC 1950 section 2.2.). + _, z.err = io.ReadFull(z.r, z.scratch[0:2]) + if z.err != nil { + if errors.Is(z.err, io.EOF) { + z.err = io.ErrUnexpectedEOF + } + + return z.err + } + + h := binary.BigEndian.Uint16(z.scratch[:2]) + if (z.scratch[0]&0x0f != zlibDeflate) || (z.scratch[0]>>4 > zlibMaxWindow) || (h%31 != 0) { + z.err = ErrHeader + + return z.err + } + + haveDict := z.scratch[1]&0x20 != 0 + if haveDict { + _, z.err = io.ReadFull(z.r, z.scratch[0:4]) + if z.err != nil { + if errors.Is(z.err, io.EOF) { + z.err = io.ErrUnexpectedEOF + } + + return z.err + } + + checksum := binary.BigEndian.Uint32(z.scratch[:4]) + if checksum != adler32.Checksum(dict) { + z.err = ErrDictionary + + return z.err + } + } + + if z.decompressor != nil { + resetter, ok := z.decompressor.(flate.Resetter) + if !ok { + panic("zlib: pooled decompressor does not implement flate.Resetter") + } + + z.err = resetter.Reset(z.r, dict) + if z.err != nil { + return z.err + } + + z.digest = adler32.New() + + return nil + } + + if haveDict { + z.decompressor = flate.NewReaderDict(z.r, dict) + } else { + z.decompressor = flate.NewReader(z.r) + } + + z.digest = adler32.New() + + return nil +} diff --git a/internal/compress/zlib/reader_test.go b/internal/compress/zlib/reader_test.go new file mode 100644 index 00000000..9b534b1e --- /dev/null +++ b/internal/compress/zlib/reader_test.go @@ -0,0 +1,193 @@ +// 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 zlib + +import ( + "bytes" + "errors" + "io" + "testing" +) + +type zlibTest struct { + desc string + raw string + compressed []byte + dict []byte + err error +} + +// Compare-to-golden test data was generated by the ZLIB example program at +// https://www.zlib.net/zpipe.c + +var zlibTests = []zlibTest{ + { + "truncated empty", + "", + []byte{}, + nil, + io.ErrUnexpectedEOF, + }, + { + "truncated dict", + "", + []byte{0x78, 0xbb}, + []byte{0x00}, + io.ErrUnexpectedEOF, + }, + { + "truncated checksum", + "", + []byte{0x78, 0xbb, 0x00, 0x01, 0x00, 0x01, 0xca, 0x48, + 0xcd, 0xc9, 0xc9, 0xd7, 0x51, 0x28, 0xcf, 0x2f, + 0xca, 0x49, 0x01, 0x04, 0x00, 0x00, 0xff, 0xff, + }, + []byte{0x00}, + io.ErrUnexpectedEOF, + }, + { + "empty", + "", + []byte{0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01}, + nil, + nil, + }, + { + "goodbye", + "goodbye, world", + []byte{ + 0x78, 0x9c, 0x4b, 0xcf, 0xcf, 0x4f, 0x49, 0xaa, + 0x4c, 0xd5, 0x51, 0x28, 0xcf, 0x2f, 0xca, 0x49, + 0x01, 0x00, 0x28, 0xa5, 0x05, 0x5e, + }, + nil, + nil, + }, + { + "bad header (CINFO)", + "", + []byte{0x88, 0x98, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01}, + nil, + ErrHeader, + }, + { + "bad header (FCHECK)", + "", + []byte{0x78, 0x9f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01}, + nil, + ErrHeader, + }, + { + "bad checksum", + "", + []byte{0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0xff}, + nil, + ErrChecksum, + }, + { + "not enough data", + "", + []byte{0x78, 0x9c, 0x03, 0x00, 0x00, 0x00}, + nil, + io.ErrUnexpectedEOF, + }, + { + "excess data is silently ignored", + "", + []byte{ + 0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x78, 0x9c, 0xff, + }, + nil, + nil, + }, + { + "dictionary", + "Hello, World!\n", + []byte{ + 0x78, 0xbb, 0x1c, 0x32, 0x04, 0x27, 0xf3, 0x00, + 0xb1, 0x75, 0x20, 0x1c, 0x45, 0x2e, 0x00, 0x24, + 0x12, 0x04, 0x74, + }, + []byte{ + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x0a, + }, + nil, + }, + { + "wrong dictionary", + "", + []byte{ + 0x78, 0xbb, 0x1c, 0x32, 0x04, 0x27, 0xf3, 0x00, + 0xb1, 0x75, 0x20, 0x1c, 0x45, 0x2e, 0x00, 0x24, + 0x12, 0x04, 0x74, + }, + []byte{ + 0x48, 0x65, 0x6c, 0x6c, + }, + ErrDictionary, + }, + { + "truncated zlib stream amid raw-block", + "hello", + []byte{ + 0x78, 0x9c, 0x00, 0x0c, 0x00, 0xf3, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, + }, + nil, + io.ErrUnexpectedEOF, + }, + { + "truncated zlib stream amid fixed-block", + "He", + []byte{ + 0x78, 0x9c, 0xf2, 0x48, 0xcd, + }, + nil, + io.ErrUnexpectedEOF, + }, +} + +func TestDecompressor(t *testing.T) { + b := new(bytes.Buffer) + for _, tt := range zlibTests { + in := bytes.NewReader(tt.compressed) + + zr, err := NewReaderDict(in, tt.dict) + if err != nil { + if !errors.Is(err, tt.err) { + t.Errorf("%s: NewReader: %s", tt.desc, err) + } + + continue + } + defer zr.Close() + + // Read and verify correctness of data. + b.Reset() + + n, err := io.Copy(b, zr) + if err != nil { + if !errors.Is(err, tt.err) { + t.Errorf("%s: io.Copy: %v want %v", tt.desc, err, tt.err) + } + + continue + } + + s := b.String() + if s != tt.raw { + t.Errorf("%s: got %d-byte %q want %d-byte %q", tt.desc, n, s, len(tt.raw), tt.raw) + } + + // Check for sticky errors. + if n, err := zr.Read([]byte{0}); n != 0 || !errors.Is(err, io.EOF) { + t.Errorf("%s: Read() = (%d, %v), want (0, io.EOF)", tt.desc, n, err) + } + + if err := zr.Close(); err != nil { + t.Errorf("%s: Close() = %v, want nil", tt.desc, err) + } + } +} diff --git a/internal/compress/zlib/writer.go b/internal/compress/zlib/writer.go new file mode 100644 index 00000000..8a5562fb --- /dev/null +++ b/internal/compress/zlib/writer.go @@ -0,0 +1,204 @@ +// 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 zlib + +import ( + "encoding/binary" + "fmt" + "hash" + "io" + "sync" + + "codeberg.org/lindenii/furgit/internal/compress/flate" +) + +// These constants are copied from the [flate] package, so that code that imports +// [compress/zlib] does not also have to import [compress/flate]. +const ( + NoCompression = flate.NoCompression + BestSpeed = flate.BestSpeed + BestCompression = flate.BestCompression + DefaultCompression = flate.DefaultCompression + HuffmanOnly = flate.HuffmanOnly +) + +// A Writer takes data written to it and writes the compressed +// form of that data to an underlying writer (see [NewWriter]). +type Writer struct { + w io.Writer + level int + dict []byte + compressor *flate.Writer + digest hash.Hash32 + err error + scratch [4]byte + wroteHeader bool +} + +var writerPool = sync.Pool{ + New: func() any { + return new(Writer) + }, +} + +// NewWriter creates a new [Writer]. +// Writes to the returned Writer are compressed and written to w. +// +// It is the caller's responsibility to call Close on the Writer when done. +// Writes may be buffered and not flushed until Close. +func NewWriter(w io.Writer) *Writer { + z, _ := NewWriterLevelDict(w, DefaultCompression, nil) + + return z +} + +// NewWriterLevel is like [NewWriter] but specifies the compression level instead +// of assuming [DefaultCompression]. +// +// The compression level can be [DefaultCompression], [NoCompression], [HuffmanOnly] +// or any integer value between [BestSpeed] and [BestCompression] inclusive. +// The error returned will be nil if the level is valid. +func NewWriterLevel(w io.Writer, level int) (*Writer, error) { + return NewWriterLevelDict(w, level, nil) +} + +// NewWriterLevelDict is like [NewWriterLevel] but specifies a dictionary to +// compress with. +// +// The dictionary may be nil. If not, its contents should not be modified until +// the Writer is closed. +func NewWriterLevelDict(w io.Writer, level int, dict []byte) (*Writer, error) { + if level < HuffmanOnly || level > BestCompression { + return nil, fmt.Errorf("zlib: invalid compression level: %d", level) + } + + v := writerPool.Get() + + z, ok := v.(*Writer) + if !ok { + panic("zlib: pool returned unexpected type") + } + + // flate.Writer can only be Reset with the same level/dictionary mode. + // Reuse it only when the configuration is unchanged and dictionary-free. + reuseCompressor := z.compressor != nil && z.level == level && z.dict == nil && dict == nil + if !reuseCompressor { + z.compressor = nil + } + + if z.digest != nil { + z.digest.Reset() + } + + *z = Writer{ + w: w, + level: level, + dict: dict, + compressor: z.compressor, + digest: z.digest, + } + if z.compressor != nil { + z.compressor.Reset(w) + } + + return z, nil +} + +// Reset clears the state of the [Writer] z such that it is equivalent to its +// initial state from [NewWriterLevel] or [NewWriterLevelDict], but instead writing +// to w. +func (z *Writer) Reset(w io.Writer) { + z.w = w + // z.level and z.dict left unchanged. + if z.compressor != nil { + z.compressor.Reset(w) + } + + if z.digest != nil { + z.digest.Reset() + } + + z.err = nil + z.scratch = [4]byte{} + z.wroteHeader = false +} + +// Write writes a compressed form of p to the underlying [io.Writer]. The +// compressed bytes are not necessarily flushed until the [Writer] is closed or +// explicitly flushed. +func (z *Writer) Write(p []byte) (n int, err error) { + if !z.wroteHeader { + z.err = z.writeHeader() + } + + if z.err != nil { + return 0, z.err + } + + if len(p) == 0 { + return 0, nil + } + + n, err = z.compressor.Write(p) + if err != nil { + z.err = err + + return n, err + } + + _, err = z.digest.Write(p) + if err != nil { + z.err = err + + return 0, z.err + } + + return n, err +} + +// Flush flushes the Writer to its underlying [io.Writer]. +func (z *Writer) Flush() error { + if !z.wroteHeader { + z.err = z.writeHeader() + } + + if z.err != nil { + return z.err + } + + z.err = z.compressor.Flush() + + return z.err +} + +// Close closes the Writer, flushing any unwritten data to the underlying +// [io.Writer], but does not close the underlying io.Writer. +func (z *Writer) Close() error { + if !z.wroteHeader { + z.err = z.writeHeader() + } + + if z.err != nil { + return z.err + } + + z.err = z.compressor.Close() + if z.err != nil { + return z.err + } + + checksum := z.digest.Sum32() + // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952). + binary.BigEndian.PutUint32(z.scratch[:], checksum) + + _, z.err = z.w.Write(z.scratch[0:4]) + if z.err != nil { + return z.err + } + + writerPool.Put(z) + + return nil +} diff --git a/internal/compress/zlib/writer_header.go b/internal/compress/zlib/writer_header.go new file mode 100644 index 00000000..43d3bdf5 --- /dev/null +++ b/internal/compress/zlib/writer_header.go @@ -0,0 +1,71 @@ +// 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 zlib + +import ( + "encoding/binary" + + "codeberg.org/lindenii/furgit/internal/adler32" + "codeberg.org/lindenii/furgit/internal/compress/flate" +) + +// writeHeader writes the ZLIB header. +func (z *Writer) writeHeader() (err error) { + z.wroteHeader = true + // ZLIB has a two-byte header (as documented in RFC 1950). + // The first four bits is the CINFO (compression info), which is 7 for the default deflate window size. + // The next four bits is the CM (compression method), which is 8 for deflate. + z.scratch[0] = 0x78 + // The next two bits is the FLEVEL (compression level). The four values are: + // 0=fastest, 1=fast, 2=default, 3=best. + // The next bit, FDICT, is set if a dictionary is given. + // The final five FCHECK bits form a mod-31 checksum. + switch z.level { + case -2, 0, 1: + z.scratch[1] = 0 << 6 + case 2, 3, 4, 5: + z.scratch[1] = 1 << 6 + case 6, -1: + z.scratch[1] = 2 << 6 + case 7, 8, 9: + z.scratch[1] = 3 << 6 + default: + panic("unreachable") + } + + if z.dict != nil { + z.scratch[1] |= 1 << 5 + } + + z.scratch[1] += uint8(31 - binary.BigEndian.Uint16(z.scratch[:2])%31) //#nosec G115 + + _, err = z.w.Write(z.scratch[0:2]) + if err != nil { + return err + } + + if z.dict != nil { + // The next four bytes are the Adler-32 checksum of the dictionary. + binary.BigEndian.PutUint32(z.scratch[:], adler32.Checksum(z.dict)) + + _, err = z.w.Write(z.scratch[0:4]) + if err != nil { + return err + } + } + + if z.compressor == nil { + // Initialize deflater unless the Writer is being reused + // after a Reset call. + z.compressor, err = flate.NewWriterDict(z.w, z.level, z.dict) + if err != nil { + return err + } + + z.digest = adler32.New() + } + + return nil +} diff --git a/internal/compress/zlib/writer_test.go b/internal/compress/zlib/writer_test.go new file mode 100644 index 00000000..6deffdb8 --- /dev/null +++ b/internal/compress/zlib/writer_test.go @@ -0,0 +1,185 @@ +// 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 zlib + +import ( + "bytes" + "fmt" + "io" + "os" + "testing" +) + +var filenames = []string{ + "../testdata/gettysburg.txt", + "../testdata/e.txt", + "../testdata/pi.txt", +} + +var data = []string{ + "test a reasonable sized string that can be compressed", +} + +// Tests that compressing and then decompressing the given file at the given compression level and dictionary +// yields equivalent bytes to the original file. +func testFileLevelDict(t *testing.T, fn string, level int, d string) { + // Read the file, as golden output. + golden, err := os.Open(fn) + if err != nil { + t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err) + + return + } + defer golden.Close() + + b0, err0 := io.ReadAll(golden) + if err0 != nil { + t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err0) + + return + } + + testLevelDict(t, fn, b0, level, d) +} + +func testLevelDict(t *testing.T, fn string, b0 []byte, level int, d string) { + // Make dictionary, if given. + var dict []byte + if d != "" { + dict = []byte(d) + } + + // Push data through a pipe that compresses at the write end, and decompresses at the read end. + piper, pipew := io.Pipe() + defer piper.Close() + + go func() { + defer pipew.Close() + + zlibw, err := NewWriterLevelDict(pipew, level, dict) + if err != nil { + t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err) + + return + } + defer zlibw.Close() + + _, err = zlibw.Write(b0) + if err != nil { + t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err) + + return + } + }() + + zlibr, err := NewReaderDict(piper, dict) + if err != nil { + t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err) + + return + } + defer zlibr.Close() + + // Compare the decompressed data. + b1, err1 := io.ReadAll(zlibr) + if err1 != nil { + t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err1) + + return + } + + if len(b0) != len(b1) { + t.Errorf("%s (level=%d, dict=%q): length mismatch %d versus %d", fn, level, d, len(b0), len(b1)) + + return + } + + for i := range b0 { + if b0[i] != b1[i] { + t.Errorf("%s (level=%d, dict=%q): mismatch at %d, 0x%02x versus 0x%02x\n", fn, level, d, i, b0[i], b1[i]) + + return + } + } +} + +func TestWriter(t *testing.T) { + for i, s := range data { + b := []byte(s) + tag := fmt.Sprintf("#%d", i) + testLevelDict(t, tag, b, DefaultCompression, "") + testLevelDict(t, tag, b, NoCompression, "") + testLevelDict(t, tag, b, HuffmanOnly, "") + + for level := BestSpeed; level <= BestCompression; level++ { + testLevelDict(t, tag, b, level, "") + } + } +} + +func TestWriterBig(t *testing.T) { + for i, fn := range filenames { + testFileLevelDict(t, fn, DefaultCompression, "") + testFileLevelDict(t, fn, NoCompression, "") + testFileLevelDict(t, fn, HuffmanOnly, "") + + for level := BestSpeed; level <= BestCompression; level++ { + testFileLevelDict(t, fn, level, "") + + if level >= 1 && testing.Short() { + break + } + } + + if i == 0 && testing.Short() { + break + } + } +} + +func TestWriterDict(t *testing.T) { + const dictionary = "0123456789." + for i, fn := range filenames { + testFileLevelDict(t, fn, DefaultCompression, dictionary) + testFileLevelDict(t, fn, NoCompression, dictionary) + testFileLevelDict(t, fn, HuffmanOnly, dictionary) + + for level := BestSpeed; level <= BestCompression; level++ { + testFileLevelDict(t, fn, level, dictionary) + + if level >= 1 && testing.Short() { + break + } + } + + if i == 0 && testing.Short() { + break + } + } +} + +func TestWriterDictIsUsed(t *testing.T) { + var ( + input = []byte("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") + buf bytes.Buffer + ) + + compressor, err := NewWriterLevelDict(&buf, BestCompression, input) + if err != nil { + t.Errorf("error in NewWriterLevelDict: %s", err) + + return + } + + compressor.Write(input) + compressor.Close() + + const expectedMaxSize = 25 + + output := buf.Bytes() + if len(output) > expectedMaxSize { + t.Errorf("result too large (got %d, want <= %d bytes). Is the dictionary being used?", len(output), expectedMaxSize) + } +} |
