|  | // 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 ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "regexp" | 
|  | "runtime" | 
|  | "strings" | 
|  | "sync" | 
|  | "sync/atomic" | 
|  | "testing" | 
|  | ) | 
|  |  | 
|  | func skipDebugLog(t *testing.T) { | 
|  | if !runtime.DlogEnabled { | 
|  | t.Skip("debug log disabled (rebuild with -tags debuglog)") | 
|  | } | 
|  | } | 
|  |  | 
|  | 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() | 
|  | var wg sync.WaitGroup | 
|  | done := int32(0) | 
|  | wg.Add(1) | 
|  | go func() { | 
|  | // Encourage main goroutine to move around to | 
|  | // different Ms and Ps. | 
|  | for atomic.LoadInt32(&done) == 0 { | 
|  | runtime.Gosched() | 
|  | } | 
|  | wg.Done() | 
|  | }() | 
|  | var want bytes.Buffer | 
|  | for i := 0; i < 1000; i++ { | 
|  | runtime.Dlog().I(i).End() | 
|  | fmt.Fprintf(&want, "[] %d\n", i) | 
|  | runtime.Gosched() | 
|  | } | 
|  | atomic.StoreInt32(&done, 1) | 
|  | wg.Wait() | 
|  |  | 
|  | gotFull := runtime.DumpDebugLog() | 
|  | got := dlogCanonicalize(gotFull) | 
|  | 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 bytes.Buffer | 
|  | 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) | 
|  | } | 
|  | } |