summaryrefslogtreecommitdiffstats
path: root/src/compress/zlib
diff options
context:
space:
mode:
Diffstat (limited to 'src/compress/zlib')
-rw-r--r--src/compress/zlib/example_test.go37
-rw-r--r--src/compress/zlib/reader.go180
-rw-r--r--src/compress/zlib/reader_test.go186
-rw-r--r--src/compress/zlib/writer.go193
-rw-r--r--src/compress/zlib/writer_test.go224
5 files changed, 820 insertions, 0 deletions
diff --git a/src/compress/zlib/example_test.go b/src/compress/zlib/example_test.go
new file mode 100644
index 0000000..7040889
--- /dev/null
+++ b/src/compress/zlib/example_test.go
@@ -0,0 +1,37 @@
+// 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 zlib_test
+
+import (
+ "bytes"
+ "compress/zlib"
+ "fmt"
+ "io"
+ "os"
+)
+
+func ExampleNewWriter() {
+ var b bytes.Buffer
+
+ w := zlib.NewWriter(&b)
+ w.Write([]byte("hello, world\n"))
+ w.Close()
+ fmt.Println(b.Bytes())
+ // Output: [120 156 202 72 205 201 201 215 81 40 207 47 202 73 225 2 4 0 0 255 255 33 231 4 147]
+}
+
+func ExampleNewReader() {
+ buff := []byte{120, 156, 202, 72, 205, 201, 201, 215, 81, 40, 207,
+ 47, 202, 73, 225, 2, 4, 0, 0, 255, 255, 33, 231, 4, 147}
+ b := bytes.NewReader(buff)
+
+ r, err := zlib.NewReader(b)
+ if err != nil {
+ panic(err)
+ }
+ io.Copy(os.Stdout, r)
+ // Output: hello, world
+ r.Close()
+}
diff --git a/src/compress/zlib/reader.go b/src/compress/zlib/reader.go
new file mode 100644
index 0000000..343a18b
--- /dev/null
+++ b/src/compress/zlib/reader.go
@@ -0,0 +1,180 @@
+// 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.
+
+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 (
+ "bufio"
+ "compress/flate"
+ "errors"
+ "hash"
+ "hash/adler32"
+ "io"
+)
+
+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")
+)
+
+type reader struct {
+ r flate.Reader
+ decompressor io.ReadCloser
+ digest hash.Hash32
+ err error
+ scratch [4]byte
+}
+
+// Resetter resets a ReadCloser returned by NewReader or NewReaderDict
+// to switch to a new underlying Reader. This permits reusing a ReadCloser
+// instead of allocating a new one.
+type Resetter interface {
+ // Reset discards any buffered data and resets the Resetter as if it was
+ // newly initialized with the given reader.
+ Reset(r io.Reader, dict []byte) error
+}
+
+// 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.
+//
+// The ReadCloser returned by NewReader also implements Resetter.
+func NewReader(r io.Reader) (io.ReadCloser, 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.
+//
+// The ReadCloser returned by NewReaderDict also implements Resetter.
+func NewReaderDict(r io.Reader, dict []byte) (io.ReadCloser, error) {
+ z := new(reader)
+ err := z.Reset(r, dict)
+ if err != nil {
+ return nil, err
+ }
+ return z, nil
+}
+
+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)
+ z.digest.Write(p[0:n])
+ if z.err != io.EOF {
+ // In the normal case we return here.
+ return n, z.err
+ }
+
+ // Finished file; check checksum.
+ if _, err := io.ReadFull(z.r, z.scratch[0:4]); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ z.err = err
+ return n, z.err
+ }
+ // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
+ checksum := uint32(z.scratch[0])<<24 | uint32(z.scratch[1])<<16 | uint32(z.scratch[2])<<8 | uint32(z.scratch[3])
+ if checksum != z.digest.Sum32() {
+ z.err = ErrChecksum
+ return n, z.err
+ }
+ return n, io.EOF
+}
+
+// Calling 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.
+func (z *reader) Close() error {
+ if z.err != nil && z.err != io.EOF {
+ return z.err
+ }
+ z.err = z.decompressor.Close()
+ return z.err
+}
+
+func (z *reader) Reset(r io.Reader, dict []byte) error {
+ *z = reader{decompressor: z.decompressor}
+ if fr, ok := r.(flate.Reader); ok {
+ z.r = fr
+ } else {
+ z.r = bufio.NewReader(r)
+ }
+
+ // Read the header (RFC 1950 section 2.2.).
+ _, z.err = io.ReadFull(z.r, z.scratch[0:2])
+ if z.err != nil {
+ if z.err == io.EOF {
+ z.err = io.ErrUnexpectedEOF
+ }
+ return z.err
+ }
+ h := uint(z.scratch[0])<<8 | uint(z.scratch[1])
+ 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 z.err == io.EOF {
+ z.err = io.ErrUnexpectedEOF
+ }
+ return z.err
+ }
+ checksum := uint32(z.scratch[0])<<24 | uint32(z.scratch[1])<<16 | uint32(z.scratch[2])<<8 | uint32(z.scratch[3])
+ if checksum != adler32.Checksum(dict) {
+ z.err = ErrDictionary
+ return z.err
+ }
+ }
+
+ if z.decompressor == nil {
+ if haveDict {
+ z.decompressor = flate.NewReaderDict(z.r, dict)
+ } else {
+ z.decompressor = flate.NewReader(z.r)
+ }
+ } else {
+ z.decompressor.(flate.Resetter).Reset(z.r, dict)
+ }
+ z.digest = adler32.New()
+ return nil
+}
diff --git a/src/compress/zlib/reader_test.go b/src/compress/zlib/reader_test.go
new file mode 100644
index 0000000..20cec69
--- /dev/null
+++ b/src/compress/zlib/reader_test.go
@@ -0,0 +1,186 @@
+// 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"
+ "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 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 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 || 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/src/compress/zlib/writer.go b/src/compress/zlib/writer.go
new file mode 100644
index 0000000..9986e38
--- /dev/null
+++ b/src/compress/zlib/writer.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 (
+ "compress/flate"
+ "encoding/binary"
+ "fmt"
+ "hash"
+ "hash/adler32"
+ "io"
+)
+
+// 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
+}
+
+// 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)
+ }
+ return &Writer{
+ w: w,
+ level: level,
+ dict: dict,
+ }, 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
+}
+
+// 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 - (uint16(z.scratch[0])<<8+uint16(z.scratch[1]))%31)
+ if _, err = z.w.Write(z.scratch[0:2]); 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))
+ if _, err = z.w.Write(z.scratch[0:4]); 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
+}
+
+// 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
+ }
+ z.digest.Write(p)
+ return
+}
+
+// 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])
+ return z.err
+}
diff --git a/src/compress/zlib/writer_test.go b/src/compress/zlib/writer_test.go
new file mode 100644
index 0000000..f0b3888
--- /dev/null
+++ b/src/compress/zlib/writer_test.go
@@ -0,0 +1,224 @@
+// 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"
+ "internal/testenv"
+ "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 := 0; i < len(b0); i++ {
+ 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 testFileLevelDictReset(t *testing.T, fn string, level int, dict []byte) {
+ var b0 []byte
+ var err error
+ if fn != "" {
+ b0, err = os.ReadFile(fn)
+ if err != nil {
+ t.Errorf("%s (level=%d): %v", fn, level, err)
+ return
+ }
+ }
+
+ // Compress once.
+ buf := new(bytes.Buffer)
+ var zlibw *Writer
+ if dict == nil {
+ zlibw, err = NewWriterLevel(buf, level)
+ } else {
+ zlibw, err = NewWriterLevelDict(buf, level, dict)
+ }
+ if err == nil {
+ _, err = zlibw.Write(b0)
+ }
+ if err == nil {
+ err = zlibw.Close()
+ }
+ if err != nil {
+ t.Errorf("%s (level=%d): %v", fn, level, err)
+ return
+ }
+ out := buf.String()
+
+ // Reset and compress again.
+ buf2 := new(bytes.Buffer)
+ zlibw.Reset(buf2)
+ _, err = zlibw.Write(b0)
+ if err == nil {
+ err = zlibw.Close()
+ }
+ if err != nil {
+ t.Errorf("%s (level=%d): %v", fn, level, err)
+ return
+ }
+ out2 := buf2.String()
+
+ if out2 != out {
+ t.Errorf("%s (level=%d): different output after reset (got %d bytes, expected %d",
+ fn, level, len(out2), len(out))
+ }
+}
+
+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() && testenv.Builder() == "" {
+ break
+ }
+ }
+ if i == 0 && testing.Short() && testenv.Builder() == "" {
+ 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() && testenv.Builder() == "" {
+ break
+ }
+ }
+ if i == 0 && testing.Short() && testenv.Builder() == "" {
+ break
+ }
+ }
+}
+
+func TestWriterReset(t *testing.T) {
+ const dictionary = "0123456789."
+ for _, fn := range filenames {
+ testFileLevelDictReset(t, fn, NoCompression, nil)
+ testFileLevelDictReset(t, fn, DefaultCompression, nil)
+ testFileLevelDictReset(t, fn, HuffmanOnly, nil)
+ testFileLevelDictReset(t, fn, NoCompression, []byte(dictionary))
+ testFileLevelDictReset(t, fn, DefaultCompression, []byte(dictionary))
+ testFileLevelDictReset(t, fn, HuffmanOnly, []byte(dictionary))
+ if testing.Short() {
+ break
+ }
+ for level := BestSpeed; level <= BestCompression; level++ {
+ testFileLevelDictReset(t, fn, level, nil)
+ }
+ }
+}
+
+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.")
+ var 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)
+ }
+}