package rigctld import ( "fmt" "net" "strings" "sync" "testing" "time" ) // A listener that binds successfully is not necessarily the one clients reach. // Windows lets a second program bind the same port on the specific address // 127.0.0.1 while ours holds 0.0.0.0, and localhost connections then go to the // more specific one. Seen with Nexus, which starts its own rigctld on 4532. func TestSelfTestWarnsWhenAnotherProgramHoldsLocalhost(t *testing.T) { // Squat 127.0.0.1 first, the way the other program does. squat, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Skipf("cannot bind localhost here: %v", err) } defer squat.Close() port := squat.Addr().(*net.TCPAddr).Port go func() { for { c, err := squat.Accept() if err != nil { return } c.Close() } }() var mu sync.Mutex var lines []string s := New(port, nil, func(f string, a ...any) { mu.Lock() lines = append(lines, fmt.Sprintf(f, a...)) mu.Unlock() }) if err := s.Start(); err != nil { // The wildcard bind is refused on some setups; nothing to prove then. t.Skipf("wildcard bind refused: %v", err) } defer s.Stop() deadline := time.Now().Add(4 * time.Second) for time.Now().Before(deadline) { mu.Lock() got := strings.Join(lines, "\n") mu.Unlock() if strings.Contains(got, "another program is already answering") { return } time.Sleep(50 * time.Millisecond) } mu.Lock() defer mu.Unlock() t.Errorf("no warning was logged while another listener owned localhost:%d.\nlog was:\n%s", port, strings.Join(lines, "\n")) } // The healthy case must stay silent: a warning on every clean start would be // noise, and noise in a log is what makes the real line easy to miss. func TestSelfTestIsSilentWhenReachable(t *testing.T) { // A real, free port: with 0 the OS picks one but s.port stays 0, so the probe // would dial 127.0.0.1:0 and prove nothing. probe, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Skipf("cannot bind: %v", err) } port := probe.Addr().(*net.TCPAddr).Port probe.Close() var mu sync.Mutex var lines []string s := New(port, nil, func(f string, a ...any) { mu.Lock() lines = append(lines, fmt.Sprintf(f, a...)) mu.Unlock() }) if err := s.Start(); err != nil { t.Skipf("start: %v", err) } defer s.Stop() time.Sleep(1500 * time.Millisecond) mu.Lock() defer mu.Unlock() for _, l := range lines { if strings.Contains(l, "WARNING") { t.Errorf("a reachable port still warned: %q", l) } } }