1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package zlib
6
7import (
8	"bytes"
9	"fmt"
10	"internal/testenv"
11	"io"
12	"os"
13	"testing"
14)
15
16var filenames = []string{
17	"../testdata/gettysburg.txt",
18	"../testdata/e.txt",
19	"../testdata/pi.txt",
20}
21
22var data = []string{
23	"test a reasonable sized string that can be compressed",
24}
25
26// Tests that compressing and then decompressing the given file at the given compression level and dictionary
27// yields equivalent bytes to the original file.
28func testFileLevelDict(t *testing.T, fn string, level int, d string) {
29	// Read the file, as golden output.
30	golden, err := os.Open(fn)
31	if err != nil {
32		t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err)
33		return
34	}
35	defer golden.Close()
36	b0, err0 := io.ReadAll(golden)
37	if err0 != nil {
38		t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err0)
39		return
40	}
41	testLevelDict(t, fn, b0, level, d)
42}
43
44func testLevelDict(t *testing.T, fn string, b0 []byte, level int, d string) {
45	// Make dictionary, if given.
46	var dict []byte
47	if d != "" {
48		dict = []byte(d)
49	}
50
51	// Push data through a pipe that compresses at the write end, and decompresses at the read end.
52	piper, pipew := io.Pipe()
53	defer piper.Close()
54	go func() {
55		defer pipew.Close()
56		zlibw, err := NewWriterLevelDict(pipew, level, dict)
57		if err != nil {
58			t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err)
59			return
60		}
61		defer zlibw.Close()
62		_, err = zlibw.Write(b0)
63		if err != nil {
64			t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err)
65			return
66		}
67	}()
68	zlibr, err := NewReaderDict(piper, dict)
69	if err != nil {
70		t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err)
71		return
72	}
73	defer zlibr.Close()
74
75	// Compare the decompressed data.
76	b1, err1 := io.ReadAll(zlibr)
77	if err1 != nil {
78		t.Errorf("%s (level=%d, dict=%q): %v", fn, level, d, err1)
79		return
80	}
81	if len(b0) != len(b1) {
82		t.Errorf("%s (level=%d, dict=%q): length mismatch %d versus %d", fn, level, d, len(b0), len(b1))
83		return
84	}
85	for i := 0; i < len(b0); i++ {
86		if b0[i] != b1[i] {
87			t.Errorf("%s (level=%d, dict=%q): mismatch at %d, 0x%02x versus 0x%02x\n", fn, level, d, i, b0[i], b1[i])
88			return
89		}
90	}
91}
92
93func testFileLevelDictReset(t *testing.T, fn string, level int, dict []byte) {
94	var b0 []byte
95	var err error
96	if fn != "" {
97		b0, err = os.ReadFile(fn)
98		if err != nil {
99			t.Errorf("%s (level=%d): %v", fn, level, err)
100			return
101		}
102	}
103
104	// Compress once.
105	buf := new(bytes.Buffer)
106	var zlibw *Writer
107	if dict == nil {
108		zlibw, err = NewWriterLevel(buf, level)
109	} else {
110		zlibw, err = NewWriterLevelDict(buf, level, dict)
111	}
112	if err == nil {
113		_, err = zlibw.Write(b0)
114	}
115	if err == nil {
116		err = zlibw.Close()
117	}
118	if err != nil {
119		t.Errorf("%s (level=%d): %v", fn, level, err)
120		return
121	}
122	out := buf.String()
123
124	// Reset and compress again.
125	buf2 := new(bytes.Buffer)
126	zlibw.Reset(buf2)
127	_, err = zlibw.Write(b0)
128	if err == nil {
129		err = zlibw.Close()
130	}
131	if err != nil {
132		t.Errorf("%s (level=%d): %v", fn, level, err)
133		return
134	}
135	out2 := buf2.String()
136
137	if out2 != out {
138		t.Errorf("%s (level=%d): different output after reset (got %d bytes, expected %d",
139			fn, level, len(out2), len(out))
140	}
141}
142
143func TestWriter(t *testing.T) {
144	for i, s := range data {
145		b := []byte(s)
146		tag := fmt.Sprintf("#%d", i)
147		testLevelDict(t, tag, b, DefaultCompression, "")
148		testLevelDict(t, tag, b, NoCompression, "")
149		testLevelDict(t, tag, b, HuffmanOnly, "")
150		for level := BestSpeed; level <= BestCompression; level++ {
151			testLevelDict(t, tag, b, level, "")
152		}
153	}
154}
155
156func TestWriterBig(t *testing.T) {
157	for i, fn := range filenames {
158		testFileLevelDict(t, fn, DefaultCompression, "")
159		testFileLevelDict(t, fn, NoCompression, "")
160		testFileLevelDict(t, fn, HuffmanOnly, "")
161		for level := BestSpeed; level <= BestCompression; level++ {
162			testFileLevelDict(t, fn, level, "")
163			if level >= 1 && testing.Short() && testenv.Builder() == "" {
164				break
165			}
166		}
167		if i == 0 && testing.Short() && testenv.Builder() == "" {
168			break
169		}
170	}
171}
172
173func TestWriterDict(t *testing.T) {
174	const dictionary = "0123456789."
175	for i, fn := range filenames {
176		testFileLevelDict(t, fn, DefaultCompression, dictionary)
177		testFileLevelDict(t, fn, NoCompression, dictionary)
178		testFileLevelDict(t, fn, HuffmanOnly, dictionary)
179		for level := BestSpeed; level <= BestCompression; level++ {
180			testFileLevelDict(t, fn, level, dictionary)
181			if level >= 1 && testing.Short() && testenv.Builder() == "" {
182				break
183			}
184		}
185		if i == 0 && testing.Short() && testenv.Builder() == "" {
186			break
187		}
188	}
189}
190
191func TestWriterReset(t *testing.T) {
192	const dictionary = "0123456789."
193	for _, fn := range filenames {
194		testFileLevelDictReset(t, fn, NoCompression, nil)
195		testFileLevelDictReset(t, fn, DefaultCompression, nil)
196		testFileLevelDictReset(t, fn, HuffmanOnly, nil)
197		testFileLevelDictReset(t, fn, NoCompression, []byte(dictionary))
198		testFileLevelDictReset(t, fn, DefaultCompression, []byte(dictionary))
199		testFileLevelDictReset(t, fn, HuffmanOnly, []byte(dictionary))
200		if testing.Short() {
201			break
202		}
203		for level := BestSpeed; level <= BestCompression; level++ {
204			testFileLevelDictReset(t, fn, level, nil)
205		}
206	}
207}
208
209func TestWriterDictIsUsed(t *testing.T) {
210	var input = []byte("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
211	var buf bytes.Buffer
212	compressor, err := NewWriterLevelDict(&buf, BestCompression, input)
213	if err != nil {
214		t.Errorf("error in NewWriterLevelDict: %s", err)
215		return
216	}
217	compressor.Write(input)
218	compressor.Close()
219	const expectedMaxSize = 25
220	output := buf.Bytes()
221	if len(output) > expectedMaxSize {
222		t.Errorf("result too large (got %d, want <= %d bytes). Is the dictionary being used?", len(output), expectedMaxSize)
223	}
224}
225