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