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