package main import ( "io" "net/http" "net/http/httptest" "strings" "sync" "testing" ) // A gap in the log reads as nothing having happened, so a line that never // reaches the worker has to be counted and reported by one that does. The two // ways it fails are a send that errors and a send that is answered with an // error, and only the first of them looks like a failure to the client. func TestShipperReportsWhatDidNotLand(t *testing.T) { var mu sync.Mutex mode := "refuse" var landed []string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mu.Lock() defer mu.Unlock() switch mode { case "refuse": http.Error(w, "no", http.StatusInternalServerError) case "drop": // A send that errors rather than answering. if conn, _, err := w.(http.Hijacker).Hijack(); err == nil { conn.Close() } default: body, _ := io.ReadAll(r.Body) landed = append(landed, string(body)) w.WriteHeader(http.StatusNoContent) } })) defer srv.Close() s := newShipper(srv.URL) // close sends what is queued and waits, so each of these is one send. for _, step := range []struct{ mode, line string }{ {"refuse", "answered with a 500"}, {"drop", "the connection went away"}, {"take", "this one lands"}, } { mu.Lock() mode = step.mode mu.Unlock() if _, err := s.Write([]byte(step.line + "\n")); err != nil { t.Fatal(err) } s.close() } mu.Lock() defer mu.Unlock() if len(landed) != 1 { t.Fatalf("the worker was handed %d bodies, want the one it accepted: %q", len(landed), landed) } if !strings.Contains(landed[0], "this one lands") { t.Errorf("the line that should have landed is missing from %q", landed[0]) } if !strings.Contains(landed[0], "2 log lines did not reach the worker") { t.Errorf("the two lines that did not land are not reported in %q", landed[0]) } }