1
2
3
4
5 package zstd
6
7 import (
8 "bytes"
9 "crypto/sha256"
10 "fmt"
11 "internal/race"
12 "internal/testenv"
13 "io"
14 "os"
15 "os/exec"
16 "path/filepath"
17 "strings"
18 "sync"
19 "testing"
20 )
21
22
23 var tests = []struct {
24 name, uncompressed, compressed string
25 }{
26 {
27 "hello",
28 "hello, world\n",
29 "\x28\xb5\x2f\xfd\x24\x0d\x69\x00\x00\x68\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x0a\x4c\x1f\xf9\xf1",
30 },
31 {
32
33 "ranges",
34 "\xcc\x11\x00\x00\x00\x00\x00\x00\xd5\x13\x00\x00\x00\x00\x00\x00" +
35 "\x1c\x14\x00\x00\x00\x00\x00\x00\x72\x14\x00\x00\x00\x00\x00\x00" +
36 "\x9d\x14\x00\x00\x00\x00\x00\x00\xd5\x14\x00\x00\x00\x00\x00\x00" +
37 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
38 "\xfb\x12\x00\x00\x00\x00\x00\x00\x09\x13\x00\x00\x00\x00\x00\x00" +
39 "\x0c\x13\x00\x00\x00\x00\x00\x00\xcb\x13\x00\x00\x00\x00\x00\x00" +
40 "\x29\x14\x00\x00\x00\x00\x00\x00\x4e\x14\x00\x00\x00\x00\x00\x00" +
41 "\x9d\x14\x00\x00\x00\x00\x00\x00\xd5\x14\x00\x00\x00\x00\x00\x00" +
42 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
43 "\xfb\x12\x00\x00\x00\x00\x00\x00\x09\x13\x00\x00\x00\x00\x00\x00" +
44 "\x67\x13\x00\x00\x00\x00\x00\x00\xcb\x13\x00\x00\x00\x00\x00\x00" +
45 "\x9d\x14\x00\x00\x00\x00\x00\x00\xd5\x14\x00\x00\x00\x00\x00\x00" +
46 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
47 "\x5f\x0b\x00\x00\x00\x00\x00\x00\x6c\x0b\x00\x00\x00\x00\x00\x00" +
48 "\x7d\x0b\x00\x00\x00\x00\x00\x00\x7e\x0c\x00\x00\x00\x00\x00\x00" +
49 "\x38\x0f\x00\x00\x00\x00\x00\x00\x5c\x0f\x00\x00\x00\x00\x00\x00" +
50 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
51 "\x83\x0c\x00\x00\x00\x00\x00\x00\xfa\x0c\x00\x00\x00\x00\x00\x00" +
52 "\xfd\x0d\x00\x00\x00\x00\x00\x00\xef\x0e\x00\x00\x00\x00\x00\x00" +
53 "\x14\x0f\x00\x00\x00\x00\x00\x00\x38\x0f\x00\x00\x00\x00\x00\x00" +
54 "\x9f\x0f\x00\x00\x00\x00\x00\x00\xac\x0f\x00\x00\x00\x00\x00\x00" +
55 "\xdb\x0f\x00\x00\x00\x00\x00\x00\xff\x0f\x00\x00\x00\x00\x00\x00" +
56 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
57 "\xfd\x0d\x00\x00\x00\x00\x00\x00\xd8\x0e\x00\x00\x00\x00\x00\x00" +
58 "\x9f\x0f\x00\x00\x00\x00\x00\x00\xac\x0f\x00\x00\x00\x00\x00\x00" +
59 "\xdb\x0f\x00\x00\x00\x00\x00\x00\xff\x0f\x00\x00\x00\x00\x00\x00" +
60 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
61 "\xfa\x0c\x00\x00\x00\x00\x00\x00\xea\x0d\x00\x00\x00\x00\x00\x00" +
62 "\xef\x0e\x00\x00\x00\x00\x00\x00\x14\x0f\x00\x00\x00\x00\x00\x00" +
63 "\x5c\x0f\x00\x00\x00\x00\x00\x00\x9f\x0f\x00\x00\x00\x00\x00\x00" +
64 "\xac\x0f\x00\x00\x00\x00\x00\x00\xdb\x0f\x00\x00\x00\x00\x00\x00" +
65 "\xff\x0f\x00\x00\x00\x00\x00\x00\x2c\x10\x00\x00\x00\x00\x00\x00" +
66 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
67 "\x60\x11\x00\x00\x00\x00\x00\x00\xd1\x16\x00\x00\x00\x00\x00\x00" +
68 "\x40\x0b\x00\x00\x00\x00\x00\x00\x2c\x10\x00\x00\x00\x00\x00\x00" +
69 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
70 "\x7a\x00\x00\x00\x00\x00\x00\x00\xb6\x00\x00\x00\x00\x00\x00\x00" +
71 "\x9f\x01\x00\x00\x00\x00\x00\x00\xa7\x01\x00\x00\x00\x00\x00\x00" +
72 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
73 "\x7a\x00\x00\x00\x00\x00\x00\x00\xa9\x00\x00\x00\x00\x00\x00\x00" +
74 "\x9f\x01\x00\x00\x00\x00\x00\x00\xa7\x01\x00\x00\x00\x00\x00\x00" +
75 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
76
77 "\x28\xb5\x2f\xfd\x64\xa0\x01\x2d\x05\x00\xc4\x04\xcc\x11\x00\xd5" +
78 "\x13\x00\x1c\x14\x00\x72\x9d\xd5\xfb\x12\x00\x09\x0c\x13\xcb\x13" +
79 "\x29\x4e\x67\x5f\x0b\x6c\x0b\x7d\x0b\x7e\x0c\x38\x0f\x5c\x0f\x83" +
80 "\x0c\xfa\x0c\xfd\x0d\xef\x0e\x14\x38\x9f\x0f\xac\x0f\xdb\x0f\xff" +
81 "\x0f\xd8\x9f\xac\xdb\xff\xea\x5c\x2c\x10\x60\xd1\x16\x40\x0b\x7a" +
82 "\x00\xb6\x00\x9f\x01\xa7\x01\xa9\x36\x20\xa0\x83\x14\x34\x63\x4a" +
83 "\x21\x70\x8c\x07\x46\x03\x4e\x10\x62\x3c\x06\x4e\xc8\x8c\xb0\x32" +
84 "\x2a\x59\xad\xb2\xf1\x02\x82\x7c\x33\xcb\x92\x6f\x32\x4f\x9b\xb0" +
85 "\xa2\x30\xf0\xc0\x06\x1e\x98\x99\x2c\x06\x1e\xd8\xc0\x03\x56\xd8" +
86 "\xc0\x03\x0f\x6c\xe0\x01\xf1\xf0\xee\x9a\xc6\xc8\x97\x99\xd1\x6c" +
87 "\xb4\x21\x45\x3b\x10\xe4\x7b\x99\x4d\x8a\x36\x64\x5c\x77\x08\x02" +
88 "\xcb\xe0\xce",
89 },
90 {
91 "fuzz1",
92 "0\x00\x00\x00\x00\x000\x00\x00\x00\x00\x001\x00\x00\x00\x00\x000000",
93 "(\xb5/\xfd\x04X\x8d\x00\x00P0\x000\x001\x000000\x03T\x02\x00\x01\x01m\xf9\xb7G",
94 },
95 {
96 "empty block",
97 "",
98 "\x28\xb5\x2f\xfd\x00\x00\x15\x00\x00\x00\x00",
99 },
100 {
101 "single skippable frame",
102 "",
103 "\x50\x2a\x4d\x18\x00\x00\x00\x00",
104 },
105 {
106 "two skippable frames",
107 "",
108 "\x50\x2a\x4d\x18\x00\x00\x00\x00" +
109 "\x50\x2a\x4d\x18\x00\x00\x00\x00",
110 },
111 }
112
113 func TestSamples(t *testing.T) {
114 for _, test := range tests {
115 test := test
116 t.Run(test.name, func(t *testing.T) {
117 r := NewReader(strings.NewReader(test.compressed))
118 got, err := io.ReadAll(r)
119 if err != nil {
120 t.Fatal(err)
121 }
122 gotstr := string(got)
123 if gotstr != test.uncompressed {
124 t.Errorf("got %q want %q", gotstr, test.uncompressed)
125 }
126 })
127 }
128 }
129
130 func TestReset(t *testing.T) {
131 input := strings.NewReader("")
132 r := NewReader(input)
133 for _, test := range tests {
134 test := test
135 t.Run(test.name, func(t *testing.T) {
136 input.Reset(test.compressed)
137 r.Reset(input)
138 got, err := io.ReadAll(r)
139 if err != nil {
140 t.Fatal(err)
141 }
142 gotstr := string(got)
143 if gotstr != test.uncompressed {
144 t.Errorf("got %q want %q", gotstr, test.uncompressed)
145 }
146 })
147 }
148 }
149
150 var (
151 bigDataOnce sync.Once
152 bigDataBytes []byte
153 bigDataErr error
154 )
155
156
157 func bigData(t testing.TB) []byte {
158 bigDataOnce.Do(func() {
159 bigDataBytes, bigDataErr = os.ReadFile("../../testdata/Isaac.Newton-Opticks.txt")
160 if bigDataErr == nil {
161 bigDataBytes = bytes.Repeat(bigDataBytes, 20)
162 }
163 })
164 if bigDataErr != nil {
165 t.Fatal(bigDataErr)
166 }
167 return bigDataBytes
168 }
169
170 func findZstd(t testing.TB) string {
171 zstd, err := exec.LookPath("zstd")
172 if err != nil {
173 t.Skip("skipping because zstd not found")
174 }
175 return zstd
176 }
177
178 var (
179 zstdBigOnce sync.Once
180 zstdBigBytes []byte
181 zstdBigErr error
182 )
183
184
185
186
187 func zstdBigData(t testing.TB) []byte {
188 input := bigData(t)
189
190 zstd := findZstd(t)
191
192 zstdBigOnce.Do(func() {
193 cmd := exec.Command(zstd, "-z")
194 cmd.Stdin = bytes.NewReader(input)
195 var compressed bytes.Buffer
196 cmd.Stdout = &compressed
197 cmd.Stderr = os.Stderr
198 if err := cmd.Run(); err != nil {
199 zstdBigErr = fmt.Errorf("running zstd failed: %v", err)
200 return
201 }
202
203 zstdBigBytes = compressed.Bytes()
204 })
205 if zstdBigErr != nil {
206 t.Fatal(zstdBigErr)
207 }
208 return zstdBigBytes
209 }
210
211
212
213 func TestLarge(t *testing.T) {
214 if testing.Short() {
215 t.Skip("skipping expensive test in short mode")
216 }
217
218 data := bigData(t)
219 compressed := zstdBigData(t)
220
221 t.Logf("zstd compressed %d bytes to %d", len(data), len(compressed))
222
223 r := NewReader(bytes.NewReader(compressed))
224 got, err := io.ReadAll(r)
225 if err != nil {
226 t.Fatal(err)
227 }
228
229 if !bytes.Equal(got, data) {
230 showDiffs(t, got, data)
231 }
232 }
233
234
235 func showDiffs(t *testing.T, got, want []byte) {
236 t.Error("data mismatch")
237 if len(got) != len(want) {
238 t.Errorf("got data length %d, want %d", len(got), len(want))
239 }
240 diffs := 0
241 for i, b := range got {
242 if i >= len(want) {
243 break
244 }
245 if b != want[i] {
246 diffs++
247 if diffs > 20 {
248 break
249 }
250 t.Logf("%d: %#x != %#x", i, b, want[i])
251 }
252 }
253 }
254
255 func TestAlloc(t *testing.T) {
256 testenv.SkipIfOptimizationOff(t)
257 if race.Enabled {
258 t.Skip("skipping allocation test under race detector")
259 }
260
261 compressed := zstdBigData(t)
262 input := bytes.NewReader(compressed)
263 r := NewReader(input)
264 c := testing.AllocsPerRun(10, func() {
265 input.Reset(compressed)
266 r.Reset(input)
267 io.Copy(io.Discard, r)
268 })
269 if c != 0 {
270 t.Errorf("got %v allocs, want 0", c)
271 }
272 }
273
274 func TestFileSamples(t *testing.T) {
275 samples, err := os.ReadDir("testdata")
276 if err != nil {
277 t.Fatal(err)
278 }
279
280 for _, sample := range samples {
281 name := sample.Name()
282 if !strings.HasSuffix(name, ".zst") {
283 continue
284 }
285
286 t.Run(name, func(t *testing.T) {
287 f, err := os.Open(filepath.Join("testdata", name))
288 if err != nil {
289 t.Fatal(err)
290 }
291
292 r := NewReader(f)
293 h := sha256.New()
294 if _, err := io.Copy(h, r); err != nil {
295 t.Fatal(err)
296 }
297 got := fmt.Sprintf("%x", h.Sum(nil))[:8]
298
299 want, _, _ := strings.Cut(name, ".")
300 if got != want {
301 t.Errorf("Wrong uncompressed content hash: got %s, want %s", got, want)
302 }
303 })
304 }
305 }
306
307 func TestReaderBad(t *testing.T) {
308 for i, s := range badStrings {
309 t.Run(fmt.Sprintf("badStrings#%d", i), func(t *testing.T) {
310 _, err := io.Copy(io.Discard, NewReader(strings.NewReader(s)))
311 if err == nil {
312 t.Error("expected error")
313 }
314 })
315 }
316 }
317
318 func BenchmarkLarge(b *testing.B) {
319 b.StopTimer()
320 b.ReportAllocs()
321
322 compressed := zstdBigData(b)
323
324 b.SetBytes(int64(len(compressed)))
325
326 input := bytes.NewReader(compressed)
327 r := NewReader(input)
328
329 b.StartTimer()
330 for i := 0; i < b.N; i++ {
331 input.Reset(compressed)
332 r.Reset(input)
333 io.Copy(io.Discard, r)
334 }
335 }
336
View as plain text