code.gitea.io/gitea@v1.22.3/modules/testlogger/testlogger.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package testlogger
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"runtime"
    11  	"strings"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"code.gitea.io/gitea/modules/log"
    17  	"code.gitea.io/gitea/modules/queue"
    18  )
    19  
    20  var (
    21  	prefix    string
    22  	SlowTest  = 10 * time.Second
    23  	SlowFlush = 5 * time.Second
    24  )
    25  
    26  var WriterCloser = &testLoggerWriterCloser{}
    27  
    28  type testLoggerWriterCloser struct {
    29  	sync.RWMutex
    30  	t []testing.TB
    31  }
    32  
    33  func (w *testLoggerWriterCloser) pushT(t testing.TB) {
    34  	w.Lock()
    35  	w.t = append(w.t, t)
    36  	w.Unlock()
    37  }
    38  
    39  func (w *testLoggerWriterCloser) Write(p []byte) (int, error) {
    40  	// There was a data race problem: the logger system could still try to output logs after the runner is finished.
    41  	// So we must ensure that the "t" in stack is still valid.
    42  	w.RLock()
    43  	defer w.RUnlock()
    44  
    45  	var t testing.TB
    46  	if len(w.t) > 0 {
    47  		t = w.t[len(w.t)-1]
    48  	}
    49  
    50  	if len(p) > 0 && p[len(p)-1] == '\n' {
    51  		p = p[:len(p)-1]
    52  	}
    53  
    54  	if t == nil {
    55  		// if there is no running test, the log message should be outputted to console, to avoid losing important information.
    56  		// the "???" prefix is used to match the "===" and "+++" in PrintCurrentTest
    57  		return fmt.Fprintf(os.Stdout, "??? [TestLogger] %s\n", p)
    58  	}
    59  
    60  	t.Log(string(p))
    61  	return len(p), nil
    62  }
    63  
    64  func (w *testLoggerWriterCloser) popT() {
    65  	w.Lock()
    66  	if len(w.t) > 0 {
    67  		w.t = w.t[:len(w.t)-1]
    68  	}
    69  	w.Unlock()
    70  }
    71  
    72  func (w *testLoggerWriterCloser) Close() error {
    73  	return nil
    74  }
    75  
    76  func (w *testLoggerWriterCloser) Reset() {
    77  	w.Lock()
    78  	if len(w.t) > 0 {
    79  		for _, t := range w.t {
    80  			if t == nil {
    81  				continue
    82  			}
    83  			_, _ = fmt.Fprintf(os.Stdout, "Unclosed logger writer in test: %s", t.Name())
    84  			t.Errorf("Unclosed logger writer in test: %s", t.Name())
    85  		}
    86  		w.t = nil
    87  	}
    88  	w.Unlock()
    89  }
    90  
    91  // PrintCurrentTest prints the current test to os.Stdout
    92  func PrintCurrentTest(t testing.TB, skip ...int) func() {
    93  	t.Helper()
    94  	start := time.Now()
    95  	actualSkip := 1
    96  	if len(skip) > 0 {
    97  		actualSkip = skip[0] + 1
    98  	}
    99  	_, filename, line, _ := runtime.Caller(actualSkip)
   100  
   101  	if log.CanColorStdout {
   102  		_, _ = fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", fmt.Formatter(log.NewColoredValue(t.Name())), strings.TrimPrefix(filename, prefix), line)
   103  	} else {
   104  		_, _ = fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", t.Name(), strings.TrimPrefix(filename, prefix), line)
   105  	}
   106  	WriterCloser.pushT(t)
   107  	return func() {
   108  		took := time.Since(start)
   109  		if took > SlowTest {
   110  			if log.CanColorStdout {
   111  				_, _ = fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgYellow)), fmt.Formatter(log.NewColoredValue(took, log.Bold, log.FgYellow)))
   112  			} else {
   113  				_, _ = fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", t.Name(), took)
   114  			}
   115  		}
   116  		timer := time.AfterFunc(SlowFlush, func() {
   117  			if log.CanColorStdout {
   118  				_, _ = fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), SlowFlush)
   119  			} else {
   120  				_, _ = fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", t.Name(), SlowFlush)
   121  			}
   122  		})
   123  		if err := queue.GetManager().FlushAll(context.Background(), time.Minute); err != nil {
   124  			t.Errorf("Flushing queues failed with error %v", err)
   125  		}
   126  		timer.Stop()
   127  		flushTook := time.Since(start) - took
   128  		if flushTook > SlowFlush {
   129  			if log.CanColorStdout {
   130  				_, _ = fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), fmt.Formatter(log.NewColoredValue(flushTook, log.Bold, log.FgRed)))
   131  			} else {
   132  				_, _ = fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", t.Name(), flushTook)
   133  			}
   134  		}
   135  		WriterCloser.popT()
   136  	}
   137  }
   138  
   139  // Printf takes a format and args and prints the string to os.Stdout
   140  func Printf(format string, args ...any) {
   141  	if log.CanColorStdout {
   142  		for i := 0; i < len(args); i++ {
   143  			args[i] = log.NewColoredValue(args[i])
   144  		}
   145  	}
   146  	_, _ = fmt.Fprintf(os.Stdout, "\t"+format, args...)
   147  }
   148  
   149  // TestLogEventWriter is a logger which will write to the testing log
   150  type TestLogEventWriter struct {
   151  	*log.EventWriterBaseImpl
   152  }
   153  
   154  // NewTestLoggerWriter creates a TestLogEventWriter as a log.LoggerProvider
   155  func NewTestLoggerWriter(name string, mode log.WriterMode) log.EventWriter {
   156  	w := &TestLogEventWriter{}
   157  	w.EventWriterBaseImpl = log.NewEventWriterBase(name, "test-log-writer", mode)
   158  	w.OutputWriteCloser = WriterCloser
   159  	return w
   160  }
   161  
   162  func init() {
   163  	const relFilePath = "modules/testlogger/testlogger.go"
   164  	_, filename, _, _ := runtime.Caller(0)
   165  	if !strings.HasSuffix(filename, relFilePath) {
   166  		panic("source code file path doesn't match expected: " + relFilePath)
   167  	}
   168  	prefix = strings.TrimSuffix(filename, relFilePath)
   169  }