github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/logger/test_logger.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package logger 5 6 import ( 7 "fmt" 8 "os" 9 "runtime" 10 "strings" 11 "sync" 12 "time" 13 14 logging "github.com/keybase/go-logging" 15 16 "golang.org/x/net/context" 17 ) 18 19 // TestLogBackend is an interface for logging to a test object (i.e., 20 // a *testing.T). We define this in order to avoid pulling in the 21 // "testing" package in exported code. 22 type TestLogBackend interface { 23 Error(args ...interface{}) 24 Errorf(format string, args ...interface{}) 25 Fatal(args ...interface{}) 26 Fatalf(format string, args ...interface{}) 27 Log(args ...interface{}) 28 Logf(format string, args ...interface{}) 29 Failed() bool 30 Name() string 31 } 32 33 // TestLogger is a Logger that writes to a TestLogBackend. All 34 // messages except Fatal are printed using Logf, to avoid failing a 35 // test that is trying to test an error condition. No context tags 36 // are logged. 37 type TestLogger struct { 38 log TestLogBackend 39 extraDepth int 40 sync.Mutex 41 } 42 43 func NewTestLogger(log TestLogBackend) *TestLogger { 44 return &TestLogger{log: log} 45 } 46 47 // Verify TestLogger fully implements the Logger interface. 48 var _ Logger = (*TestLogger)(nil) 49 50 // Whether "TEST FAILED" has output for a particular test is stored globally. 51 // Because some tests use multiple instances of TestLogger so storing 52 // it there would result in multiple "TEST FAILED" per test. 53 // This way has the drawback that when two tests in different packages 54 // share a name, only one of their "TEST FAILED" will print. 55 var globalFailReportedLock sync.Mutex 56 var globalFailReported = make(map[string]struct{}) 57 58 // ctx can be `nil` 59 func (log *TestLogger) common(ctx context.Context, lvl logging.Level, useFatal bool, fmts string, arg ...interface{}) { 60 if log.log.Failed() { 61 globalFailReportedLock.Lock() 62 name := log.log.Name() 63 if _, reported := globalFailReported[name]; !reported { 64 log.log.Logf("TEST FAILED: %s", name) 65 globalFailReported[name] = struct{}{} 66 } 67 globalFailReportedLock.Unlock() 68 if stringListContains(strings.ToLower(os.Getenv("KEYBASE_TEST_LOG_AFTER_FAIL")), []string{"0", "false", "n", "no"}) { 69 return 70 } 71 } 72 73 if os.Getenv("KEYBASE_TEST_DUP_LOG_TO_STDOUT") != "" { 74 fmt.Printf(prepareString(ctx, 75 log.prefixCaller(log.extraDepth, lvl, fmts))+"\n", arg...) 76 } 77 78 if ctx != nil { 79 if useFatal { 80 log.log.Fatalf(prepareString(ctx, 81 log.prefixCaller(log.extraDepth, lvl, fmts)), arg...) 82 } else { 83 log.log.Logf(prepareString(ctx, 84 log.prefixCaller(log.extraDepth, lvl, fmts)), arg...) 85 } 86 } else { 87 if useFatal { 88 log.log.Fatalf(log.prefixCaller(log.extraDepth, lvl, fmts), arg...) 89 } else { 90 log.log.Logf(log.prefixCaller(log.extraDepth, lvl, fmts), arg...) 91 } 92 } 93 } 94 95 func (log *TestLogger) prefixCaller(extraDepth int, lvl logging.Level, fmts string) string { 96 // The testing library doesn't let us control the stack depth, 97 // and it always prints out its own prefix, so use \r to clear 98 // it out (at least on a terminal) and do our own formatting. 99 _, file, line, _ := runtime.Caller(3 + extraDepth) 100 elements := strings.Split(file, "/") 101 failed := "" 102 if log.log.Failed() { 103 failed = "[X] " 104 } 105 106 fileLine := fmt.Sprintf("%s:%d", elements[len(elements)-1], line) 107 return fmt.Sprintf("\r%s %s%-23s: [%.1s] %s", time.Now().Format("2006-01-02 15:04:05.00000"), 108 failed, fileLine, lvl, fmts) 109 } 110 111 func (log *TestLogger) Debug(fmts string, arg ...interface{}) { 112 log.common(context.TODO(), logging.DEBUG, false, fmts, arg...) 113 } 114 115 func (log *TestLogger) CDebugf(ctx context.Context, fmts string, 116 arg ...interface{}) { 117 log.common(ctx, logging.DEBUG, false, fmts, arg...) 118 } 119 120 func (log *TestLogger) Info(fmts string, arg ...interface{}) { 121 log.common(context.TODO(), logging.INFO, false, fmts, arg...) 122 } 123 124 func (log *TestLogger) CInfof(ctx context.Context, fmts string, 125 arg ...interface{}) { 126 log.common(ctx, logging.INFO, false, fmts, arg...) 127 } 128 129 func (log *TestLogger) Notice(fmts string, arg ...interface{}) { 130 log.common(context.TODO(), logging.NOTICE, false, fmts, arg...) 131 } 132 133 func (log *TestLogger) CNoticef(ctx context.Context, fmts string, 134 arg ...interface{}) { 135 log.common(ctx, logging.NOTICE, false, fmts, arg...) 136 } 137 138 func (log *TestLogger) Warning(fmts string, arg ...interface{}) { 139 log.common(context.TODO(), logging.WARNING, false, fmts, arg...) 140 } 141 142 func (log *TestLogger) CWarningf(ctx context.Context, fmts string, 143 arg ...interface{}) { 144 log.common(ctx, logging.WARNING, false, fmts, arg...) 145 } 146 147 func (log *TestLogger) Error(fmts string, arg ...interface{}) { 148 log.common(context.TODO(), logging.ERROR, false, fmts, arg...) 149 } 150 151 func (log *TestLogger) Errorf(fmts string, arg ...interface{}) { 152 log.common(context.TODO(), logging.ERROR, false, fmts, arg...) 153 } 154 155 func (log *TestLogger) CErrorf(ctx context.Context, fmts string, 156 arg ...interface{}) { 157 log.common(ctx, logging.ERROR, false, fmts, arg...) 158 } 159 160 func (log *TestLogger) Critical(fmts string, arg ...interface{}) { 161 log.common(context.TODO(), logging.CRITICAL, false, fmts, arg...) 162 } 163 164 func (log *TestLogger) CCriticalf(ctx context.Context, fmts string, 165 arg ...interface{}) { 166 log.common(ctx, logging.CRITICAL, false, fmts, arg...) 167 } 168 169 func (log *TestLogger) Fatalf(fmts string, arg ...interface{}) { 170 log.common(context.TODO(), logging.CRITICAL, true, fmts, arg...) 171 } 172 173 func (log *TestLogger) CFatalf(ctx context.Context, fmts string, 174 arg ...interface{}) { 175 log.common(ctx, logging.CRITICAL, true, fmts, arg...) 176 } 177 178 func (log *TestLogger) Profile(fmts string, arg ...interface{}) { 179 log.common(context.TODO(), logging.CRITICAL, false, fmts, arg...) 180 } 181 182 func (log *TestLogger) Configure(style string, debug bool, filename string) { 183 // no-op 184 } 185 186 func (log *TestLogger) CloneWithAddedDepth(depth int) Logger { 187 log.Lock() 188 defer log.Unlock() 189 var clone TestLogger 190 clone.log = log.log 191 clone.extraDepth = log.extraDepth + depth 192 return &clone 193 } 194 195 // no-op stubs to fulfill the Logger interface 196 func (log *TestLogger) SetExternalHandler(_ ExternalHandler) {} 197 198 func stringListContains(s string, a []string) bool { 199 for _, t := range a { 200 if s == t { 201 return true 202 } 203 } 204 return false 205 }