package nettrace import ( "fmt" "strings" "testing" "time" ) func expectEvents(t *testing.T, tr Trace, n int) { t.Helper() if evts := tr.(*trace).Events(); len(evts) != n { t.Errorf("expected %d events, got %d", n, len(evts)) t.Logf("%v", evts) } } func TestBasic(t *testing.T) { var tr Trace = New("TestBasic", "basic") defer tr.Finish() tr.Printf("hola marola") tr.Printf("hola marola 2") c1 := tr.NewChild("TestBasic", "basic child") c1.Printf("hijo de la noche") expectEvents(t, tr, 3) if s := tr.(*trace).String(); !strings.Contains(s, "TestBasic, basic") { t.Errorf("tr.String does not contain family and title: %q", s) } } func TestLong(t *testing.T) { tr := New("TestLong", "long") defer tr.Finish() tr.SetMaxEvents(100) // First 90 events, no drop. for i := 0; i < 90; i++ { tr.Printf("evt %d", i) } expectEvents(t, tr, 90) // Up to 99, still no drop. for i := 0; i < 9; i++ { tr.Printf("evt %d", i) } expectEvents(t, tr, 99) // Note that we go up to 101 due to rounding errors, we're ok with it. tr.Printf("evt 100") expectEvents(t, tr, 100) tr.Printf("evt 101") expectEvents(t, tr, 101) tr.Printf("evt 102") expectEvents(t, tr, 101) // Add more events, expect none of them to exceed 101. for i := 0; i < 9; i++ { tr.Printf("evt %d", i) expectEvents(t, tr, 101) } } func TestIsError(t *testing.T) { tr := New("TestIsError", "long") defer tr.Finish() if tr.(*trace).IsError() != false { tr.Errorf("new trace is error") } tr.Errorf("this is an error") if tr.(*trace).IsError() != true { tr.Errorf("error not recorded properly") } } func TestFindViaRef(t *testing.T) { parent := New("TestFindViaRef", "parent") parentID := parent.(*trace).ID defer parent.Finish() child := parent.NewChild("TestFindViaRef", "child") childID := child.(*trace).ID defer child.Finish() // Basic check that both can be directly found. if f := findInFamilies(parentID, id("")); f != parent { t.Errorf("didn't find parent directly, found: %v", f) } if f := findInFamilies(childID, id("")); f != child { t.Errorf("didn't find child directly, found: %v", f) } // Hackily remove child from the active traces, to force a reference // lookup when needed. familiesMu.Lock() delete(families["TestFindViaRef"].active, child.(*trace).ID) familiesMu.Unlock() // Now the child should not be findable directly anymore. if f := findInFamilies(childID, id("")); f != nil { t.Errorf("child should not be findable directly, found: %v", f) } // But we still should be able to get to it via the parent. if f := findInFamilies(childID, parentID); f != child { t.Errorf("didn't find child via parent, found: %v", f) } } func TestMaxEvents(t *testing.T) { tr := trace{} // Test that we keep a minimum, and that the cutoff behaves as expected. cases := []struct{ me, exp, cutoffExp int }{ {0, 6, 2}, {5, 6, 2}, {6, 6, 2}, {7, 7, 2}, {8, 8, 2}, {9, 9, 3}, {12, 12, 4}, } for _, c := range cases { tr.SetMaxEvents(c.me) if got := tr.maxEvents; got != c.exp { t.Errorf("set max events to %d, expected %d, got %d", c.me, c.exp, got) } if got := tr.cutoff; got != c.cutoffExp { t.Errorf("set max events to %d, expected cutoff %d, got %d", c.me, c.cutoffExp, got) } } } func TestFind(t *testing.T) { // Make sure we find the right bucket, including for latencies above the // last one. for i, d := range buckets { found := findBucket(d + 1*time.Millisecond) if found != i { t.Errorf("find bucket [%s + 1ms] got %d, expected %d", d, found, i) } } // Create a family, populate it with traces in all buckets. finished := [nBuckets]*trace{} for i, d := range buckets { lat := d + 1*time.Millisecond tr := newTrace("TestFind", fmt.Sprintf("evt-%s", lat)) tr.end = tr.Start.Add(lat) families[tr.Family].finalize(tr) finished[i] = tr } // Also have an active trace. activeTr := newTrace("TestFind", "active") // And add an error trace, which isn't on any of the other buckets (to // simulate that they've been rotated out of the latency buckets, but are // still around in errors) errTr := newTrace("TestFind", "evt-err") errTr.end = errTr.Start.Add(666 * time.Millisecond) errTr.SetError() delete(families[errTr.Family].active, errTr.ID) families[errTr.Family].errors.Add(errTr) // Find all of them. for i := range buckets { found := findInFamilies(finished[i].ID, "") if found != finished[i] { t.Errorf("finding trace %d on bucket %s, expected %v, got %v", i, buckets[i], finished[i], found) } } if found := findInFamilies(activeTr.ID, ""); found != activeTr { t.Errorf("finding active trace, expected %v, got %v", activeTr, found) } if found := findInFamilies(errTr.ID, ""); found != errTr { t.Errorf("finding error trace, expected %v, got %v", errTr, found) } // Non-existent traces. if found := findInFamilies("does!notexist", ""); found != nil { t.Errorf("finding non-existent trace, expected nil, got %v", found) } if found := findInFamilies("does!notexist", "does!notexist"); found != nil { t.Errorf("finding non-existent trace w/ref, expected nil, got %v", found) } } func TestFindParent(t *testing.T) { // Direct parent finding. // If the ref is the parent, we should find it even if the target trace // isn't known to the family (e.g. the child is there, but the parent has // been rotated and is no longer referenced). parent := newTrace("TestFindParent", "parent") child := parent.NewChild("TestFindParent", "child").(*trace) // Remove the parent from the active list. delete(families[parent.Family].active, parent.ID) if found := findInFamilies(parent.ID, ""); found != nil { t.Errorf("finding parent without ref, expected nil, got %v", found) } if found := findInFamilies(parent.ID, child.ID); found != parent { t.Errorf("finding parent with ref, expected %v, got %v", parent, found) } }