1// Copyright 2015, Joe Tsai. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE.md file. 4 5package meta 6 7import ( 8 "bytes" 9 "io" 10 "math/rand" 11 "testing" 12 13 "github.com/dsnet/compress/internal/errors" 14 "github.com/dsnet/compress/internal/testutil" 15) 16 17// TestWriter tests that the encoded output matches the expected output exactly. 18// A failure here does not necessarily mean that the encoder is wrong since 19// there are many possible representations. Before changing the test vectors to 20// make a test pass, one must verify the new output is correct. 21func TestWriter(t *testing.T) { 22 db := testutil.MustDecodeBitGen 23 dh := testutil.MustDecodeHex 24 25 errFuncs := map[string]func(error) bool{ 26 "IsInvalid": errors.IsInvalid, 27 } 28 vectors := []struct { 29 desc string // Description of the text 30 input []byte // Test input string 31 output []byte // Expected output string 32 final FinalMode // Input final mode 33 errf string // Name of error checking callback 34 }{{ 35 desc: "empty meta block (FinalNil)", 36 input: dh(""), 37 output: db(`<<< 38 < (0 10) (00011 00000 1010) (011 000 011 001 000 (000 000)*4 010) 0 39 > (111 <D7:127) (111 <D7:99) 10 (110 <D2:3) 10 40 < 0*3 0 1*3 41 `), 42 final: FinalNil, 43 }, { 44 desc: "empty meta block (FinalMeta)", 45 input: dh(""), 46 output: db(`<<< 47 < (0 10) (00011 00000 1010) (011 000 011 001 000 (000 000)*4 010) 0 48 > 10 (111 <D7:127) (111 <D7:99) 10 (110 <D2:3) 49 < 0*3 0 1*3 50 `), 51 final: FinalMeta, 52 }, { 53 desc: "input string 'a'", 54 input: []byte("a"), 55 output: db(`<<< 56 < (0 10) (00110 00000 1000) (011 000 011 001 000 (000 000)*3 010) 0 57 > 10 0 10 0 (110 <D2:0) 10 0 (110 <D2:0) 10*2 (111 <D7:127) 58 (111 <D7:82) 10 (110 <D2:3) (110 <D2:1) 59 < 0*6 0 1*4 60 `), 61 final: FinalMeta, 62 }, { 63 desc: "input string 'ab'", 64 input: []byte("ab"), 65 output: db(`<<< 66 < (0 10) (00000 00000 1000) (011 000 011 001 000 (000 000)*3 010) 0 67 > 10 0*2 10 0*3 10 0 (110 <D2:0) 10*2 0*2 10 0*3 10*2 (111 <D7:127) 68 (111 <D7:77) 10 (110 <D2:3) 10 69 < 0*0 0 1*4 70 `), 71 final: FinalMeta, 72 }, { 73 desc: "input string 'abc'", 74 input: []byte("abc"), 75 output: db(`<<< 76 < (0 10) (00000 00000 0110) (011 000 011 001 000 (000 000)*2 010) 0 77 > 10 0 10*2 0*3 10 0 (110 <D2:0) 10*2 0*2 10 0*3 10*2 0 10*2 0*3 78 10*2 (111 <D7:127) (111 <D7:58) 10 (110 <D2:3)*2 (110 <D2:3) 79 < 0*0 0 1*5 80 `), 81 final: FinalMeta, 82 }, { 83 desc: "input string 'Hello, world!' with FinalNil", 84 input: dh("48656c6c6f2c20776f726c6421"), 85 output: db(`<<< 86 < (0 10) (00111 00000 0100) (011 000 011 001 000 (000 000)*1 010) 0 87 > 0*2 10 0 10*2 0 (110 <D2:0) 10 0*2 10 0 10 0 10 0*2 10*2 0*3 10*2 88 0 10*2 0*3 10*2 0 10*2 0 10 (110 <D2:0) 0 10*2 0*3 10*2 0 10 0 89 (110 <D2:3) 10 0*2 10*3 0 10*3 0 10 (110 <D2:0) 0 10*2 0*2 10 0*2 90 10*3 0*3 10*2 0 10*2 0*3 10 0*2 10*2 0 10 0 (110 <D2:0) 10 91 (111 <D7:124) 10 (110 <D2:3) (110 <D2:2) 92 < 0*7 0 1*6 93 `), 94 final: FinalNil, 95 }, { 96 desc: "input string 'Hello, world!' with FinalMeta", 97 input: dh("48656c6c6f2c20776f726c6421"), 98 output: db(`<<< 99 < (0 10) (00110 00000 0100) (011 000 011 001 000 (000 000)*1 010) 0 100 > 10 0 10 0 10*2 0 (110 <D2:0) 10 0*2 10 0 10 0 10 0*2 10*2 0*3 10*2 101 0 10*2 0*3 10*2 0 10*2 0 10 (110 <D2:0) 0 10*2 0*3 10*2 0 10 0 102 (110 <D2:3) 10 0*2 10*3 0 10*3 0 10 (110 <D2:0) 0 10*2 0*2 10 0*2 103 10*3 0*3 10*2 0 10*2 0*3 10 0*2 10*2 0 10 0 (110 <D2:0) 10 104 (111 <D7:125) 10 (110 <D2:3) (110 <D2:1) 105 < 0*6 0 1*6 106 `), 107 final: FinalMeta, 108 }, { 109 desc: "input string 'Hello, world!' with FinalStream", 110 input: dh("48656c6c6f2c20776f726c6421"), 111 output: db(`<<< 112 < (1 10) (00110 00000 0100) (011 000 011 001 000 (000 000)*1 010) 0 113 > 10 0 10 0 10*2 0 (110 <D2:0) 10 0*2 10 0 10 0 10 0*2 10*2 0*3 10*2 114 0 10*2 0*3 10*2 0 10*2 0 10 (110 <D2:0) 0 10*2 0*3 10*2 0 10 0 115 (110 <D2:3) 10 0*2 10*3 0 10*3 0 10 (110 <D2:0) 0 10*2 0*2 10 0*2 116 10*3 0*3 10*2 0 10*2 0*3 10 0*2 10*2 0 10 0 (110 <D2:0) 10 117 (111 <D7:125) 10 (110 <D2:3) (110 <D2:1) 118 < 0*6 0 1*6 119 `), 120 final: FinalStream, 121 }, { 122 desc: "input hex-string '00'*4", 123 input: dh("00000000"), 124 output: db(`<<< 125 < (0 10) (00110 00000 1010) (011 000 011 001 000 (000 000)*4 010) 0 126 > 10 0*3 10 (111 <D7:127) (111 <D7:96) 10 (110 <D2:2) 127 < 0*6 0 1*3 128 `), 129 final: FinalMeta, 130 }, { 131 desc: "input hex-string '00'*8", 132 input: dh("0000000000000000"), 133 output: db(`<<< 134 < (0 10) (00011 00000 1010) (011 000 011 001 000 (000 000)*4 010) 0 135 > 10 0 (110 <D2:0) 10 (111 <D7:127) (111 <D7:95) 10 (110 <D2:2) 136 < 0*3 0 1*3 137 `), 138 final: FinalMeta, 139 }, { 140 desc: "input hex-string '00'*16", 141 input: dh("00000000000000000000000000000000"), 142 output: db(`<<< 143 < (0 10) (00011 00000 1010) (011 000 011 001 000 (000 000)*4 010) 0 144 > 10 0 (110 <D2:1) 10 (111 <D7:127) (111 <D7:94) 10 (110 <D2:2) 145 < 0*3 0 1*3 146 `), 147 final: FinalMeta, 148 }, { 149 desc: "input hex-string 'ff'*4", 150 input: dh("ffffffff"), 151 output: db(`<<< 152 < (0 10) (00101 00000 1010) (011 000 011 001 000 (000 000)*4 010) 0 153 > 10*2 0*2 10 (111 <D7:127) (111 <D7:97) 10 (110 <D2:1) 154 < 0*5 0 1*3 155 `), 156 final: FinalMeta, 157 }, { 158 desc: "input hex-string 'ff'*8", 159 input: dh("ffffffffffffffff"), 160 output: db(`<<< 161 < (0 10) (00100 00000 1010) (011 000 011 001 000 (000 000)*4 010) 0 162 > 10*2 0*3 10 (111 <D7:127) (111 <D7:96) 10 (110 <D2:1) 163 < 0*4 0 1*3 164 `), 165 final: FinalMeta, 166 }, { 167 desc: "input hex-string 'ff'*16", 168 input: dh("ffffffffffffffffffffffffffffffff"), 169 output: db(`<<< 170 < (0 10) (00001 00000 1010) (011 000 011 001 000 (000 000)*4 010) 0 171 > 10*2 0 (110 <D2:0) 10 (111 <D7:127) (111 <D7:95) 10 (110 <D2:1) 172 < 0*1 0 1*3 173 `), 174 final: FinalMeta, 175 }, { 176 desc: "the random hex-string '911fe47084a4668b'", 177 input: dh("911fe47084a4668b"), 178 output: db(`<<< 179 < (0 10) (00100 00000 0100) (011 000 011 001 000 (000 000)*1 010) 0 180 > 10 0 (110 <D2:0) 10 0 10 0*3 10 0*2 10 (110 <D2:2) 0 (110 <D2:1) 181 10 0*2 10*3 0 (110 <D2:0) 10*3 0*3 10 0 (110 <D2:0) 10 0*2 10 0*2 182 10 0 10 0 10*2 0*2 10*2 0 10*2 0 10 0*3 10 (111 <D7:127) 183 (111 <D7:2) 10 (110 <D2:3)*5 (110 <D2:0) 184 < 0*4 0 1*6 185 `), 186 final: FinalMeta, 187 }, { 188 desc: "the random hex-string 'de9fa94cb16f40fc'", 189 input: dh("de9fa94cb16f40fc"), 190 output: db(`<<< 191 < (0 10) (00001 00000 0100) (011 000 011 001 000 (000 000)*1 010) 0 192 > 10*2 0*3 10 0 10 0 (110 <D2:0) 10 0 (110 <D2:3) 10*2 0*2 10*2 0 10 193 0 10 0 10*2 0*2 10*2 0 10 0 10*3 0*2 10 0 (110 <D2:1) 10 0*2 10 194 (110 <D2:3) 0 10*3 (111 <D7:127) (111 <D7:9) 10 (110 <D2:3)*5 10*2 195 < 0*1 0 1*6 196 `), 197 final: FinalMeta, 198 }, { 199 desc: "input hex-string '55'*22", 200 input: dh("55555555555555555555555555555555555555555555"), 201 output: db(`<<< 202 < (1 10) (00000 00000 0010) (011 000 011 001 000 (000 000)*0 010) 0 203 > 10 0*2 10*2 0 10*2 (0 10)*87 (111 <D7:27) 10 (110 <D2:3)*5 (110 <D2:2) 204 < 0*0 0 1*7 205 `), 206 final: FinalStream, 207 }, { 208 desc: "input hex-string '55'*23", 209 input: dh("5555555555555555555555555555555555555555555555"), 210 output: db(`<<< 211 < (0 10) (00000 00000 0010) (011 000 011 001 000 (000 000)*0 010) 0 212 > 10 0 10*3 0 10*2 (0 10)*91 (111 <D7:24) 10 (110 <D2:3)*5 213 < 0*0 0 1*7 214 `), 215 final: FinalMeta, 216 }, { 217 desc: "input hex-string '55'*24", 218 input: dh("555555555555555555555555555555555555555555555555"), 219 output: db(`<<< 220 < (0 10) (00111 00000 0010) (011 000 011 001 000 010) 221 > 0 (110 <D2:2) 10*3 (0 10)*95 (111 <D7:17) 10 (110 <D2:3)*4 (110 <D2:2) 222 < 0*7 0 1*7 223 `), 224 final: FinalNil, 225 }, { 226 desc: "input hex-string '55'*25", 227 input: dh("55555555555555555555555555555555555555555555555555"), 228 output: db(`<<< 229 < (1 10) (00110 00000 0010) (011 000 011 001 000 010) 230 > 0 10 0 10 0*2 10*3 (0 10)*99 (111 <D7:15) 10 (110 <D2:3)*3 (110 <D2:2) 231 < 0*6 0 1*7 232 `), 233 final: FinalStream, 234 }, { 235 desc: "input hex-string '55'*26", 236 input: dh("5555555555555555555555555555555555555555555555555555"), 237 output: db(`<<< 238 < (0 10) (00101 00000 0010) (011 000 011 001 000 010) 239 > 0 10 0*2 10 0 10*3 (0 10)*103 (111 <D7:11) 10 (110 <D2:3)*3 10 240 < 0*5 0 1*7 241 `), 242 final: FinalMeta, 243 }, { 244 desc: "input hex-string '55'*27", 245 input: dh("555555555555555555555555555555555555555555555555555555"), 246 output: db(`<<< 247 < (0 10) (00011 00000 0010) (011 000 011 001 000 010) 248 > 0*3 10*2 0 10*3 (0 10)*107 (111 <D7:7) 10 (110 <D2:3)*2 (110 <D2:0) 249 < 0*3 0 1*7 250 `), 251 final: FinalNil, 252 }, { 253 desc: "input hex-string '55'*28", 254 input: dh("55555555555555555555555555555555555555555555555555555555"), 255 output: db(`<<< 256 < (1 10) (00101 00000 0010) (011 000 011 001 000 010) 257 > 0 10 0*3 10 (110 <D2:0) (0 10)*111 (111 <D7:3) 10 (110 <D2:3) (110 <D2:2) 258 < 0*5 0 1*7 259 `), 260 final: FinalStream, 261 }, { 262 desc: "input hex-string '55'*29", 263 input: dh("5555555555555555555555555555555555555555555555555555555555"), 264 output: db(`<<< 265 < (0 10) (00101 00000 0010) (011 000 011 001 000 010) 266 > 0 10 0 10 0 10 (110 <D2:0) (0 10)*115 (111 <D7:0) 10 (110 <D2:3) 267 < 0*5 0 1*7 268 `), 269 final: FinalMeta, 270 }, { 271 desc: "input hex-string '55'*30", 272 input: dh("555555555555555555555555555555555555555555555555555555555555"), 273 output: db(`<<< 274 < (0 10) (00110 00000 0010) (011 000 011 001 000 010) 275 > 0 (110 <D2:0) 10 (110 <D2:1) (0 10)*119 0 (110 <D2:2) 10 (110 <D2:0) 276 < 0*6 0 1*7 277 `), 278 final: FinalNil, 279 }, { 280 desc: "input hex-string '55'*31", 281 input: dh("55555555555555555555555555555555555555555555555555555555555555"), 282 final: FinalStream, 283 errf: "IsInvalid", 284 }, { 285 desc: "input hex-string '55'*32", 286 input: dh("5555555555555555555555555555555555555555555555555555555555555555"), 287 final: FinalMeta, 288 errf: "IsInvalid", 289 }, { 290 desc: "input hex-string '73de76bebcf69d5fed3fb3cee87bacfd7de876facffedf'", 291 input: dh("73de76bebcf69d5fed3fb3cee87bacfd7de876facffedf"), 292 output: db(`<<< 293 < (0 10) (00010 00000 0100) (011 000 011 001 000 (000 000) 010) 294 > 0*2 10 (110 <D2:0) 0 10 0*2 10*2 0*3 10*2 0 (110 <D2:0) 10 0*2 10 295 0*2 10 0*3 10*2 0 (110 <D2:1) 10 0 10*2 0 (110 <D2:0) 10 0 10 0*2 296 10 0 (110 <D2:1) 10 0*3 10*2 0 (110 <D2:2) 10 0 10 0 10 0*2 10 0 297 (110 <D2:3) 0*2 10*2 0*2 10*2 0*2 10 0 10 0*3 10*2 0*2 10*3 0 10 0 298 (110 <D2:1) 10 0 (110 <D2:0) 10*3 0*2 10 0 10 0*2 10 0 (110 <D2:3) 299 10 0 (110 <D2:1) 10 (110 <D2:0) 0 10 0*3 10 0*2 10 0*3 10*2 0 10 0 300 (110 <D2:3) 0*2 10*2 0*2 10 (111 <D7:1) 10 (111 <D7:53) 10*3 301 < 0*2 0 1*6 302 `), 303 final: FinalNil, 304 }, { 305 desc: "input hex-string '73de76bebcf69d5fed3fb3cee87bacfd7de876facffede'", 306 input: dh("73de76bebcf69d5fed3fb3cee87bacfd7de876facffede"), 307 final: FinalStream, 308 errf: "IsInvalid", 309 }, { 310 desc: "input hex-string 'def773bfab15d257ffffffbbafdf3fef6e1fefd6e75ffffff6fefcff67d9'", 311 input: dh("def773bfab15d257ffffffbbafdf3fef6e1fefd6e75ffffff6fefcff67d9"), 312 output: db(`<<< 313 < (0 10) (00111 00000 0100) (011 000 011 001 000 (000 000) 010) 314 > 0 10*2 0 10 (110 <D2:1) 0 (110 <D2:0) 10 0 (110 <D2:1) 10 0 315 (110 <D2:2) 10*2 0*3 10 0 (110 <D2:2) 10 0*3 10 0 10 0 10 0*2 10 0 316 10 0 10 (110 <D2:0) 0 10*2 0 10 0 (110 <D2:1) 10 0 10 0 10 317 (111 <D7:15) 10 0*3 10 0 (110 <D2:1) 10 0 10 0 (110 <D2:2) 10 0 318 (110 <D2:3) 0 10*2 0 (110 <D2:0) 10 0*3 10 0*3 10 0*2 10 0 319 (110 <D2:1) 10*3 0 (110 <D2:0) 10 0*3 10 0*2 10 0 10 0 (110 <D2:1) 320 10*2 0 (110 <D2:3) 0 10 0 10 (111 <D7:5) 10 0*2 10 0 (110 <D2:0) 321 10 0 (110 <D2:3) 10*2 (111 <D7:6) 10*2 0*2 10 0 10*2 0*2 10 0 322 (110 <D2:3) 0 10*3 323 < 0*7 0 1*6 324 `), 325 final: FinalMeta, 326 }, { 327 desc: "input hex-string 'dff773bfab15d257ffffffbbafdf3fef6e1fefd6e75ffffff6fefcff67d9'", 328 input: dh("dff773bfab15d257ffffffbbafdf3fef6e1fefd6e75ffffff6fefcff67d9"), 329 final: FinalMeta, 330 errf: "IsInvalid", 331 }} 332 333 for i, v := range vectors { 334 var b bytes.Buffer 335 mw := NewWriter(&b) 336 mw.bufCnt = copy(mw.buf[:], v.input) 337 for _, b := range v.input { 338 b0s, b1s := numBits(b) 339 mw.buf0s, mw.buf1s = b0s+mw.buf0s, b1s+mw.buf1s 340 } 341 err := mw.encodeBlock(v.final) 342 output := b.Bytes() 343 344 if got, want, ok := testutil.BytesCompare(output, v.output); !ok { 345 t.Errorf("test %d (%s), mismatching data:\ngot %s\nwant %s", i, v.desc, got, want) 346 } 347 if len(output) != int(mw.OutputOffset) { 348 t.Errorf("test %d (%s), mismatching offset: got %d, want %d", i, v.desc, len(output), mw.OutputOffset) 349 } 350 if v.errf != "" && !errFuncs[v.errf](err) { 351 t.Errorf("test %d (%s), mismatching error:\ngot %v\nwant %s(got) == true", i, v.desc, err, v.errf) 352 } else if v.errf == "" && err != nil { 353 t.Errorf("test %d (%s), unexpected error: got %v", i, v.desc, err) 354 } 355 } 356} 357 358type faultyWriter struct{} 359 360func (faultyWriter) Write([]byte) (int, error) { return 0, io.ErrShortWrite } 361 362func TestWriterReset(t *testing.T) { 363 bb := new(bytes.Buffer) 364 mw := NewWriter(bb) 365 buf := make([]byte, 512) 366 367 // Test Writer for idempotent Close. 368 if err := mw.Close(); err != nil { 369 t.Errorf("unexpected error, Close() = %v", err) 370 } 371 if err := mw.Close(); err != nil { 372 t.Errorf("unexpected error, Close() = %v", err) 373 } 374 if _, err := mw.Write(buf); err != errClosed { 375 t.Errorf("unexpected error, Write(...) = %v, want %v", err, errClosed) 376 } 377 378 // Test Writer with faulty writer. 379 mw.Reset(faultyWriter{}) 380 if _, err := mw.Write(buf); err != io.ErrShortWrite { 381 t.Errorf("unexpected error, Write(...) = %v, want %v", err, io.ErrShortWrite) 382 } 383 if err := mw.Close(); err != io.ErrShortWrite { 384 t.Errorf("unexpected error, Close() = %v, want %v", err, io.ErrShortWrite) 385 } 386 387 // Test Writer in normal use. 388 bb.Reset() 389 mw.Reset(bb) 390 data := []byte("The quick brown fox jumped over the lazy dog.") 391 cnt, err := mw.Write(data) 392 if err != nil { 393 t.Errorf("unexpected error, Write(...) = %v", err) 394 } 395 if cnt != len(data) { 396 t.Errorf("write count mismatch, got %d, want %d", cnt, len(data)) 397 } 398 if err := mw.Close(); err != nil { 399 t.Errorf("unexpected error, Close() = %v", err) 400 } 401 if mw.InputOffset != int64(len(data)) { 402 t.Errorf("input offset mismatch, got %d, want %d", mw.InputOffset, len(data)) 403 } 404 if mw.OutputOffset != int64(bb.Len()) { 405 t.Errorf("output offset mismatch, got %d, want %d", mw.OutputOffset, bb.Len()) 406 } 407} 408 409func BenchmarkWriter(b *testing.B) { 410 data := make([]byte, 1<<16) 411 rand.Read(data) 412 413 rd := new(bytes.Reader) 414 bb := new(bytes.Buffer) 415 mw := new(Writer) 416 417 b.ReportAllocs() 418 b.SetBytes(int64(len(data))) 419 b.ResetTimer() 420 421 for i := 0; i < b.N; i++ { 422 rd.Reset(data) 423 bb.Reset() 424 mw.Reset(bb) 425 426 cnt, err := io.Copy(mw, rd) 427 if cnt != int64(len(data)) || err != nil { 428 b.Fatalf("Copy() = (%d, %v), want (%d, nil)", cnt, err, len(data)) 429 } 430 if err := mw.Close(); err != nil { 431 b.Fatalf("Close() = %v, want nil", err) 432 } 433 } 434} 435