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 xflate 6 7import ( 8 "bytes" 9 "compress/flate" 10 "io/ioutil" 11 "testing" 12 13 "github.com/dsnet/compress/internal/testutil" 14) 15 16func TestWriter(t *testing.T) { 17 br := bytes.Repeat 18 dh := testutil.MustDecodeHex 19 20 vectors := []struct { 21 desc string // Test description 22 conf *WriterConfig // Input WriterConfig 23 input []interface{} // Test input tokens (either flush mode or input string) 24 output []byte // Expected output string 25 }{{ 26 desc: "empty stream", 27 input: []interface{}{}, 28 output: dh("" + 29 "0d008705000048c82a51e8ff37dbf1", // Footer 30 ), 31 }, { 32 desc: "empty stream with empty chunk", 33 input: []interface{}{FlushSync}, 34 output: dh("" + 35 "000000ffff000000ffff" + // Chunk0 36 "34c086050020916cb2a50bd20369da192deaff3bda05f8" + // Index0 37 "1dc08605002021ab44219b4aff7fd6de3bf8", // Footer 38 ), 39 }, { 40 desc: "empty stream with empty index", 41 input: []interface{}{FlushIndex}, 42 output: dh("" + 43 "04c086050020191d53a1a508c9e8ff5bda7bf8" + // Index0 44 "15c08605002021ab44219ba2ff2f6bef5df8", // Footer 45 ), 46 }, { 47 desc: "empty stream with multiple empty chunks", 48 input: []interface{}{FlushFull, FlushFull, FlushFull}, 49 output: dh("" + 50 "000000ffff" + // Chunk0 51 "000000ffff" + // Chunk1 52 "000000ffff" + // Chunk2 53 "148086058044655366e3817441ba205d504a83348c445ddcde7b6ffc" + // Index0 54 "15c08605002021ab44a103aaff2f6bef5df8", // Footer 55 ), 56 }, { 57 desc: "empty stream with multiple empty indexes", 58 input: []interface{}{FlushIndex, FlushIndex, FlushIndex}, 59 output: dh("" + 60 "04c086050020191d53a1a508c9e8ff5bda7bf8" + // Index0 61 "3cc08605002019293a24a55464a585faff9bf600f8" + // Index1 62 "04c08605002019493a2494d050560afd7f4c7bfb" + // Index2 63 "25008705000048c82a51e880f4ff834df0", // Footer 64 ), 65 }, { 66 desc: "3k zeros, 1KiB chunks", 67 conf: &WriterConfig{ChunkSize: 1 << 10}, 68 input: []interface{}{br([]byte{0}, 3000)}, 69 output: dh("" + 70 "621805a360148c5800000000ffff" + // Chunk0 71 "621805a360148c5800000000ffff" + // Chunk1 72 "621805a360140c3900000000ffff" + // Chunk2 73 "1c8086058044642b3bc9aa3464540784acea809055d99586dd5492446555a7b607fc" + // Index0 74 "0d008705000048c82a51c81ea1ff0f6cf2", // Footer 75 ), 76 }, { 77 desc: "quick brown fox - manual chunking/indexing", 78 input: []interface{}{ 79 "the quick", FlushSync, " brown fox", FlushFull, FlushFull, " jumped over the", FlushIndex, " lazy dog", 80 }, 81 output: dh("" + 82 "2ac94855282ccd4cce06000000ffff52482aca2fcf5348cbaf00000000ffff" + // Chunk0 83 "000000ffff" + // Chunk1 84 "52c82acd2d484d51c82f4b2d5228c94805000000ffff" + // Chunk2 85 "2480860580446553762a0ad14211d207253b234546a1528ad4d3edbd0bfc" + // Index0 86 "52c849acaa5448c94f07000000ffff" + // Chunk3 87 "2c8086058044a281ec8611190d23b21221ca0851fdafbdf7de05fc" + // Index1 88 "1dc08605002021ab44219b52ff7fd6de3bf8", // Footer 89 ), 90 }, { 91 desc: "quick brown fox - automatic chunking/indexing", 92 conf: &WriterConfig{ChunkSize: 4, IndexSize: 3}, 93 input: []interface{}{"the quick brown fox jumped over the lazy dog"}, 94 output: dh("" + 95 "2ac9485500000000ffff" + // Chunk0 96 "2a2ccd4c06000000ffff" + // Chunk1 97 "ca56482a02000000ffff" + // Chunk2 98 "2c8086058044655376c32a2b9999c9cc4c665691d04ea5a474747bef01fc" + // Index0 99 "ca2fcf5300000000ffff" + // Chunk3 100 "4acbaf5000000000ffff" + // Chunk4 101 "ca2acd2d00000000ffff" + // Chunk5 102 "04808605804445036537acb2929999cccc6466cb48112a45a193db7beffc" + // Index1 103 "4a4d51c807000000ffff" + // Chunk6 104 "2a4b2d5200000000ffff" + // Chunk7 105 "2ac9485500000000ffff" + // Chunk8 106 "04808605804445036537acb2929999cccc6466cb48112a45a193db7beffc" + // Index2 107 "ca49acaa04000000ffff" + // Chunk9 108 "5248c94f07000000ffff" + // Chunk10 109 "148086058084a261644b665632339399d9425629a44877b7f7de3bfc" + // Index3 110 "15c08605002021ab44a103aaff2f6bef5df8", // Footer 111 ), 112 }} 113 114 for i, v := range vectors { 115 // Encode the test input. 116 var b, bb bytes.Buffer 117 xw, err := NewWriter(&b, v.conf) 118 if err != nil { 119 t.Errorf("test %d (%s), unexpected error: NewWriter() = %v", i, v.desc, err) 120 } 121 for _, tok := range v.input { 122 switch tok := tok.(type) { 123 case string: 124 bb.WriteString(tok) 125 if _, err := xw.Write([]byte(tok)); err != nil { 126 t.Errorf("test %d (%s), unexpected error: Write() = %v", i, v.desc, err) 127 } 128 case []byte: 129 bb.Write(tok) 130 if _, err := xw.Write(tok); err != nil { 131 t.Errorf("test %d (%s), unexpected error: Write() = %v", i, v.desc, err) 132 } 133 case FlushMode: 134 if err := xw.Flush(tok); err != nil { 135 t.Errorf("test %d (%s), unexpected error: Flush() = %v", i, v.desc, err) 136 } 137 default: 138 t.Fatalf("test %d (%s), unknown token: %v", i, v.desc, tok) 139 } 140 } 141 if err := xw.Close(); err != nil { 142 t.Errorf("test %d (%s), unexpected error: Close() = %v", i, v.desc, err) 143 } 144 if got, want, ok := testutil.BytesCompare(b.Bytes(), v.output); !ok { 145 t.Errorf("test %d (%s), mismatching bytes:\ngot %s\nwant %s", i, v.desc, got, want) 146 } 147 if xw.OutputOffset != int64(b.Len()) { 148 t.Errorf("test %d (%s), output offset mismatch: got %d, want %d", i, v.desc, xw.OutputOffset, b.Len()) 149 } 150 if xw.InputOffset != int64(bb.Len()) { 151 t.Errorf("test %d (%s), input offset mismatch: got %d, want %d", i, v.desc, xw.InputOffset, bb.Len()) 152 } 153 154 // Verify that the output stream is DEFLATE compatible. 155 rd := bytes.NewReader(b.Bytes()) 156 fr := flate.NewReader(rd) 157 buf, err := ioutil.ReadAll(fr) 158 if err != nil { 159 t.Errorf("test %d (%s), unexpected error: ReadAll() = %v", i, v.desc, err) 160 } 161 if got, want, ok := testutil.BytesCompare(buf, bb.Bytes()); !ok { 162 t.Errorf("test %d (%s), mismatching bytes:\ngot %s\nwant %s", i, v.desc, got, want) 163 } 164 if rd.Len() > 0 { 165 t.Errorf("test %d (%s), not all bytes consumed: %d > 0", i, v.desc, rd.Len()) 166 } 167 } 168} 169 170func TestWriterReset(t *testing.T) { 171 // Test bad Writer config. 172 if _, err := NewWriter(ioutil.Discard, &WriterConfig{Level: -431}); err == nil { 173 t.Fatalf("unexpected success: NewWriter()") 174 } 175 176 // Test Writer for idempotent Close. 177 xw := new(Writer) 178 xw.Reset(ioutil.Discard) 179 if _, err := xw.Write([]byte("hello, world!")); err != nil { 180 t.Fatalf("unexpected error: Write() = %v", err) 181 } 182 if err := xw.Close(); err != nil { 183 t.Fatalf("unexpected error: Close() = %v", err) 184 } 185 if err := xw.Close(); err != nil { 186 t.Fatalf("unexpected error: Close() = %v", err) 187 } 188 if _, err := xw.Write([]byte("hello, world!")); err != errClosed { 189 t.Fatalf("mismatching error: Write() = %v, want %v", err, errClosed) 190 } 191} 192 193// BenchmarkWriter benchmarks the overhead of the XFLATE format over DEFLATE. 194// Thus, it intentionally uses a very small chunk size with no compression. 195func BenchmarkWriter(b *testing.B) { 196 twain := testutil.MustLoadFile("../testdata/twain.txt") 197 bb := bytes.NewBuffer(make([]byte, 0, 2*len(twain))) 198 xw, _ := NewWriter(nil, &WriterConfig{Level: NoCompression, ChunkSize: 1 << 10}) 199 200 b.ReportAllocs() 201 b.SetBytes(int64(len(twain))) 202 b.ResetTimer() 203 204 for i := 0; i < b.N; i++ { 205 bb.Reset() 206 xw.Reset(bb) 207 if _, err := xw.Write(twain); err != nil { 208 b.Fatalf("unexpected error: Write() = %v", err) 209 } 210 if err := xw.Close(); err != nil { 211 b.Fatalf("unexpected error: Close() = %v", err) 212 } 213 } 214} 215