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