package names import ( "context" "errors" "net" "net/netip" "sync/atomic" "testing" "time" ) func TestNames(t *testing.T) { var calls atomic.Int32 slow := make(chan struct{}) r := NewFunc(func(ctx context.Context, addr string) ([]string, error) { calls.Add(1) switch addr { case "10.0.0.1": return []string{"nas.example.net."}, nil case "10.0.0.2": return nil, &net.DNSError{Err: "no such host", Name: addr, IsNotFound: true} case "10.0.0.3": return nil, errors.New("server failure") case "10.0.0.4": <-slow return []string{"slow.example.net."}, nil } return nil, nil }, time.Hour) a := netip.MustParseAddr all := []netip.Addr{a("10.0.0.1"), a("10.0.0.2"), a("10.0.0.3"), a("10.0.0.4")} got := r.Names(context.Background(), all, 200*time.Millisecond) if len(got) != 1 || got[a("10.0.0.1")] != "nas.example.net" { t.Fatalf("first call: %v", got) } // The slow lookup finishes later and is there next time; the others // come from the cache, the failure too until it is retried. close(slow) time.Sleep(50 * time.Millisecond) got = r.Names(context.Background(), all, 200*time.Millisecond) if len(got) != 2 || got[a("10.0.0.4")] != "slow.example.net" { t.Fatalf("second call: %v", got) } if n := calls.Load(); n != 4 { t.Fatalf("%d lookups, want 4 (cached)", n) } // A failure is tried again after retry, a missing name after ttl. now := time.Now().Add(2 * time.Minute) r.now = func() time.Time { return now } r.Names(context.Background(), all, 200*time.Millisecond) if n := calls.Load(); n != 5 { t.Fatalf("%d lookups, want 5 (the failure again)", n) } }