package nettrace import ( "fmt" "io" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" ) func getValues(t *testing.T, vs url.Values, code int) string { t.Helper() req := httptest.NewRequest("GET", "/debug/traces?"+vs.Encode(), nil) w := httptest.NewRecorder() RenderTraces(w, req) resp := w.Result() body, _ := io.ReadAll(resp.Body) if resp.StatusCode != code { t.Errorf("expected %d, got %v", code, resp) } return string(body) } type v struct { fam, b, lat, trace, ref, all string } func getCode(t *testing.T, vs v, code int) string { t.Helper() u := url.Values{} if vs.fam != "" { u.Add("fam", vs.fam) } if vs.b != "" { u.Add("b", vs.b) } if vs.lat != "" { u.Add("lat", vs.lat) } if vs.trace != "" { u.Add("trace", vs.trace) } if vs.ref != "" { u.Add("ref", vs.ref) } if vs.all != "" { u.Add("all", vs.all) } return getValues(t, u, code) } func get(t *testing.T, fam, b, lat, trace, ref, all string) string { t.Helper() return getCode(t, v{fam, b, lat, trace, ref, all}, 200) } func getErr(t *testing.T, fam, b, lat, trace, ref, all string, code int, err string) string { t.Helper() body := getCode(t, v{fam, b, lat, trace, ref, all}, code) if !strings.Contains(body, err) { t.Errorf("Body does not contain error message %q", err) t.Logf("Body: %v", body) } return body } func checkContains(t *testing.T, body, s string) { t.Helper() if !strings.Contains(body, s) { t.Errorf("Body does not contain %q", s) t.Logf("Body: %v", body) } } func TestHTTP(t *testing.T) { tr := New("TestHTTP", "http") tr.Printf("entry #1") tr.Finish() tr = New("TestHTTP", "http") tr.Printf("entry #2") tr.Finish() tr = New("TestHTTP", "http") tr.Errorf("entry #3 (error)") tr.Finish() tr = New("TestHTTP", "http") tr.Printf("hola marola") tr.Printf("entry #4") // This one is active until the end. defer tr.Finish() // Get the plain index. body := get(t, "", "", "", "", "", "") checkContains(t, body, "TestHTTP") // Get a specific family, but no bucket. body = get(t, "TestHTTP", "", "", "", "", "") checkContains(t, body, "TestHTTP") // Get a family and active bucket. body = get(t, "TestHTTP", "-1", "", "", "", "") checkContains(t, body, "hola marola") // Get a family and error bucket. body = get(t, "TestHTTP", "-2", "", "", "", "") checkContains(t, body, "entry #3 (error)") // Get a family and first bucket. body = get(t, "TestHTTP", "0", "", "", "", "") checkContains(t, body, "entry #2") // Latency view. There are 3 events because the 4th is active. body = get(t, "TestHTTP", "", "lat", "", "", "") checkContains(t, body, "Count: 3") // Get a specific trace. No family given, since it shouldn't be needed (we // take it from the id). body = get(t, "", "", "", string(tr.(*trace).ID), "", "") checkContains(t, body, "hola marola") // Check the "all=true" views. body = get(t, "TestHTTP", "0", "", "", "", "true") checkContains(t, body, "entry #2") checkContains(t, body, "?fam=TestHTTP&b=-2&all=true") tr.Finish() } func TestHTTPLong(t *testing.T) { // Test a long trace. tr := New("TestHTTPLong", "verbose") for i := 0; i < 1000; i++ { tr.Printf("entry #%d", i) } tr.Finish() get(t, "TestHTTPLong", "", "", string(tr.(*trace).ID), "", "") } func TestHTTPErrors(t *testing.T) { tr := New("TestHTTPErrors", "http") tr.Printf("entry #1") tr.Finish() // Unknown family. getErr(t, "unkfamily", "", "", "", "", "", 404, "Unknown family") // Invalid bucket. getErr(t, "TestHTTPErrors", "abc", "", "", "", "", 400, "Invalid bucket") getErr(t, "TestHTTPErrors", "-3", "", "", "", "", 400, "Invalid bucket") getErr(t, "TestHTTPErrors", "9", "", "", "", "", 400, "Invalid bucket") // Unknown trace id (malformed). getErr(t, "TestHTTPErrors", "", "", "unktrace", "", "", 404, "Trace not found") // Unknown trace id. getErr(t, "TestHTTPErrors", "", "", string(tr.(*trace).ID)+"xxx", "", "", 404, "Trace not found") // Check that the trace is actually there. get(t, "", "", "", string(tr.(*trace).ID), "", "") } func TestHTTPUroboro(t *testing.T) { trA := New("TestHTTPUroboro", "trA") defer trA.Finish() trA.Printf("this is trace A") trB := New("TestHTTPUroboro", "trB") defer trB.Finish() trB.Printf("this is trace B") trA.Link(trB, "B is my friend") trB.Link(trA, "A is my friend") // Check that we handle cross-linked events well. get(t, "TestHTTPUroboro", "", "", "", "", "") get(t, "TestHTTPUroboro", "-1", "", "", "", "") get(t, "", "", "", string(trA.(*trace).ID), "", "") get(t, "", "", "", string(trB.(*trace).ID), "", "") } func TestHTTPDeep(t *testing.T) { tr := New("TestHTTPDeep", "level-0") defer tr.Finish() ts := []Trace{tr} for i := 1; i <= 9; i++ { tr = tr.NewChild("TestHTTPDeep", fmt.Sprintf("level-%d", i)) defer tr.Finish() ts = append(ts, tr) } // Active view. body := get(t, "TestHTTPDeep", "-1", "", "", "", "") checkContains(t, body, "level-9") // Recursive view. body = get(t, "TestHTTPDeep", "", "", string(ts[0].(*trace).ID), "", "") checkContains(t, body, "level-9") } func TestStripZeros(t *testing.T) { cases := []struct { d time.Duration exp string }{ {0 * time.Second, " . 0"}, {1 * time.Millisecond, " . 1000"}, {5 * time.Millisecond, " . 5000"}, {1 * time.Second, "1.000000"}, {1*time.Second + 8*time.Millisecond, "1.008000"}, } for _, c := range cases { if got := stripZeros(c.d); got != c.exp { t.Errorf("stripZeros(%s) got %q, expected %q", c.d, got, c.exp) } } } func TestRegisterHandler(t *testing.T) { mux := http.NewServeMux() RegisterHandler(mux) req := httptest.NewRequest("GET", "/debug/traces", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) resp := w.Result() if resp.StatusCode != 200 { t.Errorf("expected 200, got %v", resp) } body, _ := io.ReadAll(resp.Body) if !strings.Contains(string(body), "

Traces

") { t.Errorf("unexpected body: %s", body) } }