]> Cypherpunks.ru repositories - gostls13.git/blob - src/internal/zstd/zstd_test.go
internal/zstd: reset reader buffer
[gostls13.git] / src / internal / zstd / zstd_test.go
1 // Copyright 2023 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
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 // tests holds some simple test cases, including some found by fuzzing.
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                 // a small compressed .debug_ranges section.
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 // bigData returns the contents of our large test file repeated multiple times.
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 var (
171         zstdBigOnce  sync.Once
172         zstdBigBytes []byte
173         zstdBigSkip  bool
174         zstdBigErr   error
175 )
176
177 // zstdBigData returns the compressed contents of our large test file.
178 // This will only run on Unix systems with zstd installed.
179 // That's OK as the package is GOOS-independent.
180 func zstdBigData(t testing.TB) []byte {
181         input := bigData(t)
182
183         zstdBigOnce.Do(func() {
184                 if _, err := os.Stat("/usr/bin/zstd"); err != nil {
185                         zstdBigSkip = true
186                         return
187                 }
188
189                 cmd := exec.Command("/usr/bin/zstd", "-z")
190                 cmd.Stdin = bytes.NewReader(input)
191                 var compressed bytes.Buffer
192                 cmd.Stdout = &compressed
193                 cmd.Stderr = os.Stderr
194                 if err := cmd.Run(); err != nil {
195                         zstdBigErr = fmt.Errorf("running zstd failed: %v", err)
196                         return
197                 }
198
199                 zstdBigBytes = compressed.Bytes()
200         })
201         if zstdBigSkip {
202                 t.Skip("skipping because /usr/bin/zstd does not exist")
203         }
204         if zstdBigErr != nil {
205                 t.Fatal(zstdBigErr)
206         }
207         return zstdBigBytes
208 }
209
210 // Test decompressing a large file. We don't have a compressor,
211 // so this test only runs on systems with zstd installed.
212 func TestLarge(t *testing.T) {
213         if testing.Short() {
214                 t.Skip("skipping expensive test in short mode")
215         }
216
217         data := bigData(t)
218         compressed := zstdBigData(t)
219
220         t.Logf("/usr/bin/zstd compressed %d bytes to %d", len(data), len(compressed))
221
222         r := NewReader(bytes.NewReader(compressed))
223         got, err := io.ReadAll(r)
224         if err != nil {
225                 t.Fatal(err)
226         }
227
228         if !bytes.Equal(got, data) {
229                 showDiffs(t, got, data)
230         }
231 }
232
233 // showDiffs reports the first few differences in two []byte.
234 func showDiffs(t *testing.T, got, want []byte) {
235         t.Error("data mismatch")
236         if len(got) != len(want) {
237                 t.Errorf("got data length %d, want %d", len(got), len(want))
238         }
239         diffs := 0
240         for i, b := range got {
241                 if i >= len(want) {
242                         break
243                 }
244                 if b != want[i] {
245                         diffs++
246                         if diffs > 20 {
247                                 break
248                         }
249                         t.Logf("%d: %#x != %#x", i, b, want[i])
250                 }
251         }
252 }
253
254 func TestAlloc(t *testing.T) {
255         testenv.SkipIfOptimizationOff(t)
256         if race.Enabled {
257                 t.Skip("skipping allocation test under race detector")
258         }
259
260         compressed := zstdBigData(t)
261         input := bytes.NewReader(compressed)
262         r := NewReader(input)
263         c := testing.AllocsPerRun(10, func() {
264                 input.Reset(compressed)
265                 r.Reset(input)
266                 io.Copy(io.Discard, r)
267         })
268         if c != 0 {
269                 t.Errorf("got %v allocs, want 0", c)
270         }
271 }
272
273 func TestFileSamples(t *testing.T) {
274         samples, err := os.ReadDir("testdata")
275         if err != nil {
276                 t.Fatal(err)
277         }
278
279         for _, sample := range samples {
280                 name := sample.Name()
281                 if !strings.HasSuffix(name, ".zst") {
282                         continue
283                 }
284
285                 t.Run(name, func(t *testing.T) {
286                         f, err := os.Open(filepath.Join("testdata", name))
287                         if err != nil {
288                                 t.Fatal(err)
289                         }
290
291                         r := NewReader(f)
292                         h := sha256.New()
293                         if _, err := io.Copy(h, r); err != nil {
294                                 t.Fatal(err)
295                         }
296                         got := fmt.Sprintf("%x", h.Sum(nil))[:8]
297
298                         want, _, _ := strings.Cut(name, ".")
299                         if got != want {
300                                 t.Errorf("Wrong uncompressed content hash: got %s, want %s", got, want)
301                         }
302                 })
303         }
304 }
305
306 func BenchmarkLarge(b *testing.B) {
307         b.StopTimer()
308         b.ReportAllocs()
309
310         compressed := zstdBigData(b)
311
312         b.SetBytes(int64(len(compressed)))
313
314         input := bytes.NewReader(compressed)
315         r := NewReader(input)
316
317         b.StartTimer()
318         for i := 0; i < b.N; i++ {
319                 input.Reset(compressed)
320                 r.Reset(input)
321                 io.Copy(io.Discard, r)
322         }
323 }