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  }