Source file
src/os/readfrom_linux_test.go
Documentation: os
1
2
3
4
5 package os_test
6
7 import (
8 "bytes"
9 "errors"
10 "internal/poll"
11 "internal/testpty"
12 "io"
13 "math/rand"
14 "net"
15 . "os"
16 "path/filepath"
17 "runtime"
18 "strconv"
19 "strings"
20 "sync"
21 "syscall"
22 "testing"
23 "time"
24
25 "golang.org/x/net/nettest"
26 )
27
28 func TestCopyFileRange(t *testing.T) {
29 sizes := []int{
30 1,
31 42,
32 1025,
33 syscall.Getpagesize() + 1,
34 32769,
35 }
36 t.Run("Basic", func(t *testing.T) {
37 for _, size := range sizes {
38 t.Run(strconv.Itoa(size), func(t *testing.T) {
39 testCopyFileRange(t, int64(size), -1)
40 })
41 }
42 })
43 t.Run("Limited", func(t *testing.T) {
44 t.Run("OneLess", func(t *testing.T) {
45 for _, size := range sizes {
46 t.Run(strconv.Itoa(size), func(t *testing.T) {
47 testCopyFileRange(t, int64(size), int64(size)-1)
48 })
49 }
50 })
51 t.Run("Half", func(t *testing.T) {
52 for _, size := range sizes {
53 t.Run(strconv.Itoa(size), func(t *testing.T) {
54 testCopyFileRange(t, int64(size), int64(size)/2)
55 })
56 }
57 })
58 t.Run("More", func(t *testing.T) {
59 for _, size := range sizes {
60 t.Run(strconv.Itoa(size), func(t *testing.T) {
61 testCopyFileRange(t, int64(size), int64(size)+7)
62 })
63 }
64 })
65 })
66 t.Run("DoesntTryInAppendMode", func(t *testing.T) {
67 dst, src, data, hook := newCopyFileRangeTest(t, 42)
68
69 dst2, err := OpenFile(dst.Name(), O_RDWR|O_APPEND, 0755)
70 if err != nil {
71 t.Fatal(err)
72 }
73 defer dst2.Close()
74
75 if _, err := io.Copy(dst2, src); err != nil {
76 t.Fatal(err)
77 }
78 if hook.called {
79 t.Fatal("called poll.CopyFileRange for destination in O_APPEND mode")
80 }
81 mustSeekStart(t, dst2)
82 mustContainData(t, dst2, data)
83 })
84 t.Run("CopyFileItself", func(t *testing.T) {
85 hook := hookCopyFileRange(t)
86
87 f, err := CreateTemp("", "file-readfrom-itself-test")
88 if err != nil {
89 t.Fatalf("failed to create tmp file: %v", err)
90 }
91 t.Cleanup(func() {
92 f.Close()
93 Remove(f.Name())
94 })
95
96 data := []byte("hello world!")
97 if _, err := f.Write(data); err != nil {
98 t.Fatalf("failed to create and feed the file: %v", err)
99 }
100
101 if err := f.Sync(); err != nil {
102 t.Fatalf("failed to save the file: %v", err)
103 }
104
105
106 if _, err := f.Seek(0, io.SeekStart); err != nil {
107 t.Fatalf("failed to rewind the file: %v", err)
108 }
109
110
111 if _, err := io.Copy(f, f); err != nil {
112 t.Fatalf("failed to read from the file: %v", err)
113 }
114
115 if !hook.called || hook.written != 0 || hook.handled || hook.err != nil {
116 t.Fatalf("poll.CopyFileRange should be called and return the EINVAL error, but got hook.called=%t, hook.err=%v", hook.called, hook.err)
117 }
118
119
120 if _, err := f.Seek(0, io.SeekStart); err != nil {
121 t.Fatalf("failed to rewind the file: %v", err)
122 }
123
124 data2, err := io.ReadAll(f)
125 if err != nil {
126 t.Fatalf("failed to read from the file: %v", err)
127 }
128
129
130 if strings.Repeat(string(data), 2) != string(data2) {
131 t.Fatalf("data mismatch: %s != %s", string(data), string(data2))
132 }
133 })
134 t.Run("NotRegular", func(t *testing.T) {
135 t.Run("BothPipes", func(t *testing.T) {
136 hook := hookCopyFileRange(t)
137
138 pr1, pw1, err := Pipe()
139 if err != nil {
140 t.Fatal(err)
141 }
142 defer pr1.Close()
143 defer pw1.Close()
144
145 pr2, pw2, err := Pipe()
146 if err != nil {
147 t.Fatal(err)
148 }
149 defer pr2.Close()
150 defer pw2.Close()
151
152
153
154
155 data := []byte("hello")
156 if _, err := pw1.Write(data); err != nil {
157 t.Fatal(err)
158 }
159 pw1.Close()
160
161 n, err := io.Copy(pw2, pr1)
162 if err != nil {
163 t.Fatal(err)
164 }
165 if n != int64(len(data)) {
166 t.Fatalf("transferred %d, want %d", n, len(data))
167 }
168 if !hook.called {
169 t.Fatalf("should have called poll.CopyFileRange")
170 }
171 pw2.Close()
172 mustContainData(t, pr2, data)
173 })
174 t.Run("DstPipe", func(t *testing.T) {
175 dst, src, data, hook := newCopyFileRangeTest(t, 255)
176 dst.Close()
177
178 pr, pw, err := Pipe()
179 if err != nil {
180 t.Fatal(err)
181 }
182 defer pr.Close()
183 defer pw.Close()
184
185 n, err := io.Copy(pw, src)
186 if err != nil {
187 t.Fatal(err)
188 }
189 if n != int64(len(data)) {
190 t.Fatalf("transferred %d, want %d", n, len(data))
191 }
192 if !hook.called {
193 t.Fatalf("should have called poll.CopyFileRange")
194 }
195 pw.Close()
196 mustContainData(t, pr, data)
197 })
198 t.Run("SrcPipe", func(t *testing.T) {
199 dst, src, data, hook := newCopyFileRangeTest(t, 255)
200 src.Close()
201
202 pr, pw, err := Pipe()
203 if err != nil {
204 t.Fatal(err)
205 }
206 defer pr.Close()
207 defer pw.Close()
208
209
210
211
212 if _, err := pw.Write(data); err != nil {
213 t.Fatal(err)
214 }
215 pw.Close()
216
217 n, err := io.Copy(dst, pr)
218 if err != nil {
219 t.Fatal(err)
220 }
221 if n != int64(len(data)) {
222 t.Fatalf("transferred %d, want %d", n, len(data))
223 }
224 if !hook.called {
225 t.Fatalf("should have called poll.CopyFileRange")
226 }
227 mustSeekStart(t, dst)
228 mustContainData(t, dst, data)
229 })
230 })
231 t.Run("Nil", func(t *testing.T) {
232 var nilFile *File
233 anyFile, err := CreateTemp("", "")
234 if err != nil {
235 t.Fatal(err)
236 }
237 defer Remove(anyFile.Name())
238 defer anyFile.Close()
239
240 if _, err := io.Copy(nilFile, nilFile); err != ErrInvalid {
241 t.Errorf("io.Copy(nilFile, nilFile) = %v, want %v", err, ErrInvalid)
242 }
243 if _, err := io.Copy(anyFile, nilFile); err != ErrInvalid {
244 t.Errorf("io.Copy(anyFile, nilFile) = %v, want %v", err, ErrInvalid)
245 }
246 if _, err := io.Copy(nilFile, anyFile); err != ErrInvalid {
247 t.Errorf("io.Copy(nilFile, anyFile) = %v, want %v", err, ErrInvalid)
248 }
249
250 if _, err := nilFile.ReadFrom(nilFile); err != ErrInvalid {
251 t.Errorf("nilFile.ReadFrom(nilFile) = %v, want %v", err, ErrInvalid)
252 }
253 if _, err := anyFile.ReadFrom(nilFile); err != ErrInvalid {
254 t.Errorf("anyFile.ReadFrom(nilFile) = %v, want %v", err, ErrInvalid)
255 }
256 if _, err := nilFile.ReadFrom(anyFile); err != ErrInvalid {
257 t.Errorf("nilFile.ReadFrom(anyFile) = %v, want %v", err, ErrInvalid)
258 }
259 })
260 }
261
262 func TestSpliceFile(t *testing.T) {
263 sizes := []int{
264 1,
265 42,
266 1025,
267 syscall.Getpagesize() + 1,
268 32769,
269 }
270 t.Run("Basic-TCP", func(t *testing.T) {
271 for _, size := range sizes {
272 t.Run(strconv.Itoa(size), func(t *testing.T) {
273 testSpliceFile(t, "tcp", int64(size), -1)
274 })
275 }
276 })
277 t.Run("Basic-Unix", func(t *testing.T) {
278 for _, size := range sizes {
279 t.Run(strconv.Itoa(size), func(t *testing.T) {
280 testSpliceFile(t, "unix", int64(size), -1)
281 })
282 }
283 })
284 t.Run("TCP-To-TTY", func(t *testing.T) {
285 testSpliceToTTY(t, "tcp", 32768)
286 })
287 t.Run("Unix-To-TTY", func(t *testing.T) {
288 testSpliceToTTY(t, "unix", 32768)
289 })
290 t.Run("Limited", func(t *testing.T) {
291 t.Run("OneLess-TCP", func(t *testing.T) {
292 for _, size := range sizes {
293 t.Run(strconv.Itoa(size), func(t *testing.T) {
294 testSpliceFile(t, "tcp", int64(size), int64(size)-1)
295 })
296 }
297 })
298 t.Run("OneLess-Unix", func(t *testing.T) {
299 for _, size := range sizes {
300 t.Run(strconv.Itoa(size), func(t *testing.T) {
301 testSpliceFile(t, "unix", int64(size), int64(size)-1)
302 })
303 }
304 })
305 t.Run("Half-TCP", func(t *testing.T) {
306 for _, size := range sizes {
307 t.Run(strconv.Itoa(size), func(t *testing.T) {
308 testSpliceFile(t, "tcp", int64(size), int64(size)/2)
309 })
310 }
311 })
312 t.Run("Half-Unix", func(t *testing.T) {
313 for _, size := range sizes {
314 t.Run(strconv.Itoa(size), func(t *testing.T) {
315 testSpliceFile(t, "unix", int64(size), int64(size)/2)
316 })
317 }
318 })
319 t.Run("More-TCP", func(t *testing.T) {
320 for _, size := range sizes {
321 t.Run(strconv.Itoa(size), func(t *testing.T) {
322 testSpliceFile(t, "tcp", int64(size), int64(size)+1)
323 })
324 }
325 })
326 t.Run("More-Unix", func(t *testing.T) {
327 for _, size := range sizes {
328 t.Run(strconv.Itoa(size), func(t *testing.T) {
329 testSpliceFile(t, "unix", int64(size), int64(size)+1)
330 })
331 }
332 })
333 })
334 }
335
336 func testSpliceFile(t *testing.T, proto string, size, limit int64) {
337 dst, src, data, hook, cleanup := newSpliceFileTest(t, proto, size)
338 defer cleanup()
339
340
341 var (
342 r io.Reader
343 lr *io.LimitedReader
344 )
345 if limit >= 0 {
346 lr = &io.LimitedReader{N: limit, R: src}
347 r = lr
348 if limit < int64(len(data)) {
349 data = data[:limit]
350 }
351 } else {
352 r = src
353 }
354
355 n, err := io.Copy(dst, r)
356 if err != nil {
357 t.Fatal(err)
358 }
359
360
361 if n > 0 && !hook.called {
362 t.Fatal("expected to called poll.Splice")
363 }
364 if hook.called && hook.dstfd != int(dst.Fd()) {
365 t.Fatalf("wrong destination file descriptor: got %d, want %d", hook.dstfd, dst.Fd())
366 }
367 sc, ok := src.(syscall.Conn)
368 if !ok {
369 t.Fatalf("server Conn is not a syscall.Conn")
370 }
371 rc, err := sc.SyscallConn()
372 if err != nil {
373 t.Fatalf("server Conn SyscallConn error: %v", err)
374 }
375 if err = rc.Control(func(fd uintptr) {
376 if hook.called && hook.srcfd != int(fd) {
377 t.Fatalf("wrong source file descriptor: got %d, want %d", hook.srcfd, int(fd))
378 }
379 }); err != nil {
380 t.Fatalf("server Conn Control error: %v", err)
381 }
382
383
384
385
386 dstoff, err := dst.Seek(0, io.SeekCurrent)
387 if err != nil {
388 t.Fatal(err)
389 }
390 if dstoff != int64(len(data)) {
391 t.Errorf("dstoff = %d, want %d", dstoff, len(data))
392 }
393 if n != int64(len(data)) {
394 t.Errorf("short ReadFrom: wrote %d bytes, want %d", n, len(data))
395 }
396 mustSeekStart(t, dst)
397 mustContainData(t, dst, data)
398
399
400 if lr != nil {
401 if want := limit - n; lr.N != want {
402 t.Fatalf("didn't update limit correctly: got %d, want %d", lr.N, want)
403 }
404 }
405 }
406
407
408 func testSpliceToTTY(t *testing.T, proto string, size int64) {
409 var wg sync.WaitGroup
410
411
412
413
414 defer wg.Wait()
415
416 pty, ttyName, err := testpty.Open()
417 if err != nil {
418 t.Skipf("skipping test because pty open failed: %v", err)
419 }
420 defer pty.Close()
421
422
423
424
425 ttyFD, err := syscall.Open(ttyName, syscall.O_RDWR, 0)
426 if err != nil {
427 t.Skipf("skipping test becaused failed to open tty: %v", err)
428 }
429 defer syscall.Close(ttyFD)
430
431 tty := NewFile(uintptr(ttyFD), "tty")
432 defer tty.Close()
433
434 client, server := createSocketPair(t, proto)
435
436 data := bytes.Repeat([]byte{'a'}, int(size))
437
438 wg.Add(1)
439 go func() {
440 defer wg.Done()
441
442
443
444 for i := 0; i < len(data); i += 1024 {
445 if _, err := client.Write(data[i : i+1024]); err != nil {
446
447
448 if !errors.Is(err, net.ErrClosed) {
449 t.Errorf("error writing to socket: %v", err)
450 }
451 return
452 }
453 }
454 client.Close()
455 }()
456
457 wg.Add(1)
458 go func() {
459 defer wg.Done()
460 buf := make([]byte, 32)
461 for {
462 if _, err := pty.Read(buf); err != nil {
463 if err != io.EOF && !errors.Is(err, ErrClosed) {
464
465
466 t.Logf("error reading from pty: %v", err)
467 }
468 return
469 }
470 }
471 }()
472
473
474 defer client.Close()
475
476 _, err = io.Copy(tty, server)
477 if err != nil {
478 t.Fatal(err)
479 }
480 }
481
482 func testCopyFileRange(t *testing.T, size int64, limit int64) {
483 dst, src, data, hook := newCopyFileRangeTest(t, size)
484
485
486 var (
487 realsrc io.Reader
488 lr *io.LimitedReader
489 )
490 if limit >= 0 {
491 lr = &io.LimitedReader{N: limit, R: src}
492 realsrc = lr
493 if limit < int64(len(data)) {
494 data = data[:limit]
495 }
496 } else {
497 realsrc = src
498 }
499
500
501
502 n, err := io.Copy(dst, realsrc)
503 if err != nil {
504 t.Fatal(err)
505 }
506
507
508
509 if limit > 0 && !hook.called {
510 t.Fatal("never called poll.CopyFileRange")
511 }
512 if hook.called && hook.dstfd != int(dst.Fd()) {
513 t.Fatalf("wrong destination file descriptor: got %d, want %d", hook.dstfd, dst.Fd())
514 }
515 if hook.called && hook.srcfd != int(src.Fd()) {
516 t.Fatalf("wrong source file descriptor: got %d, want %d", hook.srcfd, src.Fd())
517 }
518
519
520
521
522 dstoff, err := dst.Seek(0, io.SeekCurrent)
523 if err != nil {
524 t.Fatal(err)
525 }
526 srcoff, err := src.Seek(0, io.SeekCurrent)
527 if err != nil {
528 t.Fatal(err)
529 }
530 if dstoff != srcoff {
531 t.Errorf("offsets differ: dstoff = %d, srcoff = %d", dstoff, srcoff)
532 }
533 if dstoff != int64(len(data)) {
534 t.Errorf("dstoff = %d, want %d", dstoff, len(data))
535 }
536 if n != int64(len(data)) {
537 t.Errorf("short ReadFrom: wrote %d bytes, want %d", n, len(data))
538 }
539 mustSeekStart(t, dst)
540 mustContainData(t, dst, data)
541
542
543 if lr != nil {
544 if want := limit - n; lr.N != want {
545 t.Fatalf("didn't update limit correctly: got %d, want %d", lr.N, want)
546 }
547 }
548 }
549
550
551
552
553
554
555 func newCopyFileRangeTest(t *testing.T, size int64) (dst, src *File, data []byte, hook *copyFileRangeHook) {
556 t.Helper()
557
558 hook = hookCopyFileRange(t)
559 tmp := t.TempDir()
560
561 src, err := Create(filepath.Join(tmp, "src"))
562 if err != nil {
563 t.Fatal(err)
564 }
565 t.Cleanup(func() { src.Close() })
566
567 dst, err = Create(filepath.Join(tmp, "dst"))
568 if err != nil {
569 t.Fatal(err)
570 }
571 t.Cleanup(func() { dst.Close() })
572
573
574
575 prng := rand.New(rand.NewSource(time.Now().Unix()))
576 data = make([]byte, size)
577 prng.Read(data)
578 if _, err := src.Write(data); err != nil {
579 t.Fatal(err)
580 }
581 if _, err := src.Seek(0, io.SeekStart); err != nil {
582 t.Fatal(err)
583 }
584
585 return dst, src, data, hook
586 }
587
588
589
590
591
592
593 func newSpliceFileTest(t *testing.T, proto string, size int64) (*File, net.Conn, []byte, *spliceFileHook, func()) {
594 t.Helper()
595
596 hook := hookSpliceFile(t)
597
598 client, server := createSocketPair(t, proto)
599
600 dst, err := CreateTemp(t.TempDir(), "dst-splice-file-test")
601 if err != nil {
602 t.Fatal(err)
603 }
604 t.Cleanup(func() { dst.Close() })
605
606 randSeed := time.Now().Unix()
607 t.Logf("random data seed: %d\n", randSeed)
608 prng := rand.New(rand.NewSource(randSeed))
609 data := make([]byte, size)
610 prng.Read(data)
611
612 done := make(chan struct{})
613 go func() {
614 client.Write(data)
615 client.Close()
616 close(done)
617 }()
618
619 return dst, server, data, hook, func() { <-done }
620 }
621
622
623
624 func mustContainData(t *testing.T, f *File, data []byte) {
625 t.Helper()
626
627 got := make([]byte, len(data))
628 if _, err := io.ReadFull(f, got); err != nil {
629 t.Fatal(err)
630 }
631 if !bytes.Equal(got, data) {
632 t.Fatalf("didn't get the same data back from %s", f.Name())
633 }
634 if _, err := f.Read(make([]byte, 1)); err != io.EOF {
635 t.Fatalf("not at EOF")
636 }
637 }
638
639 func mustSeekStart(t *testing.T, f *File) {
640 if _, err := f.Seek(0, io.SeekStart); err != nil {
641 t.Fatal(err)
642 }
643 }
644
645 func hookCopyFileRange(t *testing.T) *copyFileRangeHook {
646 h := new(copyFileRangeHook)
647 h.install()
648 t.Cleanup(h.uninstall)
649 return h
650 }
651
652 type copyFileRangeHook struct {
653 called bool
654 dstfd int
655 srcfd int
656 remain int64
657
658 written int64
659 handled bool
660 err error
661
662 original func(dst, src *poll.FD, remain int64) (int64, bool, error)
663 }
664
665 func (h *copyFileRangeHook) install() {
666 h.original = *PollCopyFileRangeP
667 *PollCopyFileRangeP = func(dst, src *poll.FD, remain int64) (int64, bool, error) {
668 h.called = true
669 h.dstfd = dst.Sysfd
670 h.srcfd = src.Sysfd
671 h.remain = remain
672 h.written, h.handled, h.err = h.original(dst, src, remain)
673 return h.written, h.handled, h.err
674 }
675 }
676
677 func (h *copyFileRangeHook) uninstall() {
678 *PollCopyFileRangeP = h.original
679 }
680
681 func hookSpliceFile(t *testing.T) *spliceFileHook {
682 h := new(spliceFileHook)
683 h.install()
684 t.Cleanup(h.uninstall)
685 return h
686 }
687
688 type spliceFileHook struct {
689 called bool
690 dstfd int
691 srcfd int
692 remain int64
693
694 written int64
695 handled bool
696 sc string
697 err error
698
699 original func(dst, src *poll.FD, remain int64) (int64, bool, string, error)
700 }
701
702 func (h *spliceFileHook) install() {
703 h.original = *PollSpliceFile
704 *PollSpliceFile = func(dst, src *poll.FD, remain int64) (int64, bool, string, error) {
705 h.called = true
706 h.dstfd = dst.Sysfd
707 h.srcfd = src.Sysfd
708 h.remain = remain
709 h.written, h.handled, h.sc, h.err = h.original(dst, src, remain)
710 return h.written, h.handled, h.sc, h.err
711 }
712 }
713
714 func (h *spliceFileHook) uninstall() {
715 *PollSpliceFile = h.original
716 }
717
718
719 func TestProcCopy(t *testing.T) {
720 t.Parallel()
721
722 const cmdlineFile = "/proc/self/cmdline"
723 cmdline, err := ReadFile(cmdlineFile)
724 if err != nil {
725 t.Skipf("can't read /proc file: %v", err)
726 }
727 in, err := Open(cmdlineFile)
728 if err != nil {
729 t.Fatal(err)
730 }
731 defer in.Close()
732 outFile := filepath.Join(t.TempDir(), "cmdline")
733 out, err := Create(outFile)
734 if err != nil {
735 t.Fatal(err)
736 }
737 if _, err := io.Copy(out, in); err != nil {
738 t.Fatal(err)
739 }
740 if err := out.Close(); err != nil {
741 t.Fatal(err)
742 }
743 copy, err := ReadFile(outFile)
744 if err != nil {
745 t.Fatal(err)
746 }
747 if !bytes.Equal(cmdline, copy) {
748 t.Errorf("copy of %q got %q want %q\n", cmdlineFile, copy, cmdline)
749 }
750 }
751
752 func TestGetPollFDAndNetwork(t *testing.T) {
753 t.Run("tcp4", func(t *testing.T) { testGetPollFDAndNetwork(t, "tcp4") })
754 t.Run("unix", func(t *testing.T) { testGetPollFDAndNetwork(t, "unix") })
755 }
756
757 func testGetPollFDAndNetwork(t *testing.T, proto string) {
758 _, server := createSocketPair(t, proto)
759 sc, ok := server.(syscall.Conn)
760 if !ok {
761 t.Fatalf("server Conn is not a syscall.Conn")
762 }
763 rc, err := sc.SyscallConn()
764 if err != nil {
765 t.Fatalf("server SyscallConn error: %v", err)
766 }
767 if err = rc.Control(func(fd uintptr) {
768 pfd, network := GetPollFDAndNetwork(server)
769 if pfd == nil {
770 t.Fatalf("GetPollFDAndNetwork didn't return poll.FD")
771 }
772 if string(network) != proto {
773 t.Fatalf("GetPollFDAndNetwork returned wrong network, got: %s, want: %s", network, proto)
774 }
775 if pfd.Sysfd != int(fd) {
776 t.Fatalf("GetPollFDAndNetwork returned wrong poll.FD, got: %d, want: %d", pfd.Sysfd, int(fd))
777 }
778 if !pfd.IsStream {
779 t.Fatalf("expected IsStream to be true")
780 }
781 if err = pfd.Init(proto, true); err == nil {
782 t.Fatalf("Init should have failed with the initialized poll.FD and return EEXIST error")
783 }
784 }); err != nil {
785 t.Fatalf("server Control error: %v", err)
786 }
787 }
788
789 func createSocketPair(t *testing.T, proto string) (client, server net.Conn) {
790 t.Helper()
791 if !nettest.TestableNetwork(proto) {
792 t.Skipf("%s does not support %q", runtime.GOOS, proto)
793 }
794
795 ln, err := nettest.NewLocalListener(proto)
796 if err != nil {
797 t.Fatalf("NewLocalListener error: %v", err)
798 }
799 t.Cleanup(func() {
800 if ln != nil {
801 ln.Close()
802 }
803 if client != nil {
804 client.Close()
805 }
806 if server != nil {
807 server.Close()
808 }
809 })
810 ch := make(chan struct{})
811 go func() {
812 var err error
813 server, err = ln.Accept()
814 if err != nil {
815 t.Errorf("Accept new connection error: %v", err)
816 }
817 ch <- struct{}{}
818 }()
819 client, err = net.Dial(proto, ln.Addr().String())
820 <-ch
821 if err != nil {
822 t.Fatalf("Dial new connection error: %v", err)
823 }
824 return client, server
825 }
826
View as plain text