package ultrabeam import ( "bufio" "errors" "strings" "testing" "time" ) // Stop must not return while the poll loop is still alive: on a serial link the // loop owns the port, and a client that outlives its Stop is what turned every // later connection into "Serial port busy". func TestStopWaitsForPollLoop(t *testing.T) { // A transport that cannot connect, so the loop spends its life in open() and // the reconnect path — the state the real fault happened in. c := New(Transport{Mode: "tcp", Host: "127.0.0.1", Port: 1}) if err := c.Start(); err != nil { t.Fatal(err) } done := c.done time.Sleep(50 * time.Millisecond) c.Stop() select { case <-done: default: t.Fatal("Stop returned while the poll loop was still running") } // Stopping twice must not panic on the closed channel. c.Stop() } func TestPortBusyHint(t *testing.T) { // The Windows driver's own words, and the ones an operator has to act on. if h := portBusyHint("serial", "COM13", errors.New("Serial port busy")); !strings.Contains(h, "COM13") { t.Fatalf("no hint for a busy port: %q", h) } if h := portBusyHint("serial", "COM13", errors.New("Access is denied.")); h == "" { t.Fatal("no hint for access denied") } // A port that simply is not there is a different problem, and saying "another // program has it" would send the operator hunting for a program that is not // running. if h := portBusyHint("serial", "COM13", errors.New("The system cannot find the file specified.")); h != "" { t.Fatalf("hinted at a busy port for a missing one: %q", h) } if h := portBusyHint("tcp", "", errors.New("connection refused")); h != "" { t.Fatalf("serial hint on a TCP link: %q", h) } } // silentPort answers every read the way a serial port with nothing on the other // end does: no bytes, no error. bufio turns a run of those into ErrNoProgress. type silentPort struct{ writes int } func (s *silentPort) Read(p []byte) (int, error) { return 0, nil } func (s *silentPort) Write(p []byte) (int, error) { s.writes++; return len(p), nil } func (s *silentPort) Close() error { return nil } // A controller that never answers must fail ONCE, promptly, with a message that // says so — not wedge the poll loop for minutes inside bufio's retry budget. func TestSilentControllerFailsWithinTheReadTimeout(t *testing.T) { c := New(Transport{Mode: "serial", COM: "COM_TEST", Baud: 9600}) c.conn = &silentPort{} c.reader = bufio.NewReader(c.conn) start := time.Now() _, err := c.sendCommand(CMD_STATUS, nil) elapsed := time.Since(start) if err == nil { t.Fatal("a silent controller reported success") } if elapsed > 3*ubReadTimeout { t.Fatalf("took %s to give up — the read deadline is not bounding the exchange", elapsed.Round(time.Millisecond)) } if !strings.Contains(err.Error(), "no reply") { t.Fatalf("unhelpful error for a silent port: %v", err) } }