github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/compressio/compressio_test.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package compressio
    16  
    17  import (
    18  	"bytes"
    19  	"compress/flate"
    20  	"encoding/base64"
    21  	"fmt"
    22  	"io"
    23  	"math/rand"
    24  	"runtime"
    25  	"testing"
    26  	"time"
    27  )
    28  
    29  type harness interface {
    30  	Errorf(format string, v ...interface{})
    31  	Fatalf(format string, v ...interface{})
    32  	Logf(format string, v ...interface{})
    33  }
    34  
    35  func initTest(t harness, size int) []byte {
    36  	// Set number of processes to number of CPUs.
    37  	runtime.GOMAXPROCS(runtime.NumCPU())
    38  
    39  	// Construct synthetic data. We do this by encoding random data with
    40  	// base64. This gives a high level of entropy, but still quite a bit of
    41  	// structure, to give reasonable compression ratios (~75%).
    42  	var buf bytes.Buffer
    43  	bufW := base64.NewEncoder(base64.RawStdEncoding, &buf)
    44  	bufR := rand.New(rand.NewSource(0))
    45  	if _, err := io.CopyN(bufW, bufR, int64(size)); err != nil {
    46  		t.Fatalf("unable to seed random data: %v", err)
    47  	}
    48  	return buf.Bytes()
    49  }
    50  
    51  type testOpts struct {
    52  	Name            string
    53  	Data            []byte
    54  	NewWriter       func(*bytes.Buffer) (io.Writer, error)
    55  	NewReader       func(*bytes.Buffer) (io.Reader, error)
    56  	PreCompress     func()
    57  	PostCompress    func()
    58  	PreDecompress   func()
    59  	PostDecompress  func()
    60  	CompressIters   int
    61  	DecompressIters int
    62  	CorruptData     bool
    63  }
    64  
    65  func doTest(t harness, opts testOpts) {
    66  	// Compress.
    67  	var compressed bytes.Buffer
    68  	compressionStartTime := time.Now()
    69  	if opts.PreCompress != nil {
    70  		opts.PreCompress()
    71  	}
    72  	if opts.CompressIters <= 0 {
    73  		opts.CompressIters = 1
    74  	}
    75  	for i := 0; i < opts.CompressIters; i++ {
    76  		compressed.Reset()
    77  		w, err := opts.NewWriter(&compressed)
    78  		if err != nil {
    79  			t.Errorf("%s: NewWriter got err %v, expected nil", opts.Name, err)
    80  		}
    81  		if _, err := io.Copy(w, bytes.NewBuffer(opts.Data)); err != nil {
    82  			t.Errorf("%s: compress got err %v, expected nil", opts.Name, err)
    83  			return
    84  		}
    85  		closer, ok := w.(io.Closer)
    86  		if ok {
    87  			if err := closer.Close(); err != nil {
    88  				t.Errorf("%s: got err %v, expected nil", opts.Name, err)
    89  				return
    90  			}
    91  		}
    92  	}
    93  	if opts.PostCompress != nil {
    94  		opts.PostCompress()
    95  	}
    96  	compressionTime := time.Since(compressionStartTime)
    97  	compressionRatio := float32(compressed.Len()) / float32(len(opts.Data))
    98  
    99  	// Decompress.
   100  	var decompressed bytes.Buffer
   101  	decompressionStartTime := time.Now()
   102  	if opts.PreDecompress != nil {
   103  		opts.PreDecompress()
   104  	}
   105  	if opts.DecompressIters <= 0 {
   106  		opts.DecompressIters = 1
   107  	}
   108  	if opts.CorruptData {
   109  		b := compressed.Bytes()
   110  		b[rand.Intn(len(b))]++
   111  	}
   112  	for i := 0; i < opts.DecompressIters; i++ {
   113  		decompressed.Reset()
   114  		r, err := opts.NewReader(bytes.NewBuffer(compressed.Bytes()))
   115  		if err != nil {
   116  			if opts.CorruptData {
   117  				continue
   118  			}
   119  			t.Errorf("%s: NewReader got err %v, expected nil", opts.Name, err)
   120  			return
   121  		}
   122  		if _, err := io.Copy(&decompressed, r); (err != nil) != opts.CorruptData {
   123  			t.Errorf("%s: decompress got err %v unexpectly", opts.Name, err)
   124  			return
   125  		}
   126  	}
   127  	if opts.PostDecompress != nil {
   128  		opts.PostDecompress()
   129  	}
   130  	decompressionTime := time.Since(decompressionStartTime)
   131  
   132  	if opts.CorruptData {
   133  		return
   134  	}
   135  
   136  	// Verify.
   137  	if decompressed.Len() != len(opts.Data) {
   138  		t.Errorf("%s: got %d bytes, expected %d", opts.Name, decompressed.Len(), len(opts.Data))
   139  	}
   140  	if !bytes.Equal(opts.Data, decompressed.Bytes()) {
   141  		t.Errorf("%s: got mismatch, expected match", opts.Name)
   142  		if len(opts.Data) < 32 { // Don't flood the logs.
   143  			t.Errorf("got %v, expected %v", decompressed.Bytes(), opts.Data)
   144  		}
   145  	}
   146  
   147  	t.Logf("%s: compression time %v, ratio %2.2f, decompression time %v",
   148  		opts.Name, compressionTime, compressionRatio, decompressionTime)
   149  }
   150  
   151  var hashKey = []byte("01234567890123456789012345678901")
   152  
   153  func TestCompress(t *testing.T) {
   154  	rand.Seed(time.Now().Unix())
   155  
   156  	var (
   157  		data  = initTest(t, 10*1024*1024)
   158  		data0 = data[:0]
   159  		data1 = data[:1]
   160  		data2 = data[:11]
   161  		data3 = data[:16]
   162  		data4 = data[:]
   163  	)
   164  
   165  	for _, data := range [][]byte{data0, data1, data2, data3, data4} {
   166  		for _, blockSize := range []uint32{1, 4, 1024, 4 * 1024, 16 * 1024} {
   167  			// Skip annoying tests; they just take too long.
   168  			if blockSize <= 16 && len(data) > 16 {
   169  				continue
   170  			}
   171  
   172  			for _, key := range [][]byte{nil, hashKey} {
   173  				for _, corruptData := range []bool{false, true} {
   174  					if key == nil && corruptData {
   175  						// No need to test corrupt data
   176  						// case when not doing hashing.
   177  						continue
   178  					}
   179  					// Do the compress test.
   180  					doTest(t, testOpts{
   181  						Name: fmt.Sprintf("len(data)=%d, blockSize=%d, key=%s, corruptData=%v", len(data), blockSize, string(key), corruptData),
   182  						Data: data,
   183  						NewWriter: func(b *bytes.Buffer) (io.Writer, error) {
   184  							return NewWriter(b, key, blockSize, flate.BestSpeed)
   185  						},
   186  						NewReader: func(b *bytes.Buffer) (io.Reader, error) {
   187  							return NewReader(b, key)
   188  						},
   189  						CorruptData: corruptData,
   190  					})
   191  				}
   192  			}
   193  		}
   194  
   195  		// Do the vanilla test.
   196  		doTest(t, testOpts{
   197  			Name: fmt.Sprintf("len(data)=%d, vanilla flate", len(data)),
   198  			Data: data,
   199  			NewWriter: func(b *bytes.Buffer) (io.Writer, error) {
   200  				return flate.NewWriter(b, flate.BestSpeed)
   201  			},
   202  			NewReader: func(b *bytes.Buffer) (io.Reader, error) {
   203  				return flate.NewReader(b), nil
   204  			},
   205  		})
   206  	}
   207  }
   208  
   209  const (
   210  	benchDataSize = 600 * 1024 * 1024
   211  )
   212  
   213  func benchmark(b *testing.B, compress bool, hash bool, blockSize uint32) {
   214  	b.StopTimer()
   215  	b.SetBytes(benchDataSize)
   216  	data := initTest(b, benchDataSize)
   217  	compIters := b.N
   218  	decompIters := b.N
   219  	if compress {
   220  		decompIters = 0
   221  	} else {
   222  		compIters = 0
   223  	}
   224  	key := hashKey
   225  	if !hash {
   226  		key = nil
   227  	}
   228  	doTest(b, testOpts{
   229  		Name:         fmt.Sprintf("compress=%t, hash=%t, len(data)=%d, blockSize=%d", compress, hash, len(data), blockSize),
   230  		Data:         data,
   231  		PreCompress:  b.StartTimer,
   232  		PostCompress: b.StopTimer,
   233  		NewWriter: func(b *bytes.Buffer) (io.Writer, error) {
   234  			return NewWriter(b, key, blockSize, flate.BestSpeed)
   235  		},
   236  		NewReader: func(b *bytes.Buffer) (io.Reader, error) {
   237  			return NewReader(b, key)
   238  		},
   239  		CompressIters:   compIters,
   240  		DecompressIters: decompIters,
   241  	})
   242  }
   243  
   244  func BenchmarkCompressNoHash64K(b *testing.B) {
   245  	benchmark(b, true, false, 64*1024)
   246  }
   247  
   248  func BenchmarkCompressHash64K(b *testing.B) {
   249  	benchmark(b, true, true, 64*1024)
   250  }
   251  
   252  func BenchmarkDecompressNoHash64K(b *testing.B) {
   253  	benchmark(b, false, false, 64*1024)
   254  }
   255  
   256  func BenchmarkDecompressHash64K(b *testing.B) {
   257  	benchmark(b, false, true, 64*1024)
   258  }
   259  
   260  func BenchmarkCompressNoHash1M(b *testing.B) {
   261  	benchmark(b, true, false, 1024*1024)
   262  }
   263  
   264  func BenchmarkCompressHash1M(b *testing.B) {
   265  	benchmark(b, true, true, 1024*1024)
   266  }
   267  
   268  func BenchmarkDecompressNoHash1M(b *testing.B) {
   269  	benchmark(b, false, false, 1024*1024)
   270  }
   271  
   272  func BenchmarkDecompressHash1M(b *testing.B) {
   273  	benchmark(b, false, true, 1024*1024)
   274  }
   275  
   276  func BenchmarkCompressNoHash16M(b *testing.B) {
   277  	benchmark(b, true, false, 16*1024*1024)
   278  }
   279  
   280  func BenchmarkCompressHash16M(b *testing.B) {
   281  	benchmark(b, true, true, 16*1024*1024)
   282  }
   283  
   284  func BenchmarkDecompressNoHash16M(b *testing.B) {
   285  	benchmark(b, false, false, 16*1024*1024)
   286  }
   287  
   288  func BenchmarkDecompressHash16M(b *testing.B) {
   289  	benchmark(b, false, true, 16*1024*1024)
   290  }