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