| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package nettest_test |
| |
| import ( |
| "bytes" |
| "errors" |
| "internal/nettest" |
| "io" |
| "net" |
| "net/netip" |
| "os" |
| "testing" |
| "testing/synctest" |
| "time" |
| ) |
| |
| func TestPacketConnListenConflict(t *testing.T) { |
| synctest.Test(t, func(t *testing.T) { |
| addr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort("10.0.0.1:1000")) |
| pnet := nettest.NewPacketNet() |
| conn, err := pnet.NewConn(addr) |
| if err != nil { |
| t.Fatalf("with no existing listener, pnet.NewConn(%v) = %v; want success", addr, err) |
| } |
| _, err = pnet.NewConn(addr) |
| if err == nil { |
| t.Fatalf("with existing listener, pnet.NewConn(%v) = nil; want error", addr) |
| } |
| conn.Close() |
| _, err = pnet.NewConn(addr) |
| if err != nil { |
| t.Fatalf("after closing existing listener, pnet.NewConn(%v) = %v; want success", addr, err) |
| } |
| }) |
| } |
| |
| func TestPacketConnReadWrite(t *testing.T) { |
| synctest.Test(t, func(t *testing.T) { |
| pnet := nettest.NewPacketNet() |
| c1 := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| c2 := mustNewPacketConn(t, pnet, "10.0.0.2:2000") |
| c3 := mustNewPacketConn(t, pnet, "10.0.0.3:3000") |
| |
| wantPacketConnWriteTo(t, c1, []byte("1->3"), c3.LocalAddr()) |
| wantPacketConnWriteTo(t, c2, []byte("2->3"), c3.LocalAddr()) |
| wantPacketConnWriteTo(t, c3, []byte("3->1"), c1.LocalAddr()) |
| |
| wantPacketConnReadBytes(t, c1, []byte("3->1"), c3.LocalAddr()) |
| wantPacketConnReadBytes(t, c3, []byte("1->3"), c1.LocalAddr()) |
| wantPacketConnReadBytes(t, c3, []byte("2->3"), c2.LocalAddr()) |
| wantPacketConnReadBlocked(t, c1) |
| wantPacketConnReadBlocked(t, c2) |
| wantPacketConnReadBlocked(t, c3) |
| |
| // Write a packet into the void (no listener on this address). |
| wantPacketConnWriteTo(t, c1, []byte("1->lost"), net.UDPAddrFromAddrPort(netip.MustParseAddrPort("10.0.0.100:1000"))) |
| }) |
| } |
| |
| func TestPacketConnWriteAddressErrors(t *testing.T) { |
| t.Skip("TODO: figure out if these should be errors") |
| |
| synctest.Test(t, func(t *testing.T) { |
| pnet := nettest.NewPacketNet() |
| c4 := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| c6 := mustNewPacketConn(t, pnet, "[::1]:1000") |
| |
| wantPacketConnWriteErr(t, c4, c6.LocalAddr(), anyError) // IPv4 -> IPv6 |
| wantPacketConnWriteErr(t, c6, c4.LocalAddr(), anyError) // IPv6 -> IPv4 |
| |
| // Not a *net.UDPAddr. |
| wantPacketConnWriteErr(t, c4, net.UDPAddrFromAddrPort(netip.MustParseAddrPort("10.0.0.1:1000")), anyError) |
| }) |
| } |
| |
| func TestPacketConnClose(t *testing.T) { |
| synctest.Test(t, func(t *testing.T) { |
| pnet := nettest.NewPacketNet() |
| pconn := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| |
| wantPacketConnWriteTo(t, pconn, []byte("hello"), pconn.LocalAddr()) |
| |
| if err := pconn.Close(); err != nil { |
| t.Errorf("pconn.Close() = %v, want success", err) |
| } |
| if err := pconn.Close(); !isOpError(err, net.ErrClosed) { |
| t.Errorf("pconn.Close() = %v, want ErrClosed", err) |
| } |
| |
| wantPacketConnReadErr(t, pconn, net.ErrClosed) |
| wantPacketConnWriteErr(t, pconn, pconn.LocalAddr(), net.ErrClosed) |
| }) |
| } |
| |
| func TestPacketConnReadDeadline(t *testing.T) { |
| for _, setDeadline := range []struct { |
| name string |
| f func(*nettest.PacketConn, time.Time) error |
| }{{ |
| name: "SetDeadline", |
| f: (*nettest.PacketConn).SetDeadline, |
| }, { |
| name: "SetReadDeadline", |
| f: (*nettest.PacketConn).SetReadDeadline, |
| }} { |
| t.Run(setDeadline.name, func(t *testing.T) { |
| testDeadline(t, func() deadlineTest { |
| pnet := nettest.NewPacketNet() |
| rconn := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| wconn := mustNewPacketConn(t, pnet, "10.0.0.2:2000") |
| return deadlineTest{ |
| what: "ReadFrom()", |
| block: func() error { |
| _, _, err := rconn.ReadFrom(make([]byte, 1)) |
| return err |
| }, |
| unblock: func() { |
| wconn.WriteTo([]byte("x"), rconn.LocalAddr()) |
| }, |
| setDeadline: func(d time.Duration) { |
| setDeadline.f(rconn, time.Now().Add(d)) |
| }, |
| } |
| }) |
| }) |
| } |
| } |
| |
| func TestPacketConnWriteDeadline(t *testing.T) { |
| synctest.Test(t, func(t *testing.T) { |
| pnet := nettest.NewPacketNet() |
| rconn := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| wconn := mustNewPacketConn(t, pnet, "10.0.0.2:2000") |
| |
| // This does nothing, even though the deadline has expired. |
| wconn.SetWriteDeadline(time.Now().Add(-1 * time.Second)) |
| |
| wantPacketConnWriteTo(t, wconn, []byte("data"), rconn.LocalAddr()) |
| wantPacketConnReadBytes(t, rconn, []byte("data"), wconn.LocalAddr()) |
| }) |
| } |
| |
| func TestPacketConnSetReadError(t *testing.T) { |
| synctest.Test(t, func(t *testing.T) { |
| wantErr := errors.New("error") |
| pnet := nettest.NewPacketNet() |
| rconn := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| wconn := mustNewPacketConn(t, pnet, "10.0.0.2:2000") |
| rconn.SetReadError(wantErr) |
| |
| // Consume buffer before returning error. |
| wantPacketConnWriteTo(t, wconn, []byte("one"), rconn.LocalAddr()) |
| wantPacketConnReadBytes(t, rconn, []byte("one"), wconn.LocalAddr()) |
| wantPacketConnReadErr(t, rconn, wantErr) |
| |
| // Write more, suppressing error until buffer drains again. |
| wantPacketConnWriteTo(t, wconn, []byte("two"), rconn.LocalAddr()) |
| wantPacketConnReadBytes(t, rconn, []byte("two"), wconn.LocalAddr()) |
| wantPacketConnReadErr(t, rconn, wantErr) |
| |
| // Error may be cleared. |
| rconn.SetReadError(nil) |
| wantPacketConnReadBlocked(t, rconn) |
| |
| // ErrClosed takes precedence over read error. |
| rconn.SetReadError(wantErr) |
| rconn.Close() |
| wantPacketConnReadErr(t, rconn, net.ErrClosed) |
| }) |
| } |
| |
| func TestPacketConnSetWriteError(t *testing.T) { |
| synctest.Test(t, func(t *testing.T) { |
| wantErr := errors.New("error") |
| pnet := nettest.NewPacketNet() |
| rconn := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| wconn := mustNewPacketConn(t, pnet, "10.0.0.2:2000") |
| wconn.SetWriteError(wantErr) |
| |
| // Error blocks writes. |
| wantPacketConnWriteErr(t, wconn, rconn.LocalAddr(), wantErr) |
| wantPacketConnReadBlocked(t, rconn) |
| |
| // Error may be cleared. |
| wconn.SetWriteError(nil) |
| wantPacketConnWriteTo(t, wconn, []byte("one"), rconn.LocalAddr()) |
| |
| // Restoring error does not prevent reading buffered data. |
| wconn.SetWriteError(wantErr) |
| wantPacketConnWriteErr(t, wconn, rconn.LocalAddr(), wantErr) |
| wantPacketConnReadBytes(t, rconn, []byte("one"), wconn.LocalAddr()) |
| |
| // Error does not interfere with closing the conn. |
| wconn.Close() |
| wantPacketConnWriteErr(t, wconn, rconn.LocalAddr(), net.ErrClosed) |
| }) |
| } |
| |
| func TestPacketConnSetCloseError(t *testing.T) { |
| synctest.Test(t, func(t *testing.T) { |
| wantErr := errors.New("error") |
| pnet := nettest.NewPacketNet() |
| rconn := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| wconn := mustNewPacketConn(t, pnet, "10.0.0.2:2000") |
| wconn.SetCloseError(wantErr) |
| |
| wantPacketConnWriteTo(t, wconn, []byte("one"), rconn.LocalAddr()) |
| if err := wconn.Close(); !isOpError(err, wantErr) { |
| t.Fatalf("wconn.Close = %v, want OpError{Err: %v}", err, wantErr) |
| } |
| if err := wconn.Close(); !isOpError(err, net.ErrClosed) { |
| t.Fatalf("wconn.Close = %v, want OpError{Err: net.ErrClosed}", err) |
| } |
| wantPacketConnReadBytes(t, rconn, []byte("one"), wconn.LocalAddr()) |
| }) |
| } |
| |
| func TestPacketConnCanRead(t *testing.T) { |
| synctest.Test(t, func(t *testing.T) { |
| pnet := nettest.NewPacketNet() |
| rconn := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| wconn := mustNewPacketConn(t, pnet, "10.0.0.2:2000") |
| if got, want := rconn.CanRead(), false; got != want { |
| t.Fatalf("before writing data: rconn.CanRead() = %v, want %v", got, want) |
| } |
| wconn.WriteTo([]byte("a"), rconn.LocalAddr()) |
| if got, want := rconn.CanRead(), true; got != want { |
| t.Fatalf("after writing data: rconn.CanRead() = %v, want %v", got, want) |
| } |
| rconn.ReadFrom(make([]byte, 1)) |
| if got, want := rconn.CanRead(), false; got != want { |
| t.Fatalf("after reading data: rconn.CanRead() = %v, want %v", got, want) |
| } |
| }) |
| } |
| |
| func TestPacketConnIsClosed(t *testing.T) { |
| synctest.Test(t, func(t *testing.T) { |
| pnet := nettest.NewPacketNet() |
| conn := mustNewPacketConn(t, pnet, "10.0.0.1:1000") |
| if got, want := conn.IsClosed(), false; got != want { |
| t.Fatalf("before closing: conn.IsClosed() = %v, want %v", got, want) |
| } |
| conn.Close() |
| if got, want := conn.IsClosed(), true; got != want { |
| t.Fatalf("after closing: conn.IsClosed() = %v, want %v", got, want) |
| } |
| }) |
| } |
| |
| func mustNewPacketConn(t *testing.T, pnet *nettest.PacketNet, addr string) *nettest.PacketConn { |
| t.Helper() |
| c, err := pnet.NewConn(net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addr))) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return c |
| } |
| |
| func wantPacketConnWriteTo(t *testing.T, c *nettest.PacketConn, b []byte, dst net.Addr) { |
| t.Helper() |
| if n, err := c.WriteTo(b, dst); n != len(b) || err != nil { |
| t.Fatalf("conn.WriteTo(%q, %v) = %v, %v; want %v, nil", b, dst, n, err, len(b)) |
| } |
| } |
| |
| func wantPacketConnWriteErr(t *testing.T, c *nettest.PacketConn, dst net.Addr, want error) { |
| t.Helper() |
| n, err := c.WriteTo(make([]byte, 1), dst) |
| if n != 0 || !isOpError(err, want) { |
| t.Fatalf("c.WriteTo() = %v, %v; want 0, OpError{Err: %q}", n, err, want) |
| } |
| } |
| |
| func wantPacketConnReadBytes(t *testing.T, c *nettest.PacketConn, want []byte, wantAddr net.Addr) { |
| t.Helper() |
| udpWantAddr, ok := wantAddr.(*net.UDPAddr) |
| if !ok { |
| t.Fatalf("wantAddr is %T, should be *net.UDPAddr", wantAddr) |
| } |
| got := make([]byte, len(want)+1) |
| n, addr, err := c.ReadFrom(got) |
| got = got[:n] |
| udpAddr, addrOK := addr.(*net.UDPAddr) |
| if n != len(want) || !addrOK || udpAddr.AddrPort() != udpWantAddr.AddrPort() { |
| t.Fatalf("conn.ReadFrom() = %v, %v, %v; want %v, %v, nil", n, addr, err, len(want), wantAddr) |
| } |
| if !bytes.Equal(got, want) { |
| t.Fatalf("conn.ReadFrom() read %q, want %q", got, want) |
| } |
| } |
| |
| func wantPacketConnReadErr(t *testing.T, c *nettest.PacketConn, want error) { |
| t.Helper() |
| n, addr, err := c.ReadFrom(make([]byte, 1)) |
| if want == io.EOF { |
| if n != 0 || err != io.EOF { |
| t.Fatalf("c.ReadFrom() = %v, %v, %v; want 0, nil, io.EOF", n, addr, err) |
| } |
| } else { |
| if n != 0 || !isOpError(err, want) { |
| t.Fatalf("c.ReadFrom() = %v, %v, %v; want 0, nil, OpError{Err: %q}", n, addr, err, want) |
| } |
| } |
| } |
| |
| func wantPacketConnReadBlocked(t *testing.T, c *nettest.PacketConn) { |
| done := false |
| go func() { |
| n, addr, err := c.ReadFrom(make([]byte, 1)) |
| if n != 0 || !errors.Is(err, os.ErrDeadlineExceeded) { |
| t.Errorf("c.Read() = %v, %v, %v; want 0, nil, ErrDeadlineExceeded", n, addr, err) |
| } |
| done = true |
| }() |
| synctest.Wait() |
| if done { |
| t.Fatalf("ReadFrom unexpectedly returned before setting deadline") |
| } |
| c.SetReadDeadline(time.Now().Add(-1 * time.Second)) |
| synctest.Wait() |
| c.SetReadDeadline(time.Time{}) |
| if !done { |
| t.Fatalf("ReadFrom unexpectedly did not return after setting deadline") |
| } |
| } |