Repository navigation
Expand file tree
/
Copy patherrors_test.go
More file actions
165 lines (133 loc) · 4.51 KB
/
Copy patherrors_test.go
File metadata and controls
165 lines (133 loc) · 4.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright 2026 Forkcloser. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xz //nolint:testpackage // white-box: tests the unexported uvarint codec on hostile streams
import (
"bytes"
"errors"
"io"
"testing"
"time"
)
// errFailingReaderAt fails every read with a distinctive I/O error.
type errFailingReaderAt struct{ err error }
func (e errFailingReaderAt) ReadAt([]byte, int64) (int, error) {
return 0, e.err
}
// TestErrCorruptDistinguishesFromIO is the point of the sentinels: a caller
// deciding whether to reject input or retry a transfer must be able to tell a
// malformed file from a failed read without matching message text.
func TestErrCorruptDistinguishesFromIO(t *testing.T) {
t.Parallel()
// A malformed file.
bad := hostileStream([]byte{0, 0, 0, 0},
[]hostileRecord{{unpaddedSize: 1, uncompressedSize: 1 << 60}}, -1)
_, err := NewParallelReader(bytes.NewReader(bad), int64(len(bad)))
if err == nil {
t.Fatal("hostile file accepted")
}
if !errors.Is(err, ErrCorrupt) {
t.Errorf("corrupt input gave %v, which does not match ErrCorrupt", err)
}
// A transport failure must not be mistaken for corruption.
ioErr := errors.New("disk on fire")
_, err = NewParallelReader(errFailingReaderAt{err: ioErr}, 1024)
if !errors.Is(err, ioErr) {
t.Errorf("I/O failure gave %v; want it to wrap the reader's error", err)
}
if errors.Is(err, ErrCorrupt) {
t.Errorf("I/O failure %v matched ErrCorrupt", err)
}
}
// TestErrClosedMatchesSentinel covers the reader and the writer, whose closed
// errors used to be unexported values with nothing in common.
func TestErrClosedMatchesSentinel(t *testing.T) {
t.Parallel()
data := parallelTestData(1 << 14)
xz := compressMultiBlock(t, data, 4<<10)
r, err := NewParallelReader(bytes.NewReader(xz), int64(len(xz)))
if err != nil {
t.Fatal(err)
}
if err = r.Close(); err != nil {
t.Fatal(err)
}
if _, err = r.Read(make([]byte, 4)); !errors.Is(err, ErrClosed) {
t.Errorf("Read after Close gave %v; want a match for ErrClosed", err)
}
var buf bytes.Buffer
w, err := NewWriter(&buf)
if err != nil {
t.Fatal(err)
}
if err = w.Close(); err != nil {
t.Fatal(err)
}
if _, err = w.Write([]byte("x")); !errors.Is(err, ErrClosed) {
t.Errorf("Write after Close gave %v; want a match for ErrClosed", err)
}
}
// TestNonCanonicalUvarintRejected pins the encoding rules the xz format sets
// and liblzma enforces: shortest form, at most nine bytes. Accepting more
// would make this package disagree with the reference decoder about what a
// file contains.
func TestNonCanonicalUvarintRejected(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
enc []byte
}{
{"zero in two bytes", []byte{0x80, 0x00}},
{"zero in nine bytes", []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00}},
{"one in three bytes", []byte{0x81, 0x80, 0x00}},
} {
_, _, err := readUvarint(bytes.NewReader(tc.enc))
if !errors.Is(err, ErrCorrupt) {
t.Errorf("%s: got %v; want a corruption error", tc.name, err)
}
}
// Ten bytes exceeds the format's limit even with a valid final byte.
tooLong := []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01}
if _, _, err := readUvarint(bytes.NewReader(tooLong)); !errors.Is(err, ErrCorrupt) {
t.Errorf("ten-byte uvarint: got %v; want a corruption error", err)
}
// The canonical forms still work.
for _, u := range []uint64{0, 1, 0x7f, 0x80, 1<<63 - 1} {
p := make([]byte, 10)
n := putUvarint(p, u)
x, m, err := readUvarint(bytes.NewReader(p[:n]))
if err != nil || x != u || m != n {
t.Errorf("round trip of %d: got (%d, %d, %v)", u, x, m, err)
}
}
}
// TestZeroWorkersStillDecodes covers the promoted Workers field being set to
// something unusable between construction and the first read. It used to leave
// Read waiting on blocks that nothing was decoding.
func TestZeroWorkersStillDecodes(t *testing.T) {
t.Parallel()
data := parallelTestData(1 << 16)
xz := compressMultiBlock(t, data, 8<<10)
r, err := NewParallelReader(bytes.NewReader(xz), int64(len(xz)))
if err != nil {
t.Fatal(err)
}
defer func() { _ = r.Close() }()
r.Workers = 0
done := make(chan error, 1)
go func() {
got, err := io.ReadAll(r)
if err == nil && !bytes.Equal(got, data) {
err = errors.New("decoded data differs from original")
}
done <- err
}()
select {
case err := <-done:
if err != nil {
t.Fatal(err)
}
case <-time.After(20 * time.Second):
t.Fatal("Read never returned with Workers set to zero")
}
}