github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/src/archive/tar/reader_test.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tar 6 7 import ( 8 "bytes" 9 "crypto/md5" 10 "errors" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "math" 15 "os" 16 "path" 17 "reflect" 18 "strconv" 19 "strings" 20 "testing" 21 "time" 22 ) 23 24 func TestReader(t *testing.T) { 25 vectors := []struct { 26 file string // Test input file 27 headers []*Header // Expected output headers 28 chksums []string // MD5 checksum of files, leave as nil if not checked 29 err error // Expected error to occur 30 }{{ 31 file: "testdata/gnu.tar", 32 headers: []*Header{{ 33 Name: "small.txt", 34 Mode: 0640, 35 Uid: 73025, 36 Gid: 5000, 37 Size: 5, 38 ModTime: time.Unix(1244428340, 0), 39 Typeflag: '0', 40 Uname: "dsymonds", 41 Gname: "eng", 42 Format: FormatGNU, 43 }, { 44 Name: "small2.txt", 45 Mode: 0640, 46 Uid: 73025, 47 Gid: 5000, 48 Size: 11, 49 ModTime: time.Unix(1244436044, 0), 50 Typeflag: '0', 51 Uname: "dsymonds", 52 Gname: "eng", 53 Format: FormatGNU, 54 }}, 55 chksums: []string{ 56 "e38b27eaccb4391bdec553a7f3ae6b2f", 57 "c65bd2e50a56a2138bf1716f2fd56fe9", 58 }, 59 }, { 60 file: "testdata/sparse-formats.tar", 61 headers: []*Header{{ 62 Name: "sparse-gnu", 63 Mode: 420, 64 Uid: 1000, 65 Gid: 1000, 66 Size: 200, 67 ModTime: time.Unix(1392395740, 0), 68 Typeflag: 0x53, 69 Linkname: "", 70 Uname: "david", 71 Gname: "david", 72 Devmajor: 0, 73 Devminor: 0, 74 SparseHoles: []SparseEntry{ 75 {0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}, {12, 1}, {14, 1}, 76 {16, 1}, {18, 1}, {20, 1}, {22, 1}, {24, 1}, {26, 1}, {28, 1}, 77 {30, 1}, {32, 1}, {34, 1}, {36, 1}, {38, 1}, {40, 1}, {42, 1}, 78 {44, 1}, {46, 1}, {48, 1}, {50, 1}, {52, 1}, {54, 1}, {56, 1}, 79 {58, 1}, {60, 1}, {62, 1}, {64, 1}, {66, 1}, {68, 1}, {70, 1}, 80 {72, 1}, {74, 1}, {76, 1}, {78, 1}, {80, 1}, {82, 1}, {84, 1}, 81 {86, 1}, {88, 1}, {90, 1}, {92, 1}, {94, 1}, {96, 1}, {98, 1}, 82 {100, 1}, {102, 1}, {104, 1}, {106, 1}, {108, 1}, {110, 1}, 83 {112, 1}, {114, 1}, {116, 1}, {118, 1}, {120, 1}, {122, 1}, 84 {124, 1}, {126, 1}, {128, 1}, {130, 1}, {132, 1}, {134, 1}, 85 {136, 1}, {138, 1}, {140, 1}, {142, 1}, {144, 1}, {146, 1}, 86 {148, 1}, {150, 1}, {152, 1}, {154, 1}, {156, 1}, {158, 1}, 87 {160, 1}, {162, 1}, {164, 1}, {166, 1}, {168, 1}, {170, 1}, 88 {172, 1}, {174, 1}, {176, 1}, {178, 1}, {180, 1}, {182, 1}, 89 {184, 1}, {186, 1}, {188, 1}, {190, 10}, 90 }, 91 Format: FormatGNU, 92 }, { 93 Name: "sparse-posix-0.0", 94 Mode: 420, 95 Uid: 1000, 96 Gid: 1000, 97 Size: 200, 98 ModTime: time.Unix(1392342187, 0), 99 Typeflag: 0x30, 100 Linkname: "", 101 Uname: "david", 102 Gname: "david", 103 Devmajor: 0, 104 Devminor: 0, 105 SparseHoles: []SparseEntry{ 106 {0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}, {12, 1}, {14, 1}, 107 {16, 1}, {18, 1}, {20, 1}, {22, 1}, {24, 1}, {26, 1}, {28, 1}, 108 {30, 1}, {32, 1}, {34, 1}, {36, 1}, {38, 1}, {40, 1}, {42, 1}, 109 {44, 1}, {46, 1}, {48, 1}, {50, 1}, {52, 1}, {54, 1}, {56, 1}, 110 {58, 1}, {60, 1}, {62, 1}, {64, 1}, {66, 1}, {68, 1}, {70, 1}, 111 {72, 1}, {74, 1}, {76, 1}, {78, 1}, {80, 1}, {82, 1}, {84, 1}, 112 {86, 1}, {88, 1}, {90, 1}, {92, 1}, {94, 1}, {96, 1}, {98, 1}, 113 {100, 1}, {102, 1}, {104, 1}, {106, 1}, {108, 1}, {110, 1}, 114 {112, 1}, {114, 1}, {116, 1}, {118, 1}, {120, 1}, {122, 1}, 115 {124, 1}, {126, 1}, {128, 1}, {130, 1}, {132, 1}, {134, 1}, 116 {136, 1}, {138, 1}, {140, 1}, {142, 1}, {144, 1}, {146, 1}, 117 {148, 1}, {150, 1}, {152, 1}, {154, 1}, {156, 1}, {158, 1}, 118 {160, 1}, {162, 1}, {164, 1}, {166, 1}, {168, 1}, {170, 1}, 119 {172, 1}, {174, 1}, {176, 1}, {178, 1}, {180, 1}, {182, 1}, 120 {184, 1}, {186, 1}, {188, 1}, {190, 10}, 121 }, 122 PAXRecords: map[string]string{ 123 "GNU.sparse.size": "200", 124 "GNU.sparse.numblocks": "95", 125 "GNU.sparse.map": "1,1,3,1,5,1,7,1,9,1,11,1,13,1,15,1,17,1,19,1,21,1,23,1,25,1,27,1,29,1,31,1,33,1,35,1,37,1,39,1,41,1,43,1,45,1,47,1,49,1,51,1,53,1,55,1,57,1,59,1,61,1,63,1,65,1,67,1,69,1,71,1,73,1,75,1,77,1,79,1,81,1,83,1,85,1,87,1,89,1,91,1,93,1,95,1,97,1,99,1,101,1,103,1,105,1,107,1,109,1,111,1,113,1,115,1,117,1,119,1,121,1,123,1,125,1,127,1,129,1,131,1,133,1,135,1,137,1,139,1,141,1,143,1,145,1,147,1,149,1,151,1,153,1,155,1,157,1,159,1,161,1,163,1,165,1,167,1,169,1,171,1,173,1,175,1,177,1,179,1,181,1,183,1,185,1,187,1,189,1", 126 }, 127 Format: FormatPAX, 128 }, { 129 Name: "sparse-posix-0.1", 130 Mode: 420, 131 Uid: 1000, 132 Gid: 1000, 133 Size: 200, 134 ModTime: time.Unix(1392340456, 0), 135 Typeflag: 0x30, 136 Linkname: "", 137 Uname: "david", 138 Gname: "david", 139 Devmajor: 0, 140 Devminor: 0, 141 SparseHoles: []SparseEntry{ 142 {0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}, {12, 1}, {14, 1}, 143 {16, 1}, {18, 1}, {20, 1}, {22, 1}, {24, 1}, {26, 1}, {28, 1}, 144 {30, 1}, {32, 1}, {34, 1}, {36, 1}, {38, 1}, {40, 1}, {42, 1}, 145 {44, 1}, {46, 1}, {48, 1}, {50, 1}, {52, 1}, {54, 1}, {56, 1}, 146 {58, 1}, {60, 1}, {62, 1}, {64, 1}, {66, 1}, {68, 1}, {70, 1}, 147 {72, 1}, {74, 1}, {76, 1}, {78, 1}, {80, 1}, {82, 1}, {84, 1}, 148 {86, 1}, {88, 1}, {90, 1}, {92, 1}, {94, 1}, {96, 1}, {98, 1}, 149 {100, 1}, {102, 1}, {104, 1}, {106, 1}, {108, 1}, {110, 1}, 150 {112, 1}, {114, 1}, {116, 1}, {118, 1}, {120, 1}, {122, 1}, 151 {124, 1}, {126, 1}, {128, 1}, {130, 1}, {132, 1}, {134, 1}, 152 {136, 1}, {138, 1}, {140, 1}, {142, 1}, {144, 1}, {146, 1}, 153 {148, 1}, {150, 1}, {152, 1}, {154, 1}, {156, 1}, {158, 1}, 154 {160, 1}, {162, 1}, {164, 1}, {166, 1}, {168, 1}, {170, 1}, 155 {172, 1}, {174, 1}, {176, 1}, {178, 1}, {180, 1}, {182, 1}, 156 {184, 1}, {186, 1}, {188, 1}, {190, 10}, 157 }, 158 PAXRecords: map[string]string{ 159 "GNU.sparse.size": "200", 160 "GNU.sparse.numblocks": "95", 161 "GNU.sparse.map": "1,1,3,1,5,1,7,1,9,1,11,1,13,1,15,1,17,1,19,1,21,1,23,1,25,1,27,1,29,1,31,1,33,1,35,1,37,1,39,1,41,1,43,1,45,1,47,1,49,1,51,1,53,1,55,1,57,1,59,1,61,1,63,1,65,1,67,1,69,1,71,1,73,1,75,1,77,1,79,1,81,1,83,1,85,1,87,1,89,1,91,1,93,1,95,1,97,1,99,1,101,1,103,1,105,1,107,1,109,1,111,1,113,1,115,1,117,1,119,1,121,1,123,1,125,1,127,1,129,1,131,1,133,1,135,1,137,1,139,1,141,1,143,1,145,1,147,1,149,1,151,1,153,1,155,1,157,1,159,1,161,1,163,1,165,1,167,1,169,1,171,1,173,1,175,1,177,1,179,1,181,1,183,1,185,1,187,1,189,1", 162 "GNU.sparse.name": "sparse-posix-0.1", 163 }, 164 Format: FormatPAX, 165 }, { 166 Name: "sparse-posix-1.0", 167 Mode: 420, 168 Uid: 1000, 169 Gid: 1000, 170 Size: 200, 171 ModTime: time.Unix(1392337404, 0), 172 Typeflag: 0x30, 173 Linkname: "", 174 Uname: "david", 175 Gname: "david", 176 Devmajor: 0, 177 Devminor: 0, 178 SparseHoles: []SparseEntry{ 179 {0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}, {12, 1}, {14, 1}, 180 {16, 1}, {18, 1}, {20, 1}, {22, 1}, {24, 1}, {26, 1}, {28, 1}, 181 {30, 1}, {32, 1}, {34, 1}, {36, 1}, {38, 1}, {40, 1}, {42, 1}, 182 {44, 1}, {46, 1}, {48, 1}, {50, 1}, {52, 1}, {54, 1}, {56, 1}, 183 {58, 1}, {60, 1}, {62, 1}, {64, 1}, {66, 1}, {68, 1}, {70, 1}, 184 {72, 1}, {74, 1}, {76, 1}, {78, 1}, {80, 1}, {82, 1}, {84, 1}, 185 {86, 1}, {88, 1}, {90, 1}, {92, 1}, {94, 1}, {96, 1}, {98, 1}, 186 {100, 1}, {102, 1}, {104, 1}, {106, 1}, {108, 1}, {110, 1}, 187 {112, 1}, {114, 1}, {116, 1}, {118, 1}, {120, 1}, {122, 1}, 188 {124, 1}, {126, 1}, {128, 1}, {130, 1}, {132, 1}, {134, 1}, 189 {136, 1}, {138, 1}, {140, 1}, {142, 1}, {144, 1}, {146, 1}, 190 {148, 1}, {150, 1}, {152, 1}, {154, 1}, {156, 1}, {158, 1}, 191 {160, 1}, {162, 1}, {164, 1}, {166, 1}, {168, 1}, {170, 1}, 192 {172, 1}, {174, 1}, {176, 1}, {178, 1}, {180, 1}, {182, 1}, 193 {184, 1}, {186, 1}, {188, 1}, {190, 10}, 194 }, 195 PAXRecords: map[string]string{ 196 "GNU.sparse.major": "1", 197 "GNU.sparse.minor": "0", 198 "GNU.sparse.realsize": "200", 199 "GNU.sparse.name": "sparse-posix-1.0", 200 }, 201 Format: FormatPAX, 202 }, { 203 Name: "end", 204 Mode: 420, 205 Uid: 1000, 206 Gid: 1000, 207 Size: 4, 208 ModTime: time.Unix(1392398319, 0), 209 Typeflag: 0x30, 210 Linkname: "", 211 Uname: "david", 212 Gname: "david", 213 Devmajor: 0, 214 Devminor: 0, 215 Format: FormatGNU, 216 }}, 217 chksums: []string{ 218 "6f53234398c2449fe67c1812d993012f", 219 "6f53234398c2449fe67c1812d993012f", 220 "6f53234398c2449fe67c1812d993012f", 221 "6f53234398c2449fe67c1812d993012f", 222 "b0061974914468de549a2af8ced10316", 223 }, 224 }, { 225 file: "testdata/star.tar", 226 headers: []*Header{{ 227 Name: "small.txt", 228 Mode: 0640, 229 Uid: 73025, 230 Gid: 5000, 231 Size: 5, 232 ModTime: time.Unix(1244592783, 0), 233 Typeflag: '0', 234 Uname: "dsymonds", 235 Gname: "eng", 236 AccessTime: time.Unix(1244592783, 0), 237 ChangeTime: time.Unix(1244592783, 0), 238 }, { 239 Name: "small2.txt", 240 Mode: 0640, 241 Uid: 73025, 242 Gid: 5000, 243 Size: 11, 244 ModTime: time.Unix(1244592783, 0), 245 Typeflag: '0', 246 Uname: "dsymonds", 247 Gname: "eng", 248 AccessTime: time.Unix(1244592783, 0), 249 ChangeTime: time.Unix(1244592783, 0), 250 }}, 251 }, { 252 file: "testdata/v7.tar", 253 headers: []*Header{{ 254 Name: "small.txt", 255 Mode: 0444, 256 Uid: 73025, 257 Gid: 5000, 258 Size: 5, 259 ModTime: time.Unix(1244593104, 0), 260 Typeflag: '\x00', 261 }, { 262 Name: "small2.txt", 263 Mode: 0444, 264 Uid: 73025, 265 Gid: 5000, 266 Size: 11, 267 ModTime: time.Unix(1244593104, 0), 268 Typeflag: '\x00', 269 }}, 270 }, { 271 file: "testdata/pax.tar", 272 headers: []*Header{{ 273 Name: "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100", 274 Mode: 0664, 275 Uid: 1000, 276 Gid: 1000, 277 Uname: "shane", 278 Gname: "shane", 279 Size: 7, 280 ModTime: time.Unix(1350244992, 23960108), 281 ChangeTime: time.Unix(1350244992, 23960108), 282 AccessTime: time.Unix(1350244992, 23960108), 283 Typeflag: TypeReg, 284 PAXRecords: map[string]string{ 285 "path": "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100", 286 "mtime": "1350244992.023960108", 287 "atime": "1350244992.023960108", 288 "ctime": "1350244992.023960108", 289 }, 290 Format: FormatPAX, 291 }, { 292 Name: "a/b", 293 Mode: 0777, 294 Uid: 1000, 295 Gid: 1000, 296 Uname: "shane", 297 Gname: "shane", 298 Size: 0, 299 ModTime: time.Unix(1350266320, 910238425), 300 ChangeTime: time.Unix(1350266320, 910238425), 301 AccessTime: time.Unix(1350266320, 910238425), 302 Typeflag: TypeSymlink, 303 Linkname: "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100", 304 PAXRecords: map[string]string{ 305 "linkpath": "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100", 306 "mtime": "1350266320.910238425", 307 "atime": "1350266320.910238425", 308 "ctime": "1350266320.910238425", 309 }, 310 Format: FormatPAX, 311 }}, 312 }, { 313 file: "testdata/pax-bad-hdr-file.tar", 314 err: ErrHeader, 315 }, { 316 file: "testdata/pax-bad-mtime-file.tar", 317 err: ErrHeader, 318 }, { 319 file: "testdata/pax-pos-size-file.tar", 320 headers: []*Header{{ 321 Name: "foo", 322 Mode: 0640, 323 Uid: 319973, 324 Gid: 5000, 325 Size: 999, 326 ModTime: time.Unix(1442282516, 0), 327 Typeflag: '0', 328 Uname: "joetsai", 329 Gname: "eng", 330 PAXRecords: map[string]string{ 331 "size": "000000000000000000000999", 332 }, 333 Format: FormatPAX, 334 }}, 335 chksums: []string{ 336 "0afb597b283fe61b5d4879669a350556", 337 }, 338 }, { 339 file: "testdata/pax-records.tar", 340 headers: []*Header{{ 341 Typeflag: TypeReg, 342 Name: "file", 343 Uname: strings.Repeat("long", 10), 344 ModTime: time.Unix(0, 0), 345 PAXRecords: map[string]string{ 346 "GOLANG.pkg": "tar", 347 "comment": "Hello, 世界", 348 "uname": strings.Repeat("long", 10), 349 }, 350 Format: FormatPAX, 351 }}, 352 }, { 353 file: "testdata/pax-global-records.tar", 354 headers: []*Header{{ 355 Typeflag: TypeXGlobalHeader, 356 PAXRecords: map[string]string{"path": "global1", "mtime": "1500000000.0"}, 357 Format: FormatPAX, 358 }, { 359 Typeflag: TypeReg, 360 Name: "file1", 361 ModTime: time.Unix(0, 0), 362 Format: FormatUSTAR, 363 }, { 364 Typeflag: TypeReg, 365 Name: "file2", 366 PAXRecords: map[string]string{"path": "file2"}, 367 ModTime: time.Unix(0, 0), 368 Format: FormatPAX, 369 }, { 370 Typeflag: TypeXGlobalHeader, 371 PAXRecords: map[string]string{"path": ""}, 372 Format: FormatPAX, 373 }, { 374 Typeflag: TypeReg, 375 Name: "file3", 376 ModTime: time.Unix(0, 0), 377 Format: FormatUSTAR, 378 }, { 379 Typeflag: TypeReg, 380 Name: "file4", 381 ModTime: time.Unix(1400000000, 0), 382 PAXRecords: map[string]string{"mtime": "1400000000"}, 383 Format: FormatPAX, 384 }}, 385 }, { 386 file: "testdata/nil-uid.tar", // golang.org/issue/5290 387 headers: []*Header{{ 388 Name: "P1050238.JPG.log", 389 Mode: 0664, 390 Uid: 0, 391 Gid: 0, 392 Size: 14, 393 ModTime: time.Unix(1365454838, 0), 394 Typeflag: TypeReg, 395 Linkname: "", 396 Uname: "eyefi", 397 Gname: "eyefi", 398 Devmajor: 0, 399 Devminor: 0, 400 Format: FormatGNU, 401 }}, 402 }, { 403 file: "testdata/xattrs.tar", 404 headers: []*Header{{ 405 Name: "small.txt", 406 Mode: 0644, 407 Uid: 1000, 408 Gid: 10, 409 Size: 5, 410 ModTime: time.Unix(1386065770, 448252320), 411 Typeflag: '0', 412 Uname: "alex", 413 Gname: "wheel", 414 AccessTime: time.Unix(1389782991, 419875220), 415 ChangeTime: time.Unix(1389782956, 794414986), 416 Xattrs: map[string]string{ 417 "user.key": "value", 418 "user.key2": "value2", 419 // Interestingly, selinux encodes the terminating null inside the xattr 420 "security.selinux": "unconfined_u:object_r:default_t:s0\x00", 421 }, 422 PAXRecords: map[string]string{ 423 "mtime": "1386065770.44825232", 424 "atime": "1389782991.41987522", 425 "ctime": "1389782956.794414986", 426 "SCHILY.xattr.user.key": "value", 427 "SCHILY.xattr.user.key2": "value2", 428 "SCHILY.xattr.security.selinux": "unconfined_u:object_r:default_t:s0\x00", 429 }, 430 Format: FormatPAX, 431 }, { 432 Name: "small2.txt", 433 Mode: 0644, 434 Uid: 1000, 435 Gid: 10, 436 Size: 11, 437 ModTime: time.Unix(1386065770, 449252304), 438 Typeflag: '0', 439 Uname: "alex", 440 Gname: "wheel", 441 AccessTime: time.Unix(1389782991, 419875220), 442 ChangeTime: time.Unix(1386065770, 449252304), 443 Xattrs: map[string]string{ 444 "security.selinux": "unconfined_u:object_r:default_t:s0\x00", 445 }, 446 PAXRecords: map[string]string{ 447 "mtime": "1386065770.449252304", 448 "atime": "1389782991.41987522", 449 "ctime": "1386065770.449252304", 450 "SCHILY.xattr.security.selinux": "unconfined_u:object_r:default_t:s0\x00", 451 }, 452 Format: FormatPAX, 453 }}, 454 }, { 455 // Matches the behavior of GNU, BSD, and STAR tar utilities. 456 file: "testdata/gnu-multi-hdrs.tar", 457 headers: []*Header{{ 458 Name: "GNU2/GNU2/long-path-name", 459 Linkname: "GNU4/GNU4/long-linkpath-name", 460 ModTime: time.Unix(0, 0), 461 Typeflag: '2', 462 Format: FormatGNU, 463 }}, 464 }, { 465 // GNU tar file with atime and ctime fields set. 466 // Created with the GNU tar v1.27.1. 467 // tar --incremental -S -cvf gnu-incremental.tar test2 468 file: "testdata/gnu-incremental.tar", 469 headers: []*Header{{ 470 Name: "test2/", 471 Mode: 16877, 472 Uid: 1000, 473 Gid: 1000, 474 Size: 14, 475 ModTime: time.Unix(1441973427, 0), 476 Typeflag: 'D', 477 Uname: "rawr", 478 Gname: "dsnet", 479 AccessTime: time.Unix(1441974501, 0), 480 ChangeTime: time.Unix(1441973436, 0), 481 Format: FormatGNU, 482 }, { 483 Name: "test2/foo", 484 Mode: 33188, 485 Uid: 1000, 486 Gid: 1000, 487 Size: 64, 488 ModTime: time.Unix(1441973363, 0), 489 Typeflag: '0', 490 Uname: "rawr", 491 Gname: "dsnet", 492 AccessTime: time.Unix(1441974501, 0), 493 ChangeTime: time.Unix(1441973436, 0), 494 Format: FormatGNU, 495 }, { 496 Name: "test2/sparse", 497 Mode: 33188, 498 Uid: 1000, 499 Gid: 1000, 500 Size: 536870912, 501 ModTime: time.Unix(1441973427, 0), 502 Typeflag: 'S', 503 Uname: "rawr", 504 Gname: "dsnet", 505 AccessTime: time.Unix(1441991948, 0), 506 ChangeTime: time.Unix(1441973436, 0), 507 SparseHoles: []SparseEntry{{0, 536870912}}, 508 Format: FormatGNU, 509 }}, 510 }, { 511 // Matches the behavior of GNU and BSD tar utilities. 512 file: "testdata/pax-multi-hdrs.tar", 513 headers: []*Header{{ 514 Name: "bar", 515 Linkname: "PAX4/PAX4/long-linkpath-name", 516 ModTime: time.Unix(0, 0), 517 Typeflag: '2', 518 PAXRecords: map[string]string{ 519 "linkpath": "PAX4/PAX4/long-linkpath-name", 520 }, 521 Format: FormatPAX, 522 }}, 523 }, { 524 // Both BSD and GNU tar truncate long names at first NUL even 525 // if there is data following that NUL character. 526 // This is reasonable as GNU long names are C-strings. 527 file: "testdata/gnu-long-nul.tar", 528 headers: []*Header{{ 529 Name: "0123456789", 530 Mode: 0644, 531 Uid: 1000, 532 Gid: 1000, 533 ModTime: time.Unix(1486082191, 0), 534 Typeflag: '0', 535 Uname: "rawr", 536 Gname: "dsnet", 537 Format: FormatGNU, 538 }}, 539 }, { 540 // This archive was generated by Writer but is readable by both 541 // GNU and BSD tar utilities. 542 // The archive generated by GNU is nearly byte-for-byte identical 543 // to the Go version except the Go version sets a negative Devminor 544 // just to force the GNU format. 545 file: "testdata/gnu-utf8.tar", 546 headers: []*Header{{ 547 Name: "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹", 548 Mode: 0644, 549 Uid: 1000, Gid: 1000, 550 ModTime: time.Unix(0, 0), 551 Typeflag: '0', 552 Uname: "☺", 553 Gname: "⚹", 554 Format: FormatGNU, 555 }}, 556 }, { 557 // This archive was generated by Writer but is readable by both 558 // GNU and BSD tar utilities. 559 // The archive generated by GNU is nearly byte-for-byte identical 560 // to the Go version except the Go version sets a negative Devminor 561 // just to force the GNU format. 562 file: "testdata/gnu-not-utf8.tar", 563 headers: []*Header{{ 564 Name: "hi\x80\x81\x82\x83bye", 565 Mode: 0644, 566 Uid: 1000, 567 Gid: 1000, 568 ModTime: time.Unix(0, 0), 569 Typeflag: '0', 570 Uname: "rawr", 571 Gname: "dsnet", 572 Format: FormatGNU, 573 }}, 574 }, { 575 // BSD tar v3.1.2 and GNU tar v1.27.1 both rejects PAX records 576 // with NULs in the key. 577 file: "testdata/pax-nul-xattrs.tar", 578 err: ErrHeader, 579 }, { 580 // BSD tar v3.1.2 rejects a PAX path with NUL in the value, while 581 // GNU tar v1.27.1 simply truncates at first NUL. 582 // We emulate the behavior of BSD since it is strange doing NUL 583 // truncations since PAX records are length-prefix strings instead 584 // of NUL-terminated C-strings. 585 file: "testdata/pax-nul-path.tar", 586 err: ErrHeader, 587 }, { 588 file: "testdata/neg-size.tar", 589 err: ErrHeader, 590 }, { 591 file: "testdata/issue10968.tar", 592 err: ErrHeader, 593 }, { 594 file: "testdata/issue11169.tar", 595 err: ErrHeader, 596 }, { 597 file: "testdata/issue12435.tar", 598 err: ErrHeader, 599 }, { 600 // Ensure that we can read back the original Header as written with 601 // a buggy pre-Go1.8 tar.Writer. 602 file: "testdata/invalid-go17.tar", 603 headers: []*Header{{ 604 Name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/foo", 605 Uid: 010000000, 606 ModTime: time.Unix(0, 0), 607 }}, 608 }, { 609 // USTAR archive with a regular entry with non-zero device numbers. 610 file: "testdata/ustar-file-devs.tar", 611 headers: []*Header{{ 612 Name: "file", 613 Mode: 0644, 614 Typeflag: '0', 615 ModTime: time.Unix(0, 0), 616 Devmajor: 1, 617 Devminor: 1, 618 Format: FormatUSTAR, 619 }}, 620 }, { 621 // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1. 622 file: "testdata/gnu-nil-sparse-data.tar", 623 headers: []*Header{{ 624 Name: "sparse.db", 625 Typeflag: TypeGNUSparse, 626 Size: 1000, 627 ModTime: time.Unix(0, 0), 628 SparseHoles: []SparseEntry{{Offset: 1000, Length: 0}}, 629 Format: FormatGNU, 630 }}, 631 }, { 632 // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1. 633 file: "testdata/gnu-nil-sparse-hole.tar", 634 headers: []*Header{{ 635 Name: "sparse.db", 636 Typeflag: TypeGNUSparse, 637 Size: 1000, 638 ModTime: time.Unix(0, 0), 639 SparseHoles: []SparseEntry{{Offset: 0, Length: 1000}}, 640 Format: FormatGNU, 641 }}, 642 }, { 643 // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1. 644 file: "testdata/pax-nil-sparse-data.tar", 645 headers: []*Header{{ 646 Name: "sparse.db", 647 Typeflag: TypeReg, 648 Size: 1000, 649 ModTime: time.Unix(0, 0), 650 SparseHoles: []SparseEntry{{Offset: 1000, Length: 0}}, 651 PAXRecords: map[string]string{ 652 "size": "1512", 653 "GNU.sparse.major": "1", 654 "GNU.sparse.minor": "0", 655 "GNU.sparse.realsize": "1000", 656 "GNU.sparse.name": "sparse.db", 657 }, 658 Format: FormatPAX, 659 }}, 660 }, { 661 // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1. 662 file: "testdata/pax-nil-sparse-hole.tar", 663 headers: []*Header{{ 664 Name: "sparse.db", 665 Typeflag: TypeReg, 666 Size: 1000, 667 ModTime: time.Unix(0, 0), 668 SparseHoles: []SparseEntry{{Offset: 0, Length: 1000}}, 669 PAXRecords: map[string]string{ 670 "size": "512", 671 "GNU.sparse.major": "1", 672 "GNU.sparse.minor": "0", 673 "GNU.sparse.realsize": "1000", 674 "GNU.sparse.name": "sparse.db", 675 }, 676 Format: FormatPAX, 677 }}, 678 }, { 679 file: "testdata/trailing-slash.tar", 680 headers: []*Header{{ 681 Typeflag: TypeDir, 682 Name: strings.Repeat("123456789/", 30), 683 ModTime: time.Unix(0, 0), 684 PAXRecords: map[string]string{ 685 "path": strings.Repeat("123456789/", 30), 686 }, 687 Format: FormatPAX, 688 }}, 689 }} 690 691 for _, v := range vectors { 692 t.Run(path.Base(v.file), func(t *testing.T) { 693 f, err := os.Open(v.file) 694 if err != nil { 695 t.Fatalf("unexpected error: %v", err) 696 } 697 defer f.Close() 698 699 // Capture all headers and checksums. 700 var ( 701 tr = NewReader(f) 702 hdrs []*Header 703 chksums []string 704 rdbuf = make([]byte, 8) 705 ) 706 for { 707 var hdr *Header 708 hdr, err = tr.Next() 709 if err != nil { 710 if err == io.EOF { 711 err = nil // Expected error 712 } 713 break 714 } 715 hdrs = append(hdrs, hdr) 716 717 if v.chksums == nil { 718 continue 719 } 720 h := md5.New() 721 _, err = io.CopyBuffer(h, tr, rdbuf) // Effectively an incremental read 722 if err != nil { 723 break 724 } 725 chksums = append(chksums, fmt.Sprintf("%x", h.Sum(nil))) 726 } 727 728 for i, hdr := range hdrs { 729 if i >= len(v.headers) { 730 t.Fatalf("entry %d: unexpected header:\ngot %+v", i, *hdr) 731 continue 732 } 733 if !reflect.DeepEqual(*hdr, *v.headers[i]) { 734 t.Fatalf("entry %d: incorrect header:\ngot %+v\nwant %+v", i, *hdr, *v.headers[i]) 735 } 736 } 737 if len(hdrs) != len(v.headers) { 738 t.Fatalf("got %d headers, want %d headers", len(hdrs), len(v.headers)) 739 } 740 741 for i, sum := range chksums { 742 if i >= len(v.chksums) { 743 t.Fatalf("entry %d: unexpected sum: got %s", i, sum) 744 continue 745 } 746 if sum != v.chksums[i] { 747 t.Fatalf("entry %d: incorrect checksum: got %s, want %s", i, sum, v.chksums[i]) 748 } 749 } 750 751 if err != v.err { 752 t.Fatalf("unexpected error: got %v, want %v", err, v.err) 753 } 754 f.Close() 755 }) 756 } 757 } 758 759 func TestPartialRead(t *testing.T) { 760 type testCase struct { 761 cnt int // Number of bytes to read 762 output string // Expected value of string read 763 } 764 vectors := []struct { 765 file string 766 cases []testCase 767 }{{ 768 file: "testdata/gnu.tar", 769 cases: []testCase{ 770 {4, "Kilt"}, 771 {6, "Google"}, 772 }, 773 }, { 774 file: "testdata/sparse-formats.tar", 775 cases: []testCase{ 776 {2, "\x00G"}, 777 {4, "\x00G\x00o"}, 778 {6, "\x00G\x00o\x00G"}, 779 {8, "\x00G\x00o\x00G\x00o"}, 780 {4, "end\n"}, 781 }, 782 }} 783 784 for _, v := range vectors { 785 t.Run(path.Base(v.file), func(t *testing.T) { 786 f, err := os.Open(v.file) 787 if err != nil { 788 t.Fatalf("Open() error: %v", err) 789 } 790 defer f.Close() 791 792 tr := NewReader(f) 793 for i, tc := range v.cases { 794 hdr, err := tr.Next() 795 if err != nil || hdr == nil { 796 t.Fatalf("entry %d, Next(): got %v, want %v", i, err, nil) 797 } 798 buf := make([]byte, tc.cnt) 799 if _, err := io.ReadFull(tr, buf); err != nil { 800 t.Fatalf("entry %d, ReadFull(): got %v, want %v", i, err, nil) 801 } 802 if string(buf) != tc.output { 803 t.Fatalf("entry %d, ReadFull(): got %q, want %q", i, string(buf), tc.output) 804 } 805 } 806 807 if _, err := tr.Next(); err != io.EOF { 808 t.Fatalf("Next(): got %v, want EOF", err) 809 } 810 }) 811 } 812 } 813 814 func TestUninitializedRead(t *testing.T) { 815 f, err := os.Open("testdata/gnu.tar") 816 if err != nil { 817 t.Fatalf("Unexpected error: %v", err) 818 } 819 defer f.Close() 820 821 tr := NewReader(f) 822 _, err = tr.Read([]byte{}) 823 if err == nil || err != io.EOF { 824 t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF) 825 } 826 827 } 828 829 type reader struct{ io.Reader } 830 type readSeeker struct{ io.ReadSeeker } 831 type readBadSeeker struct{ io.ReadSeeker } 832 833 func (rbs *readBadSeeker) Seek(int64, int) (int64, error) { return 0, fmt.Errorf("illegal seek") } 834 835 // TestReadTruncation test the ending condition on various truncated files and 836 // that truncated files are still detected even if the underlying io.Reader 837 // satisfies io.Seeker. 838 func TestReadTruncation(t *testing.T) { 839 var ss []string 840 for _, p := range []string{ 841 "testdata/gnu.tar", 842 "testdata/ustar-file-reg.tar", 843 "testdata/pax-path-hdr.tar", 844 "testdata/sparse-formats.tar", 845 } { 846 buf, err := ioutil.ReadFile(p) 847 if err != nil { 848 t.Fatalf("unexpected error: %v", err) 849 } 850 ss = append(ss, string(buf)) 851 } 852 853 data1, data2, pax, sparse := ss[0], ss[1], ss[2], ss[3] 854 data2 += strings.Repeat("\x00", 10*512) 855 trash := strings.Repeat("garbage ", 64) // Exactly 512 bytes 856 857 vectors := []struct { 858 input string // Input stream 859 cnt int // Expected number of headers read 860 err error // Expected error outcome 861 }{ 862 {"", 0, io.EOF}, // Empty file is a "valid" tar file 863 {data1[:511], 0, io.ErrUnexpectedEOF}, 864 {data1[:512], 1, io.ErrUnexpectedEOF}, 865 {data1[:1024], 1, io.EOF}, 866 {data1[:1536], 2, io.ErrUnexpectedEOF}, 867 {data1[:2048], 2, io.EOF}, 868 {data1, 2, io.EOF}, 869 {data1[:2048] + data2[:1536], 3, io.EOF}, 870 {data2[:511], 0, io.ErrUnexpectedEOF}, 871 {data2[:512], 1, io.ErrUnexpectedEOF}, 872 {data2[:1195], 1, io.ErrUnexpectedEOF}, 873 {data2[:1196], 1, io.EOF}, // Exact end of data and start of padding 874 {data2[:1200], 1, io.EOF}, 875 {data2[:1535], 1, io.EOF}, 876 {data2[:1536], 1, io.EOF}, // Exact end of padding 877 {data2[:1536] + trash[:1], 1, io.ErrUnexpectedEOF}, 878 {data2[:1536] + trash[:511], 1, io.ErrUnexpectedEOF}, 879 {data2[:1536] + trash, 1, ErrHeader}, 880 {data2[:2048], 1, io.EOF}, // Exactly 1 empty block 881 {data2[:2048] + trash[:1], 1, io.ErrUnexpectedEOF}, 882 {data2[:2048] + trash[:511], 1, io.ErrUnexpectedEOF}, 883 {data2[:2048] + trash, 1, ErrHeader}, 884 {data2[:2560], 1, io.EOF}, // Exactly 2 empty blocks (normal end-of-stream) 885 {data2[:2560] + trash[:1], 1, io.EOF}, 886 {data2[:2560] + trash[:511], 1, io.EOF}, 887 {data2[:2560] + trash, 1, io.EOF}, 888 {data2[:3072], 1, io.EOF}, 889 {pax, 0, io.EOF}, // PAX header without data is a "valid" tar file 890 {pax + trash[:1], 0, io.ErrUnexpectedEOF}, 891 {pax + trash[:511], 0, io.ErrUnexpectedEOF}, 892 {sparse[:511], 0, io.ErrUnexpectedEOF}, 893 {sparse[:512], 0, io.ErrUnexpectedEOF}, 894 {sparse[:3584], 1, io.EOF}, 895 {sparse[:9200], 1, io.EOF}, // Terminate in padding of sparse header 896 {sparse[:9216], 1, io.EOF}, 897 {sparse[:9728], 2, io.ErrUnexpectedEOF}, 898 {sparse[:10240], 2, io.EOF}, 899 {sparse[:11264], 2, io.ErrUnexpectedEOF}, 900 {sparse, 5, io.EOF}, 901 {sparse + trash, 5, io.EOF}, 902 } 903 904 for i, v := range vectors { 905 for j := 0; j < 6; j++ { 906 var tr *Reader 907 var s1, s2 string 908 909 switch j { 910 case 0: 911 tr = NewReader(&reader{strings.NewReader(v.input)}) 912 s1, s2 = "io.Reader", "auto" 913 case 1: 914 tr = NewReader(&reader{strings.NewReader(v.input)}) 915 s1, s2 = "io.Reader", "manual" 916 case 2: 917 tr = NewReader(&readSeeker{strings.NewReader(v.input)}) 918 s1, s2 = "io.ReadSeeker", "auto" 919 case 3: 920 tr = NewReader(&readSeeker{strings.NewReader(v.input)}) 921 s1, s2 = "io.ReadSeeker", "manual" 922 case 4: 923 tr = NewReader(&readBadSeeker{strings.NewReader(v.input)}) 924 s1, s2 = "ReadBadSeeker", "auto" 925 case 5: 926 tr = NewReader(&readBadSeeker{strings.NewReader(v.input)}) 927 s1, s2 = "ReadBadSeeker", "manual" 928 } 929 930 var cnt int 931 var err error 932 for { 933 if _, err = tr.Next(); err != nil { 934 break 935 } 936 cnt++ 937 if s2 == "manual" { 938 if _, err = tr.WriteTo(ioutil.Discard); err != nil { 939 break 940 } 941 } 942 } 943 if err != v.err { 944 t.Errorf("test %d, NewReader(%s) with %s discard: got %v, want %v", 945 i, s1, s2, err, v.err) 946 } 947 if cnt != v.cnt { 948 t.Errorf("test %d, NewReader(%s) with %s discard: got %d headers, want %d headers", 949 i, s1, s2, cnt, v.cnt) 950 } 951 } 952 } 953 } 954 955 // TestReadHeaderOnly tests that Reader does not attempt to read special 956 // header-only files. 957 func TestReadHeaderOnly(t *testing.T) { 958 f, err := os.Open("testdata/hdr-only.tar") 959 if err != nil { 960 t.Fatalf("unexpected error: %v", err) 961 } 962 defer f.Close() 963 964 var hdrs []*Header 965 tr := NewReader(f) 966 for { 967 hdr, err := tr.Next() 968 if err == io.EOF { 969 break 970 } 971 if err != nil { 972 t.Errorf("Next(): got %v, want %v", err, nil) 973 continue 974 } 975 hdrs = append(hdrs, hdr) 976 977 // If a special flag, we should read nothing. 978 cnt, _ := io.ReadFull(tr, []byte{0}) 979 if cnt > 0 && hdr.Typeflag != TypeReg { 980 t.Errorf("ReadFull(...): got %d bytes, want 0 bytes", cnt) 981 } 982 } 983 984 // File is crafted with 16 entries. The later 8 are identical to the first 985 // 8 except that the size is set. 986 if len(hdrs) != 16 { 987 t.Fatalf("len(hdrs): got %d, want %d", len(hdrs), 16) 988 } 989 for i := 0; i < 8; i++ { 990 hdr1, hdr2 := hdrs[i+0], hdrs[i+8] 991 hdr1.Size, hdr2.Size = 0, 0 992 if !reflect.DeepEqual(*hdr1, *hdr2) { 993 t.Errorf("incorrect header:\ngot %+v\nwant %+v", *hdr1, *hdr2) 994 } 995 } 996 } 997 998 func TestMergePAX(t *testing.T) { 999 vectors := []struct { 1000 in map[string]string 1001 want *Header 1002 ok bool 1003 }{{ 1004 in: map[string]string{ 1005 "path": "a/b/c", 1006 "uid": "1000", 1007 "mtime": "1350244992.023960108", 1008 }, 1009 want: &Header{ 1010 Name: "a/b/c", 1011 Uid: 1000, 1012 ModTime: time.Unix(1350244992, 23960108), 1013 PAXRecords: map[string]string{ 1014 "path": "a/b/c", 1015 "uid": "1000", 1016 "mtime": "1350244992.023960108", 1017 }, 1018 }, 1019 ok: true, 1020 }, { 1021 in: map[string]string{ 1022 "gid": "gtgergergersagersgers", 1023 }, 1024 ok: false, 1025 }, { 1026 in: map[string]string{ 1027 "missing": "missing", 1028 "SCHILY.xattr.key": "value", 1029 }, 1030 want: &Header{ 1031 Xattrs: map[string]string{"key": "value"}, 1032 PAXRecords: map[string]string{ 1033 "missing": "missing", 1034 "SCHILY.xattr.key": "value", 1035 }, 1036 }, 1037 ok: true, 1038 }} 1039 1040 for i, v := range vectors { 1041 got := new(Header) 1042 err := mergePAX(got, v.in) 1043 if v.ok && !reflect.DeepEqual(*got, *v.want) { 1044 t.Errorf("test %d, mergePAX(...):\ngot %+v\nwant %+v", i, *got, *v.want) 1045 } 1046 if ok := err == nil; ok != v.ok { 1047 t.Errorf("test %d, mergePAX(...): got %v, want %v", i, ok, v.ok) 1048 } 1049 } 1050 } 1051 1052 func TestParsePAX(t *testing.T) { 1053 vectors := []struct { 1054 in string 1055 want map[string]string 1056 ok bool 1057 }{ 1058 {"", nil, true}, 1059 {"6 k=1\n", map[string]string{"k": "1"}, true}, 1060 {"10 a=name\n", map[string]string{"a": "name"}, true}, 1061 {"9 a=name\n", map[string]string{"a": "name"}, true}, 1062 {"30 mtime=1350244992.023960108\n", map[string]string{"mtime": "1350244992.023960108"}, true}, 1063 {"3 somelongkey=\n", nil, false}, 1064 {"50 tooshort=\n", nil, false}, 1065 {"13 key1=haha\n13 key2=nana\n13 key3=kaka\n", 1066 map[string]string{"key1": "haha", "key2": "nana", "key3": "kaka"}, true}, 1067 {"13 key1=val1\n13 key2=val2\n8 key1=\n", 1068 map[string]string{"key1": "", "key2": "val2"}, true}, 1069 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=2\n" + 1070 "23 GNU.sparse.offset=1\n25 GNU.sparse.numbytes=2\n" + 1071 "23 GNU.sparse.offset=3\n25 GNU.sparse.numbytes=4\n", 1072 map[string]string{paxGNUSparseSize: "10", paxGNUSparseNumBlocks: "2", paxGNUSparseMap: "1,2,3,4"}, true}, 1073 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" + 1074 "25 GNU.sparse.numbytes=2\n23 GNU.sparse.offset=1\n", 1075 nil, false}, 1076 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" + 1077 "25 GNU.sparse.offset=1,2\n25 GNU.sparse.numbytes=2\n", 1078 nil, false}, 1079 } 1080 1081 for i, v := range vectors { 1082 r := strings.NewReader(v.in) 1083 got, err := parsePAX(r) 1084 if !reflect.DeepEqual(got, v.want) && !(len(got) == 0 && len(v.want) == 0) { 1085 t.Errorf("test %d, parsePAX():\ngot %v\nwant %v", i, got, v.want) 1086 } 1087 if ok := err == nil; ok != v.ok { 1088 t.Errorf("test %d, parsePAX(): got %v, want %v", i, ok, v.ok) 1089 } 1090 } 1091 } 1092 1093 func TestReadOldGNUSparseMap(t *testing.T) { 1094 populateSparseMap := func(sa sparseArray, sps []string) []string { 1095 for i := 0; len(sps) > 0 && i < sa.MaxEntries(); i++ { 1096 copy(sa.Entry(i), sps[0]) 1097 sps = sps[1:] 1098 } 1099 if len(sps) > 0 { 1100 copy(sa.IsExtended(), "\x80") 1101 } 1102 return sps 1103 } 1104 1105 makeInput := func(format Format, size string, sps ...string) (out []byte) { 1106 // Write the initial GNU header. 1107 var blk block 1108 gnu := blk.GNU() 1109 sparse := gnu.Sparse() 1110 copy(gnu.RealSize(), size) 1111 sps = populateSparseMap(sparse, sps) 1112 if format != FormatUnknown { 1113 blk.SetFormat(format) 1114 } 1115 out = append(out, blk[:]...) 1116 1117 // Write extended sparse blocks. 1118 for len(sps) > 0 { 1119 var blk block 1120 sps = populateSparseMap(blk.Sparse(), sps) 1121 out = append(out, blk[:]...) 1122 } 1123 return out 1124 } 1125 1126 makeSparseStrings := func(sp []SparseEntry) (out []string) { 1127 var f formatter 1128 for _, s := range sp { 1129 var b [24]byte 1130 f.formatNumeric(b[:12], s.Offset) 1131 f.formatNumeric(b[12:], s.Length) 1132 out = append(out, string(b[:])) 1133 } 1134 return out 1135 } 1136 1137 vectors := []struct { 1138 input []byte 1139 wantMap sparseDatas 1140 wantSize int64 1141 wantErr error 1142 }{{ 1143 input: makeInput(FormatUnknown, ""), 1144 wantErr: ErrHeader, 1145 }, { 1146 input: makeInput(FormatGNU, "1234", "fewa"), 1147 wantSize: 01234, 1148 wantErr: ErrHeader, 1149 }, { 1150 input: makeInput(FormatGNU, "0031"), 1151 wantSize: 031, 1152 }, { 1153 input: makeInput(FormatGNU, "80"), 1154 wantErr: ErrHeader, 1155 }, { 1156 input: makeInput(FormatGNU, "1234", 1157 makeSparseStrings(sparseDatas{{0, 0}, {1, 1}})...), 1158 wantMap: sparseDatas{{0, 0}, {1, 1}}, 1159 wantSize: 01234, 1160 }, { 1161 input: makeInput(FormatGNU, "1234", 1162 append(makeSparseStrings(sparseDatas{{0, 0}, {1, 1}}), []string{"", "blah"}...)...), 1163 wantMap: sparseDatas{{0, 0}, {1, 1}}, 1164 wantSize: 01234, 1165 }, { 1166 input: makeInput(FormatGNU, "3333", 1167 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}})...), 1168 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}}, 1169 wantSize: 03333, 1170 }, { 1171 input: makeInput(FormatGNU, "", 1172 append(append( 1173 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}}), 1174 []string{"", ""}...), 1175 makeSparseStrings(sparseDatas{{4, 1}, {6, 1}})...)...), 1176 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}}, 1177 }, { 1178 input: makeInput(FormatGNU, "", 1179 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:blockSize], 1180 wantErr: io.ErrUnexpectedEOF, 1181 }, { 1182 input: makeInput(FormatGNU, "", 1183 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:3*blockSize/2], 1184 wantErr: io.ErrUnexpectedEOF, 1185 }, { 1186 input: makeInput(FormatGNU, "", 1187 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...), 1188 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}}, 1189 }, { 1190 input: makeInput(FormatGNU, "", 1191 makeSparseStrings(sparseDatas{{10 << 30, 512}, {20 << 30, 512}})...), 1192 wantMap: sparseDatas{{10 << 30, 512}, {20 << 30, 512}}, 1193 }} 1194 1195 for i, v := range vectors { 1196 var blk block 1197 var hdr Header 1198 v.input = v.input[copy(blk[:], v.input):] 1199 tr := Reader{r: bytes.NewReader(v.input)} 1200 got, err := tr.readOldGNUSparseMap(&hdr, &blk) 1201 if !equalSparseEntries(got, v.wantMap) { 1202 t.Errorf("test %d, readOldGNUSparseMap(): got %v, want %v", i, got, v.wantMap) 1203 } 1204 if err != v.wantErr { 1205 t.Errorf("test %d, readOldGNUSparseMap() = %v, want %v", i, err, v.wantErr) 1206 } 1207 if hdr.Size != v.wantSize { 1208 t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize) 1209 } 1210 } 1211 } 1212 1213 func TestReadGNUSparsePAXHeaders(t *testing.T) { 1214 padInput := func(s string) string { 1215 return s + string(zeroBlock[:blockPadding(int64(len(s)))]) 1216 } 1217 1218 vectors := []struct { 1219 inputData string 1220 inputHdrs map[string]string 1221 wantMap sparseDatas 1222 wantSize int64 1223 wantName string 1224 wantErr error 1225 }{{ 1226 inputHdrs: nil, 1227 wantErr: nil, 1228 }, { 1229 inputHdrs: map[string]string{ 1230 paxGNUSparseNumBlocks: strconv.FormatInt(math.MaxInt64, 10), 1231 paxGNUSparseMap: "0,1,2,3", 1232 }, 1233 wantErr: ErrHeader, 1234 }, { 1235 inputHdrs: map[string]string{ 1236 paxGNUSparseNumBlocks: "4\x00", 1237 paxGNUSparseMap: "0,1,2,3", 1238 }, 1239 wantErr: ErrHeader, 1240 }, { 1241 inputHdrs: map[string]string{ 1242 paxGNUSparseNumBlocks: "4", 1243 paxGNUSparseMap: "0,1,2,3", 1244 }, 1245 wantErr: ErrHeader, 1246 }, { 1247 inputHdrs: map[string]string{ 1248 paxGNUSparseNumBlocks: "2", 1249 paxGNUSparseMap: "0,1,2,3", 1250 }, 1251 wantMap: sparseDatas{{0, 1}, {2, 3}}, 1252 }, { 1253 inputHdrs: map[string]string{ 1254 paxGNUSparseNumBlocks: "2", 1255 paxGNUSparseMap: "0, 1,2,3", 1256 }, 1257 wantErr: ErrHeader, 1258 }, { 1259 inputHdrs: map[string]string{ 1260 paxGNUSparseNumBlocks: "2", 1261 paxGNUSparseMap: "0,1,02,3", 1262 paxGNUSparseRealSize: "4321", 1263 }, 1264 wantMap: sparseDatas{{0, 1}, {2, 3}}, 1265 wantSize: 4321, 1266 }, { 1267 inputHdrs: map[string]string{ 1268 paxGNUSparseNumBlocks: "2", 1269 paxGNUSparseMap: "0,one1,2,3", 1270 }, 1271 wantErr: ErrHeader, 1272 }, { 1273 inputHdrs: map[string]string{ 1274 paxGNUSparseMajor: "0", 1275 paxGNUSparseMinor: "0", 1276 paxGNUSparseNumBlocks: "2", 1277 paxGNUSparseMap: "0,1,2,3", 1278 paxGNUSparseSize: "1234", 1279 paxGNUSparseRealSize: "4321", 1280 paxGNUSparseName: "realname", 1281 }, 1282 wantMap: sparseDatas{{0, 1}, {2, 3}}, 1283 wantSize: 1234, 1284 wantName: "realname", 1285 }, { 1286 inputHdrs: map[string]string{ 1287 paxGNUSparseMajor: "0", 1288 paxGNUSparseMinor: "0", 1289 paxGNUSparseNumBlocks: "1", 1290 paxGNUSparseMap: "10737418240,512", 1291 paxGNUSparseSize: "10737418240", 1292 paxGNUSparseName: "realname", 1293 }, 1294 wantMap: sparseDatas{{10737418240, 512}}, 1295 wantSize: 10737418240, 1296 wantName: "realname", 1297 }, { 1298 inputHdrs: map[string]string{ 1299 paxGNUSparseMajor: "0", 1300 paxGNUSparseMinor: "0", 1301 paxGNUSparseNumBlocks: "0", 1302 paxGNUSparseMap: "", 1303 }, 1304 wantMap: sparseDatas{}, 1305 }, { 1306 inputHdrs: map[string]string{ 1307 paxGNUSparseMajor: "0", 1308 paxGNUSparseMinor: "1", 1309 paxGNUSparseNumBlocks: "4", 1310 paxGNUSparseMap: "0,5,10,5,20,5,30,5", 1311 }, 1312 wantMap: sparseDatas{{0, 5}, {10, 5}, {20, 5}, {30, 5}}, 1313 }, { 1314 inputHdrs: map[string]string{ 1315 paxGNUSparseMajor: "1", 1316 paxGNUSparseMinor: "0", 1317 paxGNUSparseNumBlocks: "4", 1318 paxGNUSparseMap: "0,5,10,5,20,5,30,5", 1319 }, 1320 wantErr: io.ErrUnexpectedEOF, 1321 }, { 1322 inputData: padInput("0\n"), 1323 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1324 wantMap: sparseDatas{}, 1325 }, { 1326 inputData: padInput("0\n")[:blockSize-1] + "#", 1327 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1328 wantMap: sparseDatas{}, 1329 }, { 1330 inputData: padInput("0"), 1331 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1332 wantErr: io.ErrUnexpectedEOF, 1333 }, { 1334 inputData: padInput("ab\n"), 1335 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1336 wantErr: ErrHeader, 1337 }, { 1338 inputData: padInput("1\n2\n3\n"), 1339 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1340 wantMap: sparseDatas{{2, 3}}, 1341 }, { 1342 inputData: padInput("1\n2\n"), 1343 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1344 wantErr: io.ErrUnexpectedEOF, 1345 }, { 1346 inputData: padInput("1\n2\n\n"), 1347 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1348 wantErr: ErrHeader, 1349 }, { 1350 inputData: string(zeroBlock[:]) + padInput("0\n"), 1351 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1352 wantErr: ErrHeader, 1353 }, { 1354 inputData: strings.Repeat("0", blockSize) + padInput("1\n5\n1\n"), 1355 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1356 wantMap: sparseDatas{{5, 1}}, 1357 }, { 1358 inputData: padInput(fmt.Sprintf("%d\n", int64(math.MaxInt64))), 1359 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1360 wantErr: ErrHeader, 1361 }, { 1362 inputData: padInput(strings.Repeat("0", 300) + "1\n" + strings.Repeat("0", 1000) + "5\n" + strings.Repeat("0", 800) + "2\n"), 1363 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1364 wantMap: sparseDatas{{5, 2}}, 1365 }, { 1366 inputData: padInput("2\n10737418240\n512\n21474836480\n512\n"), 1367 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1368 wantMap: sparseDatas{{10737418240, 512}, {21474836480, 512}}, 1369 }, { 1370 inputData: padInput("100\n" + func() string { 1371 var ss []string 1372 for i := 0; i < 100; i++ { 1373 ss = append(ss, fmt.Sprintf("%d\n%d\n", int64(i)<<30, 512)) 1374 } 1375 return strings.Join(ss, "") 1376 }()), 1377 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"}, 1378 wantMap: func() (spd sparseDatas) { 1379 for i := 0; i < 100; i++ { 1380 spd = append(spd, SparseEntry{int64(i) << 30, 512}) 1381 } 1382 return spd 1383 }(), 1384 }} 1385 1386 for i, v := range vectors { 1387 var hdr Header 1388 hdr.PAXRecords = v.inputHdrs 1389 r := strings.NewReader(v.inputData + "#") // Add canary byte 1390 tr := Reader{curr: ®FileReader{r, int64(r.Len())}} 1391 got, err := tr.readGNUSparsePAXHeaders(&hdr) 1392 if !equalSparseEntries(got, v.wantMap) { 1393 t.Errorf("test %d, readGNUSparsePAXHeaders(): got %v, want %v", i, got, v.wantMap) 1394 } 1395 if err != v.wantErr { 1396 t.Errorf("test %d, readGNUSparsePAXHeaders() = %v, want %v", i, err, v.wantErr) 1397 } 1398 if hdr.Size != v.wantSize { 1399 t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize) 1400 } 1401 if hdr.Name != v.wantName { 1402 t.Errorf("test %d, Header.Name = %s, want %s", i, hdr.Name, v.wantName) 1403 } 1404 if v.wantErr == nil && r.Len() == 0 { 1405 t.Errorf("test %d, canary byte unexpectedly consumed", i) 1406 } 1407 } 1408 } 1409 1410 // testNonEmptyReader wraps an io.Reader and ensures that 1411 // Read is never called with an empty buffer. 1412 type testNonEmptyReader struct{ io.Reader } 1413 1414 func (r testNonEmptyReader) Read(b []byte) (int, error) { 1415 if len(b) == 0 { 1416 return 0, errors.New("unexpected empty Read call") 1417 } 1418 return r.Reader.Read(b) 1419 } 1420 1421 func TestFileReader(t *testing.T) { 1422 type ( 1423 testRead struct { // Read(cnt) == (wantStr, wantErr) 1424 cnt int 1425 wantStr string 1426 wantErr error 1427 } 1428 testWriteTo struct { // WriteTo(testFile{ops}) == (wantCnt, wantErr) 1429 ops fileOps 1430 wantCnt int64 1431 wantErr error 1432 } 1433 testRemaining struct { // LogicalRemaining() == wantLCnt, PhysicalRemaining() == wantPCnt 1434 wantLCnt int64 1435 wantPCnt int64 1436 } 1437 testFnc interface{} // testRead | testWriteTo | testRemaining 1438 ) 1439 1440 type ( 1441 makeReg struct { 1442 str string 1443 size int64 1444 } 1445 makeSparse struct { 1446 makeReg makeReg 1447 spd sparseDatas 1448 size int64 1449 } 1450 fileMaker interface{} // makeReg | makeSparse 1451 ) 1452 1453 vectors := []struct { 1454 maker fileMaker 1455 tests []testFnc 1456 }{{ 1457 maker: makeReg{"", 0}, 1458 tests: []testFnc{ 1459 testRemaining{0, 0}, 1460 testRead{0, "", io.EOF}, 1461 testRead{1, "", io.EOF}, 1462 testWriteTo{nil, 0, nil}, 1463 testRemaining{0, 0}, 1464 }, 1465 }, { 1466 maker: makeReg{"", 1}, 1467 tests: []testFnc{ 1468 testRemaining{1, 1}, 1469 testRead{5, "", io.ErrUnexpectedEOF}, 1470 testWriteTo{nil, 0, io.ErrUnexpectedEOF}, 1471 testRemaining{1, 1}, 1472 }, 1473 }, { 1474 maker: makeReg{"hello", 5}, 1475 tests: []testFnc{ 1476 testRemaining{5, 5}, 1477 testRead{5, "hello", io.EOF}, 1478 testRemaining{0, 0}, 1479 }, 1480 }, { 1481 maker: makeReg{"hello, world", 50}, 1482 tests: []testFnc{ 1483 testRemaining{50, 50}, 1484 testRead{7, "hello, ", nil}, 1485 testRemaining{43, 43}, 1486 testRead{5, "world", nil}, 1487 testRemaining{38, 38}, 1488 testWriteTo{nil, 0, io.ErrUnexpectedEOF}, 1489 testRead{1, "", io.ErrUnexpectedEOF}, 1490 testRemaining{38, 38}, 1491 }, 1492 }, { 1493 maker: makeReg{"hello, world", 5}, 1494 tests: []testFnc{ 1495 testRemaining{5, 5}, 1496 testRead{0, "", nil}, 1497 testRead{4, "hell", nil}, 1498 testRemaining{1, 1}, 1499 testWriteTo{fileOps{"o"}, 1, nil}, 1500 testRemaining{0, 0}, 1501 testWriteTo{nil, 0, nil}, 1502 testRead{0, "", io.EOF}, 1503 }, 1504 }, { 1505 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8}, 1506 tests: []testFnc{ 1507 testRemaining{8, 5}, 1508 testRead{3, "ab\x00", nil}, 1509 testRead{10, "\x00\x00cde", io.EOF}, 1510 testRemaining{0, 0}, 1511 }, 1512 }, { 1513 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8}, 1514 tests: []testFnc{ 1515 testRemaining{8, 5}, 1516 testWriteTo{fileOps{"ab", int64(3), "cde"}, 8, nil}, 1517 testRemaining{0, 0}, 1518 }, 1519 }, { 1520 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 10}, 1521 tests: []testFnc{ 1522 testRemaining{10, 5}, 1523 testRead{100, "ab\x00\x00\x00cde\x00\x00", io.EOF}, 1524 testRemaining{0, 0}, 1525 }, 1526 }, { 1527 maker: makeSparse{makeReg{"abc", 5}, sparseDatas{{0, 2}, {5, 3}}, 10}, 1528 tests: []testFnc{ 1529 testRemaining{10, 5}, 1530 testRead{100, "ab\x00\x00\x00c", io.ErrUnexpectedEOF}, 1531 testRemaining{4, 2}, 1532 }, 1533 }, { 1534 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 8}, 1535 tests: []testFnc{ 1536 testRemaining{8, 5}, 1537 testRead{8, "\x00abc\x00\x00de", io.EOF}, 1538 testRemaining{0, 0}, 1539 }, 1540 }, { 1541 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 0}, {6, 0}, {6, 2}}, 8}, 1542 tests: []testFnc{ 1543 testRemaining{8, 5}, 1544 testRead{8, "\x00abc\x00\x00de", io.EOF}, 1545 testRemaining{0, 0}, 1546 }, 1547 }, { 1548 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 0}, {6, 0}, {6, 2}}, 8}, 1549 tests: []testFnc{ 1550 testRemaining{8, 5}, 1551 testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, nil}, 1552 testRemaining{0, 0}, 1553 }, 1554 }, { 1555 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10}, 1556 tests: []testFnc{ 1557 testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF}, 1558 }, 1559 }, { 1560 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10}, 1561 tests: []testFnc{ 1562 testWriteTo{fileOps{int64(1), "abc", int64(2), "de", int64(1), "\x00"}, 10, nil}, 1563 }, 1564 }, { 1565 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}, {8, 0}, {8, 0}, {8, 0}, {8, 0}}, 10}, 1566 tests: []testFnc{ 1567 testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF}, 1568 }, 1569 }, { 1570 maker: makeSparse{makeReg{"", 0}, sparseDatas{}, 2}, 1571 tests: []testFnc{ 1572 testRead{100, "\x00\x00", io.EOF}, 1573 }, 1574 }, { 1575 maker: makeSparse{makeReg{"", 8}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1576 tests: []testFnc{ 1577 testRead{100, "\x00", io.ErrUnexpectedEOF}, 1578 }, 1579 }, { 1580 maker: makeSparse{makeReg{"ab", 2}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1581 tests: []testFnc{ 1582 testRead{100, "\x00ab", errMissData}, 1583 }, 1584 }, { 1585 maker: makeSparse{makeReg{"ab", 8}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1586 tests: []testFnc{ 1587 testRead{100, "\x00ab", io.ErrUnexpectedEOF}, 1588 }, 1589 }, { 1590 maker: makeSparse{makeReg{"abc", 3}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1591 tests: []testFnc{ 1592 testRead{100, "\x00abc\x00\x00", errMissData}, 1593 }, 1594 }, { 1595 maker: makeSparse{makeReg{"abc", 8}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1596 tests: []testFnc{ 1597 testRead{100, "\x00abc\x00\x00", io.ErrUnexpectedEOF}, 1598 }, 1599 }, { 1600 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1601 tests: []testFnc{ 1602 testRead{100, "\x00abc\x00\x00de", errMissData}, 1603 }, 1604 }, { 1605 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1606 tests: []testFnc{ 1607 testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, errMissData}, 1608 }, 1609 }, { 1610 maker: makeSparse{makeReg{"abcde", 8}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1611 tests: []testFnc{ 1612 testRead{100, "\x00abc\x00\x00de", io.ErrUnexpectedEOF}, 1613 }, 1614 }, { 1615 maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1616 tests: []testFnc{ 1617 testRemaining{15, 13}, 1618 testRead{100, "\x00abc\x00\x00defgh\x00\x00\x00\x00", errUnrefData}, 1619 testWriteTo{nil, 0, errUnrefData}, 1620 testRemaining{0, 5}, 1621 }, 1622 }, { 1623 maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15}, 1624 tests: []testFnc{ 1625 testRemaining{15, 13}, 1626 testWriteTo{fileOps{int64(1), "abc", int64(2), "defgh", int64(4)}, 15, errUnrefData}, 1627 testRead{100, "", errUnrefData}, 1628 testRemaining{0, 5}, 1629 }, 1630 }} 1631 1632 for i, v := range vectors { 1633 var fr fileReader 1634 switch maker := v.maker.(type) { 1635 case makeReg: 1636 r := testNonEmptyReader{strings.NewReader(maker.str)} 1637 fr = ®FileReader{r, maker.size} 1638 case makeSparse: 1639 if !validateSparseEntries(maker.spd, maker.size) { 1640 t.Fatalf("invalid sparse map: %v", maker.spd) 1641 } 1642 sph := invertSparseEntries(maker.spd, maker.size) 1643 r := testNonEmptyReader{strings.NewReader(maker.makeReg.str)} 1644 fr = ®FileReader{r, maker.makeReg.size} 1645 fr = &sparseFileReader{fr, sph, 0} 1646 default: 1647 t.Fatalf("test %d, unknown make operation: %T", i, maker) 1648 } 1649 1650 for j, tf := range v.tests { 1651 switch tf := tf.(type) { 1652 case testRead: 1653 b := make([]byte, tf.cnt) 1654 n, err := fr.Read(b) 1655 if got := string(b[:n]); got != tf.wantStr || err != tf.wantErr { 1656 t.Errorf("test %d.%d, Read(%d):\ngot (%q, %v)\nwant (%q, %v)", i, j, tf.cnt, got, err, tf.wantStr, tf.wantErr) 1657 } 1658 case testWriteTo: 1659 f := &testFile{ops: tf.ops} 1660 got, err := fr.WriteTo(f) 1661 if _, ok := err.(testError); ok { 1662 t.Errorf("test %d.%d, WriteTo(): %v", i, j, err) 1663 } else if got != tf.wantCnt || err != tf.wantErr { 1664 t.Errorf("test %d.%d, WriteTo() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr) 1665 } 1666 if len(f.ops) > 0 { 1667 t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops)) 1668 } 1669 case testRemaining: 1670 if got := fr.LogicalRemaining(); got != tf.wantLCnt { 1671 t.Errorf("test %d.%d, LogicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt) 1672 } 1673 if got := fr.PhysicalRemaining(); got != tf.wantPCnt { 1674 t.Errorf("test %d.%d, PhysicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt) 1675 } 1676 default: 1677 t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf) 1678 } 1679 } 1680 } 1681 }