• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Wuffs 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//    https://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
15package rac
16
17import (
18	"bytes"
19	"encoding/hex"
20	"fmt"
21	"io"
22	"io/ioutil"
23	"os"
24	"strings"
25	"testing"
26)
27
28const bytesPerHexDumpLine = 79
29
30const fakeCodec = Codec(0xEE)
31
32// Note that these exact bytes depends on the zlib encoder's algorithm, but
33// there is more than one valid zlib encoding of any given input. This "compare
34// to golden output" test is admittedly brittle, as the standard library's zlib
35// package's output isn't necessarily stable across Go releases.
36
37const writerWantEmpty = "" +
38	"00000000  72 c3 63 01 30 14 00 ff  00 00 00 00 00 00 00 ee  |r.c.0...........|\n" +
39	"00000010  20 00 00 00 00 00 01 ff  20 00 00 00 00 00 01 01  | ....... .......|\n"
40
41const writerWantILAEnd = "" +
42	"00000000  72 c3 63 00 52 72 72 53  73 41 61 61 42 62 62 62  |r.c.RrrSsAaaBbbb|\n" +
43	"00000010  43 63 63 63 63 63 63 63  63 63 31 32 72 c3 63 05  |Cccccccccc12r.c.|\n" +
44	"00000020  1d 45 00 ff 00 00 00 00  00 00 00 ff 00 00 00 00  |.E..............|\n" +
45	"00000030  00 00 00 ff 11 00 00 00  00 00 00 ff 33 00 00 00  |............3...|\n" +
46	"00000040  00 00 00 01 77 00 00 00  00 00 00 ee 04 00 00 00  |....w...........|\n" +
47	"00000050  00 00 01 ff 07 00 00 00  00 00 01 ff 09 00 00 00  |................|\n" +
48	"00000060  00 00 01 ff 0c 00 00 00  00 00 01 00 10 00 00 00  |................|\n" +
49	"00000070  00 00 01 00 7c 00 00 00  00 00 01 05              |....|.......|\n"
50
51const writerWantILAEndCPageSize8 = "" +
52	"00000000  72 c3 63 00 52 72 72 00  53 73 41 61 61 00 00 00  |r.c.Rrr.SsAaa...|\n" +
53	"00000010  42 62 62 62 43 63 63 63  63 63 63 63 63 63 31 32  |BbbbCccccccccc12|\n" +
54	"00000020  72 c3 63 05 90 5e 00 ff  00 00 00 00 00 00 00 ff  |r.c..^..........|\n" +
55	"00000030  00 00 00 00 00 00 00 ff  11 00 00 00 00 00 00 ff  |................|\n" +
56	"00000040  33 00 00 00 00 00 00 01  77 00 00 00 00 00 00 ee  |3.......w.......|\n" +
57	"00000050  04 00 00 00 00 00 01 ff  08 00 00 00 00 00 01 ff  |................|\n" +
58	"00000060  0a 00 00 00 00 00 01 ff  10 00 00 00 00 00 01 00  |................|\n" +
59	"00000070  14 00 00 00 00 00 01 00  80 00 00 00 00 00 01 05  |................|\n"
60
61const writerWantILAStart = "" +
62	"00000000  72 c3 63 05 bc dc 00 ff  00 00 00 00 00 00 00 ff  |r.c.............|\n" +
63	"00000010  00 00 00 00 00 00 00 ff  11 00 00 00 00 00 00 ff  |................|\n" +
64	"00000020  33 00 00 00 00 00 00 01  77 00 00 00 00 00 00 ee  |3.......w.......|\n" +
65	"00000030  60 00 00 00 00 00 01 ff  63 00 00 00 00 00 01 ff  |`.......c.......|\n" +
66	"00000040  65 00 00 00 00 00 01 ff  68 00 00 00 00 00 01 00  |e.......h.......|\n" +
67	"00000050  6c 00 00 00 00 00 01 00  78 00 00 00 00 00 01 05  |l.......x.......|\n" +
68	"00000060  52 72 72 53 73 41 61 61  42 62 62 62 43 63 63 63  |RrrSsAaaBbbbCccc|\n" +
69	"00000070  63 63 63 63 63 63 31 32                           |cccccc12|\n"
70
71const writerWantILAStartCPageSize4 = "" +
72	"00000000  72 c3 63 05 fc 4c 00 ff  00 00 00 00 00 00 00 ff  |r.c..L..........|\n" +
73	"00000010  00 00 00 00 00 00 00 ff  11 00 00 00 00 00 00 ff  |................|\n" +
74	"00000020  33 00 00 00 00 00 00 01  77 00 00 00 00 00 00 ee  |3.......w.......|\n" +
75	"00000030  60 00 00 00 00 00 01 ff  64 00 00 00 00 00 01 ff  |`.......d.......|\n" +
76	"00000040  68 00 00 00 00 00 01 ff  6c 00 00 00 00 00 01 00  |h.......l.......|\n" +
77	"00000050  70 00 00 00 00 00 01 00  7c 00 00 00 00 00 01 05  |p.......|.......|\n" +
78	"00000060  52 72 72 00 53 73 00 00  41 61 61 00 42 62 62 62  |Rrr.Ss..Aaa.Bbbb|\n" +
79	"00000070  43 63 63 63 63 63 63 63  63 63 31 32              |Cccccccccc12|\n"
80
81const writerWantILAStartCPageSize128 = "" +
82	"00000000  72 c3 63 05 d8 df 00 ff  00 00 00 00 00 00 00 ff  |r.c.............|\n" +
83	"00000010  00 00 00 00 00 00 00 ff  11 00 00 00 00 00 00 ff  |................|\n" +
84	"00000020  33 00 00 00 00 00 00 01  77 00 00 00 00 00 00 ee  |3.......w.......|\n" +
85	"00000030  80 00 00 00 00 00 01 ff  83 00 00 00 00 00 01 ff  |................|\n" +
86	"00000040  85 00 00 00 00 00 01 ff  88 00 00 00 00 00 01 00  |................|\n" +
87	"00000050  8c 00 00 00 00 00 01 00  98 00 00 00 00 00 01 05  |................|\n" +
88	"00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n" +
89	"00000070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|\n" +
90	"00000080  52 72 72 53 73 41 61 61  42 62 62 62 43 63 63 63  |RrrSsAaaBbbbCccc|\n" +
91	"00000090  63 63 63 63 63 63 31 32                           |cccccc12|\n"
92
93func TestWriterILAEndEmpty(t *testing.T) {
94	if err := testWriter(IndexLocationAtEnd, nil, 0, true); err != nil {
95		t.Fatal(err)
96	}
97}
98
99func TestWriterILAStartEmpty(t *testing.T) {
100	tempFile := &bytes.Buffer{}
101	if err := testWriter(IndexLocationAtStart, tempFile, 0, true); err != nil {
102		t.Fatal(err)
103	}
104}
105
106func TestWriterILAEndNoTempFile(t *testing.T) {
107	if err := testWriter(IndexLocationAtEnd, nil, 0, false); err != nil {
108		t.Fatal(err)
109	}
110}
111
112func TestWriterILAEndMemTempFile(t *testing.T) {
113	tempFile := &bytes.Buffer{}
114	if err := testWriter(IndexLocationAtEnd, tempFile, 0, false); err == nil {
115		t.Fatal("err: got nil, want non-nil")
116	} else if !strings.HasPrefix(err.Error(), "rac: IndexLocationAtEnd requires") {
117		t.Fatal(err)
118	}
119}
120
121func TestWriterILAStartNoTempFile(t *testing.T) {
122	if err := testWriter(IndexLocationAtStart, nil, 0, false); err == nil {
123		t.Fatal("err: got nil, want non-nil")
124	} else if !strings.HasPrefix(err.Error(), "rac: IndexLocationAtStart requires") {
125		t.Fatal(err)
126	}
127}
128
129func TestWriterILAStartMemTempFile(t *testing.T) {
130	tempFile := &bytes.Buffer{}
131	if err := testWriter(IndexLocationAtStart, tempFile, 0, false); err != nil {
132		t.Fatal(err)
133	}
134}
135
136func TestWriterILAStartRealTempFile(t *testing.T) {
137	f, err := ioutil.TempFile("", "rac_test")
138	if err != nil {
139		t.Fatalf("TempFile: %v", err)
140	}
141	defer os.Remove(f.Name())
142	defer f.Close()
143
144	if err := testWriter(IndexLocationAtStart, f, 0, false); err != nil {
145		t.Fatal(err)
146	}
147}
148
149func TestWriterILAEndCPageSize8(t *testing.T) {
150	if err := testWriter(IndexLocationAtEnd, nil, 8, false); err != nil {
151		t.Fatal(err)
152	}
153}
154
155func TestWriterILAStartCPageSize4(t *testing.T) {
156	tempFile := &bytes.Buffer{}
157	if err := testWriter(IndexLocationAtStart, tempFile, 4, false); err != nil {
158		t.Fatal(err)
159	}
160}
161
162func TestWriterILAStartCPageSize128(t *testing.T) {
163	tempFile := &bytes.Buffer{}
164	if err := testWriter(IndexLocationAtStart, tempFile, 128, false); err != nil {
165		t.Fatal(err)
166	}
167}
168
169func testWriter(iloc IndexLocation, tempFile io.ReadWriter, cPageSize uint64, empty bool) error {
170	buf := &bytes.Buffer{}
171	w := &Writer{
172		Writer:        buf,
173		Codec:         fakeCodec,
174		IndexLocation: iloc,
175		TempFile:      tempFile,
176		CPageSize:     cPageSize,
177	}
178
179	if !empty {
180		// We ignore errors (assigning them to _) from the AddXxx calls. Any
181		// non-nil errors are sticky, and should be returned by Close.
182		res0, _ := w.AddResource([]byte("Rrr"))
183		res1, _ := w.AddResource([]byte("Ss"))
184		_ = w.AddChunk(0x11, []byte("Aaa"), 0, 0)
185		_ = w.AddChunk(0x22, []byte("Bbbb"), res0, 0)
186		_ = w.AddChunk(0x44, []byte("Cccccccccc12"), res0, res1)
187	}
188
189	if err := w.Close(); err != nil {
190		return err
191	}
192	got := hex.Dump(buf.Bytes())
193
194	want := ""
195	switch {
196	case empty:
197		want = writerWantEmpty
198	case (iloc == IndexLocationAtEnd) && (cPageSize == 0):
199		want = writerWantILAEnd
200	case (iloc == IndexLocationAtEnd) && (cPageSize == 8):
201		want = writerWantILAEndCPageSize8
202	case (iloc == IndexLocationAtStart) && (cPageSize == 0):
203		want = writerWantILAStart
204	case (iloc == IndexLocationAtStart) && (cPageSize == 4):
205		want = writerWantILAStartCPageSize4
206	case (iloc == IndexLocationAtStart) && (cPageSize == 128):
207		want = writerWantILAStartCPageSize128
208	default:
209		return fmt.Errorf("unsupported iloc/cPageSize combination")
210	}
211
212	if got != want {
213		return fmt.Errorf("\ngot:\n%s\nwant:\n%s", got, want)
214	}
215	return nil
216}
217
218func TestWriterMultiLevelIndex(t *testing.T) {
219	buf := &bytes.Buffer{}
220	w := &Writer{
221		Writer:        buf,
222		Codec:         fakeCodec,
223		IndexLocation: IndexLocationAtStart,
224		TempFile:      &bytes.Buffer{},
225	}
226
227	// Write 260 chunks with 3 resources. With the current "func gather"
228	// algorithm, this results in a root node with two children, both of which
229	// are branch nodes. The first branch contains 252 chunks and refers to 3
230	// resources (so that its arity is 255). The second branch contains 8
231	// chunks and refers to 1 resource (so that its arity is 9).
232	xRes := OptResource(0)
233	yRes := OptResource(0)
234	zRes := OptResource(0)
235	for i := 0; i < 260; i++ {
236		secondary := OptResource(0)
237		tertiary := OptResource(0)
238
239		switch i {
240		case 3:
241			xRes, _ = w.AddResource([]byte("XX"))
242			yRes, _ = w.AddResource([]byte("YY"))
243			secondary = xRes
244			tertiary = yRes
245
246		case 4:
247			zRes, _ = w.AddResource([]byte("ZZ"))
248			secondary = yRes
249			tertiary = zRes
250
251		case 259:
252			secondary = yRes
253		}
254
255		primary := []byte(fmt.Sprintf("p%02x", i&0xFF))
256		_ = w.AddChunk(0x10000, primary, secondary, tertiary)
257	}
258
259	if err := w.Close(); err != nil {
260		t.Fatalf("Close: %v", err)
261	}
262
263	encoded := buf.Bytes()
264	if got, want := len(encoded), 0x13E2; got != want {
265		t.Fatalf("len(encoded): got 0x%X, want 0x%X", got, want)
266	}
267
268	got := hex.Dump(encoded)
269	got = "" +
270		got[0x000*bytesPerHexDumpLine:0x008*bytesPerHexDumpLine] +
271		"...\n" +
272		got[0x080*bytesPerHexDumpLine:0x088*bytesPerHexDumpLine] +
273		"...\n" +
274		got[0x100*bytesPerHexDumpLine:0x110*bytesPerHexDumpLine] +
275		"...\n" +
276		got[0x13C*bytesPerHexDumpLine:]
277
278	const want = "" +
279		"00000000  72 c3 63 02 a0 24 00 ff  00 00 fc 00 00 00 00 ff  |r.c..$..........|\n" +
280		"00000010  00 00 04 01 00 00 00 ee  30 00 00 00 00 00 04 ff  |........0.......|\n" +
281		"00000020  30 10 00 00 00 00 01 ff  e2 13 00 00 00 00 01 02  |0...............|\n" +
282		"00000030  72 c3 63 ff 4c be 00 ff  00 00 00 00 00 00 00 ff  |r.c.L...........|\n" +
283		"00000040  00 00 00 00 00 00 00 ff  00 00 00 00 00 00 00 ff  |................|\n" +
284		"00000050  00 00 01 00 00 00 00 ff  00 00 02 00 00 00 00 ff  |................|\n" +
285		"00000060  00 00 03 00 00 00 00 01  00 00 04 00 00 00 00 02  |................|\n" +
286		"00000070  00 00 05 00 00 00 00 ff  00 00 06 00 00 00 00 ff  |................|\n" +
287		"...\n" +
288		"00000800  00 00 f7 00 00 00 00 ff  00 00 f8 00 00 00 00 ff  |................|\n" +
289		"00000810  00 00 f9 00 00 00 00 ff  00 00 fa 00 00 00 00 ff  |................|\n" +
290		"00000820  00 00 fb 00 00 00 00 ff  00 00 fc 00 00 00 00 ee  |................|\n" +
291		"00000830  d9 10 00 00 00 00 01 ff  db 10 00 00 00 00 01 ff  |................|\n" +
292		"00000840  e0 10 00 00 00 00 01 ff  d0 10 00 00 00 00 01 ff  |................|\n" +
293		"00000850  d3 10 00 00 00 00 01 ff  d6 10 00 00 00 00 01 ff  |................|\n" +
294		"00000860  dd 10 00 00 00 00 01 00  e2 10 00 00 00 00 01 01  |................|\n" +
295		"00000870  e5 10 00 00 00 00 01 ff  e8 10 00 00 00 00 01 ff  |................|\n" +
296		"...\n" +
297		"00001000  bb 13 00 00 00 00 01 ff  be 13 00 00 00 00 01 ff  |................|\n" +
298		"00001010  c1 13 00 00 00 00 01 ff  c4 13 00 00 00 00 01 ff  |................|\n" +
299		"00001020  c7 13 00 00 00 00 01 ff  e2 13 00 00 00 00 01 ff  |................|\n" +
300		"00001030  72 c3 63 09 f0 09 00 ff  00 00 00 00 00 00 00 ff  |r.c.............|\n" +
301		"00001040  00 00 01 00 00 00 00 ff  00 00 02 00 00 00 00 ff  |................|\n" +
302		"00001050  00 00 03 00 00 00 00 ff  00 00 04 00 00 00 00 ff  |................|\n" +
303		"00001060  00 00 05 00 00 00 00 ff  00 00 06 00 00 00 00 ff  |................|\n" +
304		"00001070  00 00 07 00 00 00 00 ff  00 00 08 00 00 00 00 ee  |................|\n" +
305		"00001080  db 10 00 00 00 00 01 ff  ca 13 00 00 00 00 01 ff  |................|\n" +
306		"00001090  cd 13 00 00 00 00 01 ff  d0 13 00 00 00 00 01 ff  |................|\n" +
307		"000010a0  d3 13 00 00 00 00 01 ff  d6 13 00 00 00 00 01 ff  |................|\n" +
308		"000010b0  d9 13 00 00 00 00 01 ff  dc 13 00 00 00 00 01 ff  |................|\n" +
309		"000010c0  df 13 00 00 00 00 01 00  e2 13 00 00 00 00 01 09  |................|\n" +
310		"000010d0  70 30 30 70 30 31 70 30  32 58 58 59 59 70 30 33  |p00p01p02XXYYp03|\n" +
311		"000010e0  5a 5a 70 30 34 70 30 35  70 30 36 70 30 37 70 30  |ZZp04p05p06p07p0|\n" +
312		"000010f0  38 70 30 39 70 30 61 70  30 62 70 30 63 70 30 64  |8p09p0ap0bp0cp0d|\n" +
313		"...\n" +
314		"000013c0  38 70 66 39 70 66 61 70  66 62 70 66 63 70 66 64  |8pf9pfapfbpfcpfd|\n" +
315		"000013d0  70 66 65 70 66 66 70 30  30 70 30 31 70 30 32 70  |pfepffp00p01p02p|\n" +
316		"000013e0  30 33                                             |03|\n"
317
318	if got != want {
319		t.Fatalf("\ngot:\n%s\nwant:\n%s", got, want)
320	}
321}
322
323func TestWriter1000Chunks(t *testing.T) {
324	w := &Writer{
325		Writer: ioutil.Discard,
326		Codec:  fakeCodec,
327	}
328	data := make([]byte, 1)
329	res, _ := w.AddResource(data)
330	for i := 0; i < 1000; i++ {
331		if i == 2*255 {
332			_ = w.AddChunk(1, data, res, 0)
333		} else {
334			_ = w.AddChunk(1, data, 0, 0)
335		}
336	}
337	if err := w.Close(); err != nil {
338		t.Fatalf("Close: %v", err)
339	}
340}
341