Source file 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  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  // zstdBigData returns the compressed contents of our large test file.
   185  // This will only run on Unix systems with zstd installed.
   186  // That's OK as the package is GOOS-independent.
   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  // Test decompressing a large file. We don't have a compressor,
   212  // so this test only runs on systems with zstd installed.
   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  // showDiffs reports the first few differences in two []byte.
   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