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 }