// SPDX-FileCopyrightText: 2026 Echolot contributors // SPDX-License-Identifier: GPL-3.0-or-later package selftest import ( "net" "strconv" "strings" "testing" ) // The real check runs against ports 80/443, which a test cannot bind without privileges; the // port list is what varies here, the mechanism is identical. func TestOccupiedPortIsDetected(t *testing.T) { // The stray-process scenario: someone else holds the port before we look. ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer ln.Close() port := strconv.Itoa(ln.Addr().(*net.TCPAddr).Port) occupied, unverifiable := portsFree([]net.IP{net.ParseIP("127.0.0.1")}, []string{port}) if len(occupied) != 1 || !strings.HasSuffix(occupied[0], ":"+port) { t.Fatalf("a listening port must be reported occupied, got occupied=%v unverifiable=%v", occupied, unverifiable) } } func TestFreePortPassesAndStaysFree(t *testing.T) { // Find a port that is free by construction, then check it. probe, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } port := strconv.Itoa(probe.Addr().(*net.TCPAddr).Port) probe.Close() occupied, unverifiable := portsFree([]net.IP{net.ParseIP("127.0.0.1")}, []string{port}) if len(occupied) != 0 || len(unverifiable) != 0 { t.Fatalf("a free port must pass silently, got occupied=%v unverifiable=%v", occupied, unverifiable) } // The check must not keep the port: it proves the state and gets out of the way. ln, err := net.Listen("tcp", "127.0.0.1:"+port) if err != nil { t.Fatalf("the check left the port unusable: %v", err) } ln.Close() } func TestUnassignedAddressIsUnverifiableNotOccupied(t *testing.T) { // 192.0.2.0/24 is TEST-NET-1: never assigned to this host, so the bind fails with something // other than EADDRINUSE. That is "could not answer", not "occupied" — conflating them would // refuse startup over a typo in ECHOLOT_RESERVED_ADDRS. occupied, unverifiable := portsFree([]net.IP{net.ParseIP("192.0.2.1")}, []string{"65001"}) if len(occupied) != 0 { t.Fatalf("an unassigned address must not be reported occupied: %v", occupied) } if len(unverifiable) != 1 { t.Fatalf("an unassigned address must be reported unverifiable, got %v", unverifiable) } }