// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT package nfsclient import ( "net" "testing" "time" "sourcedock.dev/petrbalvin/nfs/internal/nfs4" ) // A server that accepts and never answers: every call fails at its // deadline instead of hanging the process. func TestCallTimeout(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer ln.Close() go func() { for { conn, err := ln.Accept() if err != nil { return } defer conn.Close() } }() cl, err := Dial(ln.Addr().String()) if err != nil { t.Fatal(err) } defer cl.Close() cl.Timeout = 40 * time.Millisecond start := time.Now() if _, _, err := cl.call(nfs4.ProcNull, nil); err == nil { t.Fatal("a silent server answered") } if elapsed := time.Since(start); elapsed > 2*time.Second { t.Fatalf("the call took %s, the deadline is 40ms", elapsed) } } // A connection that dies mid call: the waiting caller reports the // loss with the cause the reader saw. func TestConnectionLost(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer ln.Close() accepted := make(chan net.Conn, 1) go func() { conn, err := ln.Accept() if err != nil { return } accepted <- conn }() cl, err := Dial(ln.Addr().String()) if err != nil { t.Fatal(err) } defer cl.Close() cl.Timeout = 5 * time.Second done := make(chan error, 1) go func() { _, _, err := cl.call(nfs4.ProcNull, nil) done <- err }() // The server side of the connection dies: the reader routes the // failure to the waiting call. var server net.Conn select { case server = <-accepted: case <-time.After(3 * time.Second): t.Fatal("the server side never saw the connection") } server.Close() select { case err := <-done: if err == nil { t.Fatal("a lost connection answered") } case <-time.After(3 * time.Second): t.Fatal("the call never noticed the lost connection") } }