| // Copyright 2019 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // TODO(austin): All of these tests are skipped if the debuglog build | |
| // tag isn't provided. That means we basically never test debuglog. | |
| // There are two potential ways around this: | |
| // | |
| // 1. Make these tests re-build the runtime test with the debuglog | |
| // build tag and re-invoke themselves. | |
| // | |
| // 2. Always build the whole debuglog infrastructure and depend on | |
| // linker dead-code elimination to drop it. This is easy for dlog() | |
| // since there won't be any calls to it. For printDebugLog, we can | |
| // make panic call a wrapper that is call printDebugLog if the | |
| // debuglog build tag is set, or otherwise do nothing. Then tests | |
| // could call printDebugLog directly. This is the right answer in | |
| // principle, but currently our linker reads in all symbols | |
| // regardless, so this would slow down and bloat all links. If the | |
| // linker gets more efficient about this, we should revisit this | |
| // approach. | |
| package runtime_test | |
| import ( | |
| "fmt" | |
| "regexp" | |
| "runtime" | |
| "strings" | |
| "sync" | |
| "testing" | |
| ) | |
| func skipDebugLog(t *testing.T) { | |
| if runtime.DlogEnabled { | |
| t.Skip("debug log tests disabled to avoid collisions with real debug logs") | |
| } | |
| } | |
| func dlogCanonicalize(x string) string { | |
| begin := regexp.MustCompile(`(?m)^>> begin log \d+ <<\n`) | |
| x = begin.ReplaceAllString(x, "") | |
| prefix := regexp.MustCompile(`(?m)^\[[^]]+\]`) | |
| x = prefix.ReplaceAllString(x, "[]") | |
| return x | |
| } | |
| func TestDebugLog(t *testing.T) { | |
| skipDebugLog(t) | |
| runtime.ResetDebugLog() | |
| runtime.Dlog().S("testing").End() | |
| got := dlogCanonicalize(runtime.DumpDebugLog()) | |
| if want := "[] testing\n"; got != want { | |
| t.Fatalf("want %q, got %q", want, got) | |
| } | |
| } | |
| func TestDebugLogTypes(t *testing.T) { | |
| skipDebugLog(t) | |
| runtime.ResetDebugLog() | |
| var varString = strings.Repeat("a", 4) | |
| runtime.Dlog().B(true).B(false).I(-42).I16(0x7fff).U64(^uint64(0)).Hex(0xfff).P(nil).S(varString).S("const string").End() | |
| got := dlogCanonicalize(runtime.DumpDebugLog()) | |
| if want := "[] true false -42 32767 18446744073709551615 0xfff 0x0 aaaa const string\n"; got != want { | |
| t.Fatalf("want %q, got %q", want, got) | |
| } | |
| } | |
| func TestDebugLogSym(t *testing.T) { | |
| skipDebugLog(t) | |
| runtime.ResetDebugLog() | |
| pc, _, _, _ := runtime.Caller(0) | |
| runtime.Dlog().PC(pc).End() | |
| got := dlogCanonicalize(runtime.DumpDebugLog()) | |
| want := regexp.MustCompile(`\[\] 0x[0-9a-f]+ \[runtime_test\.TestDebugLogSym\+0x[0-9a-f]+ .*/debuglog_test\.go:[0-9]+\]\n`) | |
| if !want.MatchString(got) { | |
| t.Fatalf("want matching %s, got %q", want, got) | |
| } | |
| } | |
| func TestDebugLogInterleaving(t *testing.T) { | |
| skipDebugLog(t) | |
| runtime.ResetDebugLog() | |
| n1 := runtime.CountDebugLog() | |
| t.Logf("number of log shards at start: %d", n1) | |
| const limit = 1000 | |
| const concurrency = 10 | |
| // Start several goroutines writing to the log simultaneously. | |
| var wg sync.WaitGroup | |
| i := 0 | |
| chans := make([]chan bool, concurrency) | |
| for gid := range concurrency { | |
| chans[gid] = make(chan bool) | |
| wg.Add(1) | |
| go func() { | |
| defer wg.Done() | |
| var log *runtime.Dlogger | |
| for { | |
| <-chans[gid] | |
| if log != nil { | |
| log.End() | |
| } | |
| next := chans[(gid+1)%len(chans)] | |
| if i >= limit { | |
| close(next) | |
| break | |
| } | |
| // Log an entry, but *don't* release the log shard until its our | |
| // turn again. This should result in at least n=concurrency log | |
| // shards. | |
| log = runtime.Dlog().I(i) | |
| i++ | |
| // Wake up the next logger goroutine. | |
| next <- true | |
| } | |
| }() | |
| } | |
| // Start the chain reaction. | |
| chans[0] <- true | |
| // Wait for them to finish and get the log. | |
| wg.Wait() | |
| gotFull := runtime.DumpDebugLog() | |
| got := dlogCanonicalize(gotFull) | |
| n2 := runtime.CountDebugLog() | |
| t.Logf("number of log shards at end: %d", n2) | |
| if n2 < concurrency { | |
| t.Errorf("created %d log shards, expected >= %d", n2, concurrency) | |
| } | |
| // Construct the desired output. | |
| var want strings.Builder | |
| for i := 0; i < limit; i++ { | |
| fmt.Fprintf(&want, "[] %d\n", i) | |
| } | |
| if got != want.String() { | |
| // Since the timestamps are useful in understand | |
| // failures of this test, we print the uncanonicalized | |
| // output. | |
| t.Fatalf("want %q, got (uncanonicalized) %q", want.String(), gotFull) | |
| } | |
| } | |
| func TestDebugLogWraparound(t *testing.T) { | |
| skipDebugLog(t) | |
| // Make sure we don't switch logs so it's easier to fill one up. | |
| runtime.LockOSThread() | |
| defer runtime.UnlockOSThread() | |
| runtime.ResetDebugLog() | |
| var longString = strings.Repeat("a", 128) | |
| var want strings.Builder | |
| for i, j := 0, 0; j < 2*runtime.DebugLogBytes; i, j = i+1, j+len(longString) { | |
| runtime.Dlog().I(i).S(longString).End() | |
| fmt.Fprintf(&want, "[] %d %s\n", i, longString) | |
| } | |
| log := runtime.DumpDebugLog() | |
| // Check for "lost" message. | |
| lost := regexp.MustCompile(`^>> begin log \d+; lost first \d+KB <<\n`) | |
| if !lost.MatchString(log) { | |
| t.Fatalf("want matching %s, got %q", lost, log) | |
| } | |
| idx := lost.FindStringIndex(log) | |
| // Strip lost message. | |
| log = dlogCanonicalize(log[idx[1]:]) | |
| // Check log. | |
| if !strings.HasSuffix(want.String(), log) { | |
| t.Fatalf("wrong suffix:\n%s", log) | |
| } | |
| } | |
| func TestDebugLogLongString(t *testing.T) { | |
| skipDebugLog(t) | |
| runtime.ResetDebugLog() | |
| var longString = strings.Repeat("a", runtime.DebugLogStringLimit+1) | |
| runtime.Dlog().S(longString).End() | |
| got := dlogCanonicalize(runtime.DumpDebugLog()) | |
| want := "[] " + strings.Repeat("a", runtime.DebugLogStringLimit) + " ..(1 more bytes)..\n" | |
| if got != want { | |
| t.Fatalf("want %q, got %q", want, got) | |
| } | |
| } |