Source file
src/os/readfrom_unix_test.go
1
2
3
4
5
6
7 package os_test
8
9 import (
10 "bytes"
11 "io"
12 "math/rand"
13 . "os"
14 "runtime"
15 "strconv"
16 "strings"
17 "syscall"
18 "testing"
19 "time"
20 )
21
22 type (
23 copyFileTestFunc func(*testing.T, int64) (*File, *File, []byte, *copyFileHook, string)
24 copyFileTestHook func(*testing.T) (*copyFileHook, string)
25 )
26
27 func TestCopyFile(t *testing.T) {
28 sizes := []int{
29 1,
30 42,
31 1025,
32 syscall.Getpagesize() + 1,
33 32769,
34 }
35 t.Run("Basic", func(t *testing.T) {
36 for _, size := range sizes {
37 t.Run(strconv.Itoa(size), func(t *testing.T) {
38 testCopyFiles(t, int64(size), -1)
39 })
40 }
41 })
42 t.Run("Limited", func(t *testing.T) {
43 t.Run("OneLess", func(t *testing.T) {
44 for _, size := range sizes {
45 t.Run(strconv.Itoa(size), func(t *testing.T) {
46 testCopyFiles(t, int64(size), int64(size)-1)
47 })
48 }
49 })
50 t.Run("Half", func(t *testing.T) {
51 for _, size := range sizes {
52 t.Run(strconv.Itoa(size), func(t *testing.T) {
53 testCopyFiles(t, int64(size), int64(size)/2)
54 })
55 }
56 })
57 t.Run("More", func(t *testing.T) {
58 for _, size := range sizes {
59 t.Run(strconv.Itoa(size), func(t *testing.T) {
60 testCopyFiles(t, int64(size), int64(size)+7)
61 })
62 }
63 })
64 })
65 t.Run("DoesntTryInAppendMode", func(t *testing.T) {
66 for _, newTest := range copyFileTests {
67 dst, src, data, hook, testName := newTest(t, 42)
68
69 dst2, err := OpenFile(dst.Name(), O_RDWR|O_APPEND, 0755)
70 if err != nil {
71 t.Fatalf("%s: %v", testName, err)
72 }
73 defer dst2.Close()
74
75 if _, err := io.Copy(dst2, src); err != nil {
76 t.Fatalf("%s: %v", testName, err)
77 }
78 switch runtime.GOOS {
79 case "illumos", "solaris":
80 if !hook.called {
81 t.Fatalf("%s: should have called the hook even with destination in O_APPEND mode", testName)
82 }
83 default:
84 if hook.called {
85 t.Fatalf("%s: hook shouldn't be called with destination in O_APPEND mode", testName)
86 }
87 }
88 mustSeekStart(t, dst2)
89 mustContainData(t, dst2, data)
90 }
91 })
92 t.Run("CopyFileItself", func(t *testing.T) {
93 for _, hookFunc := range copyFileHooks {
94 hook, testName := hookFunc(t)
95
96 f, err := CreateTemp("", "file-readfrom-itself-test")
97 if err != nil {
98 t.Fatalf("%s: failed to create tmp file: %v", testName, err)
99 }
100 t.Cleanup(func() {
101 f.Close()
102 Remove(f.Name())
103 })
104
105 data := []byte("hello world!")
106 if _, err := f.Write(data); err != nil {
107 t.Fatalf("%s: failed to create and feed the file: %v", testName, err)
108 }
109
110 if err := f.Sync(); err != nil {
111 t.Fatalf("%s: failed to save the file: %v", testName, err)
112 }
113
114
115 if _, err := f.Seek(0, io.SeekStart); err != nil {
116 t.Fatalf("%s: failed to rewind the file: %v", testName, err)
117 }
118
119
120 if _, err := io.Copy(f, f); err != nil {
121 t.Fatalf("%s: failed to read from the file: %v", testName, err)
122 }
123
124 if hook.written != 0 || hook.handled || hook.err != nil {
125 t.Fatalf("%s: File.readFrom is expected not to use any zero-copy techniques when copying itself."+
126 "got hook.written=%d, hook.handled=%t, hook.err=%v; expected hook.written=0, hook.handled=false, hook.err=nil",
127 testName, hook.written, hook.handled, hook.err)
128 }
129
130 switch testName {
131 case "hookCopyFileRange":
132
133
134
135 if !hook.called {
136 t.Fatalf("%s: should have called the hook", testName)
137 }
138 case "hookSendFile", "hookSendFileOverCopyFileRange":
139
140
141 if hook.called {
142 t.Fatalf("%s: shouldn't have called the hook", testName)
143 }
144 default:
145 t.Fatalf("%s: unexpected test", testName)
146 }
147
148
149 if _, err := f.Seek(0, io.SeekStart); err != nil {
150 t.Fatalf("%s: failed to rewind the file: %v", testName, err)
151 }
152
153 data2, err := io.ReadAll(f)
154 if err != nil {
155 t.Fatalf("%s: failed to read from the file: %v", testName, err)
156 }
157
158
159 if s := strings.Repeat(string(data), 2); s != string(data2) {
160 t.Fatalf("%s: file contained %s, expected %s", testName, data2, s)
161 }
162 }
163 })
164 t.Run("NotRegular", func(t *testing.T) {
165 t.Run("BothPipes", func(t *testing.T) {
166 for _, hookFunc := range copyFileHooks {
167 hook, testName := hookFunc(t)
168
169 pr1, pw1, err := Pipe()
170 if err != nil {
171 t.Fatalf("%s: %v", testName, err)
172 }
173 defer pr1.Close()
174 defer pw1.Close()
175
176 pr2, pw2, err := Pipe()
177 if err != nil {
178 t.Fatalf("%s: %v", testName, err)
179 }
180 defer pr2.Close()
181 defer pw2.Close()
182
183
184
185
186 data := []byte("hello")
187 if _, err := pw1.Write(data); err != nil {
188 t.Fatalf("%s: %v", testName, err)
189 }
190 pw1.Close()
191
192 n, err := io.Copy(pw2, pr1)
193 if err != nil {
194 t.Fatalf("%s: %v", testName, err)
195 }
196 if n != int64(len(data)) {
197 t.Fatalf("%s: transferred %d, want %d", testName, n, len(data))
198 }
199 switch runtime.GOOS {
200 case "illumos", "solaris":
201
202
203
204
205 if hook.called {
206 t.Fatalf("%s: shouldn't have called the hook with a source or a destination of pipe", testName)
207 }
208 default:
209 if !hook.called {
210 t.Fatalf("%s: should have called the hook with both source and destination of pipe", testName)
211 }
212 }
213 pw2.Close()
214 mustContainData(t, pr2, data)
215 }
216 })
217 t.Run("DstPipe", func(t *testing.T) {
218 for _, newTest := range copyFileTests {
219 dst, src, data, hook, testName := newTest(t, 255)
220 dst.Close()
221
222 pr, pw, err := Pipe()
223 if err != nil {
224 t.Fatalf("%s: %v", testName, err)
225 }
226 defer pr.Close()
227 defer pw.Close()
228
229 n, err := io.Copy(pw, src)
230 if err != nil {
231 t.Fatalf("%s: %v", testName, err)
232 }
233 if n != int64(len(data)) {
234 t.Fatalf("%s: transferred %d, want %d", testName, n, len(data))
235 }
236 switch runtime.GOOS {
237 case "illumos":
238
239
240 if hook.called {
241 t.Fatalf("%s: shouldn't have called the hook with a destination of pipe", testName)
242 }
243 default:
244 if !hook.called {
245 t.Fatalf("%s: should have called the hook with a destination of pipe", testName)
246 }
247 }
248 pw.Close()
249 mustContainData(t, pr, data)
250 }
251 })
252 t.Run("SrcPipe", func(t *testing.T) {
253 for _, newTest := range copyFileTests {
254 dst, src, data, hook, testName := newTest(t, 255)
255 src.Close()
256
257 pr, pw, err := Pipe()
258 if err != nil {
259 t.Fatalf("%s: %v", testName, err)
260 }
261 defer pr.Close()
262 defer pw.Close()
263
264
265
266
267 if _, err := pw.Write(data); err != nil {
268 t.Fatalf("%s: %v", testName, err)
269 }
270 pw.Close()
271
272 n, err := io.Copy(dst, pr)
273 if err != nil {
274 t.Fatalf("%s: %v", testName, err)
275 }
276 if n != int64(len(data)) {
277 t.Fatalf("%s: transferred %d, want %d", testName, n, len(data))
278 }
279 switch runtime.GOOS {
280 case "illumos", "solaris":
281
282
283 if hook.called {
284 t.Fatalf("%s: shouldn't have called the hook with a source of pipe", testName)
285 }
286 default:
287 if !hook.called {
288 t.Fatalf("%s: should have called the hook with a source of pipe", testName)
289 }
290 }
291 mustSeekStart(t, dst)
292 mustContainData(t, dst, data)
293 }
294 })
295 })
296 t.Run("Nil", func(t *testing.T) {
297 var nilFile *File
298 anyFile, err := CreateTemp("", "")
299 if err != nil {
300 t.Fatal(err)
301 }
302 defer Remove(anyFile.Name())
303 defer anyFile.Close()
304
305 if _, err := io.Copy(nilFile, nilFile); err != ErrInvalid {
306 t.Errorf("io.Copy(nilFile, nilFile) = %v, want %v", err, ErrInvalid)
307 }
308 if _, err := io.Copy(anyFile, nilFile); err != ErrInvalid {
309 t.Errorf("io.Copy(anyFile, nilFile) = %v, want %v", err, ErrInvalid)
310 }
311 if _, err := io.Copy(nilFile, anyFile); err != ErrInvalid {
312 t.Errorf("io.Copy(nilFile, anyFile) = %v, want %v", err, ErrInvalid)
313 }
314
315 if _, err := nilFile.ReadFrom(nilFile); err != ErrInvalid {
316 t.Errorf("nilFile.ReadFrom(nilFile) = %v, want %v", err, ErrInvalid)
317 }
318 if _, err := anyFile.ReadFrom(nilFile); err != ErrInvalid {
319 t.Errorf("anyFile.ReadFrom(nilFile) = %v, want %v", err, ErrInvalid)
320 }
321 if _, err := nilFile.ReadFrom(anyFile); err != ErrInvalid {
322 t.Errorf("nilFile.ReadFrom(anyFile) = %v, want %v", err, ErrInvalid)
323 }
324 })
325 }
326
327 func testCopyFile(t *testing.T, dst, src *File, data []byte, hook *copyFileHook, limit int64, testName string) {
328
329 var (
330 realsrc io.Reader
331 lr *io.LimitedReader
332 )
333 if limit >= 0 {
334 lr = &io.LimitedReader{N: limit, R: src}
335 realsrc = lr
336 if limit < int64(len(data)) {
337 data = data[:limit]
338 }
339 } else {
340 realsrc = src
341 }
342
343
344
345 n, err := io.Copy(dst, realsrc)
346 if err != nil {
347 t.Fatalf("%s: %v", testName, err)
348 }
349
350
351
352 if limit != 0 && !hook.called {
353 t.Fatalf("%s: never called the hook", testName)
354 }
355 if hook.called && hook.dstfd != int(dst.Fd()) {
356 t.Fatalf("%s: wrong destination file descriptor: got %d, want %d", testName, hook.dstfd, dst.Fd())
357 }
358 if hook.called && hook.srcfd != int(src.Fd()) {
359 t.Fatalf("%s: wrong source file descriptor: got %d, want %d", testName, hook.srcfd, src.Fd())
360 }
361
362
363
364
365 dstoff, err := dst.Seek(0, io.SeekCurrent)
366 if err != nil {
367 t.Fatalf("%s: %v", testName, err)
368 }
369 srcoff, err := src.Seek(0, io.SeekCurrent)
370 if err != nil {
371 t.Fatalf("%s: %v", testName, err)
372 }
373 if dstoff != srcoff {
374 t.Errorf("%s: offsets differ: dstoff = %d, srcoff = %d", testName, dstoff, srcoff)
375 }
376 if dstoff != int64(len(data)) {
377 t.Errorf("%s: dstoff = %d, want %d", testName, dstoff, len(data))
378 }
379 if n != int64(len(data)) {
380 t.Errorf("%s: short ReadFrom: wrote %d bytes, want %d", testName, n, len(data))
381 }
382 mustSeekStart(t, dst)
383 mustContainData(t, dst, data)
384
385
386 if lr != nil {
387 if want := limit - n; lr.N != want {
388 t.Fatalf("%s: didn't update limit correctly: got %d, want %d", testName, lr.N, want)
389 }
390 }
391 }
392
393
394
395 func mustContainData(t *testing.T, f *File, data []byte) {
396 t.Helper()
397
398 got := make([]byte, len(data))
399 if _, err := io.ReadFull(f, got); err != nil {
400 t.Fatal(err)
401 }
402 if !bytes.Equal(got, data) {
403 t.Fatalf("didn't get the same data back from %s", f.Name())
404 }
405 if _, err := f.Read(make([]byte, 1)); err != io.EOF {
406 t.Fatalf("not at EOF")
407 }
408 }
409
410 func mustSeekStart(t *testing.T, f *File) {
411 if _, err := f.Seek(0, io.SeekStart); err != nil {
412 t.Fatal(err)
413 }
414 }
415
416
417
418
419
420 func newCopyFileTest(t *testing.T, size int64) (dst, src *File, data []byte) {
421 src, data = createTempFile(t, "test-copy-file-src", size)
422
423 dst, err := CreateTemp(t.TempDir(), "test-copy-file-dst")
424 if err != nil {
425 t.Fatal(err)
426 }
427 t.Cleanup(func() { dst.Close() })
428
429 return
430 }
431
432 type copyFileHook struct {
433 called bool
434 dstfd int
435 srcfd int
436
437 written int64
438 handled bool
439 err error
440 }
441
442 func createTempFile(tb testing.TB, name string, size int64) (*File, []byte) {
443 f, err := CreateTemp(tb.TempDir(), name)
444 if err != nil {
445 tb.Fatalf("failed to create temporary file: %v", err)
446 }
447 tb.Cleanup(func() {
448 f.Close()
449 })
450
451 randSeed := time.Now().Unix()
452 tb.Logf("random data seed: %d\n", randSeed)
453 prng := rand.New(rand.NewSource(randSeed))
454 data := make([]byte, size)
455 prng.Read(data)
456 if _, err := f.Write(data); err != nil {
457 tb.Fatalf("failed to create and feed the file: %v", err)
458 }
459 if err := f.Sync(); err != nil {
460 tb.Fatalf("failed to save the file: %v", err)
461 }
462 if _, err := f.Seek(0, io.SeekStart); err != nil {
463 tb.Fatalf("failed to rewind the file: %v", err)
464 }
465
466 return f, data
467 }
468
View as plain text