Skip to content

Add test log checker #18744

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions modules/log/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package log
import (
"fmt"
"sync"
"sync/atomic"
"time"
)

Expand Down Expand Up @@ -152,6 +153,7 @@ type MultiChannelledLog struct {
stacktraceLevel Level
closed chan bool
paused chan bool
processedCount int64
}

// NewMultiChannelledLog a new logger instance with given logger provider and config.
Expand Down Expand Up @@ -313,6 +315,7 @@ func (m *MultiChannelledLog) Start() {
m.closeLoggers()
return
}
atomic.AddInt64(&m.processedCount, 1)
m.rwmutex.RLock()
for _, logger := range m.loggers {
err := logger.LogEvent(event)
Expand Down Expand Up @@ -352,6 +355,10 @@ func (m *MultiChannelledLog) LogEvent(event *Event) error {
}
}

func (m *MultiChannelledLog) GetProcessedCount() int64 {
return atomic.LoadInt64(&m.processedCount)
}

// Close this MultiChannelledLog
func (m *MultiChannelledLog) Close() {
m.close <- true
Expand Down Expand Up @@ -403,3 +410,7 @@ func (m *MultiChannelledLog) ResetLevel() Level {
func (m *MultiChannelledLog) GetName() string {
return m.name
}

func (e *Event) GetMsg() string {
return e.msg
}
114 changes: 114 additions & 0 deletions modules/testlog/testlog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package testlog

import (
"fmt"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

"code.gitea.io/gitea/modules/log"
)

type LogChecker struct {
logger *log.MultiChannelledLogger
loggerName string
eventLoggerName string
expectedMessages []string
expectedMessageIdx int
mu sync.Mutex
}

func (lc *LogChecker) LogEvent(event *log.Event) error {
lc.mu.Lock()
defer lc.mu.Unlock()

if lc.expectedMessageIdx < len(lc.expectedMessages) {
if strings.Contains(event.GetMsg(), lc.expectedMessages[lc.expectedMessageIdx]) {
lc.expectedMessageIdx++
}
}
return nil
}

func (lc *LogChecker) Close() {
}

func (lc *LogChecker) Flush() {
}

func (lc *LogChecker) GetLevel() log.Level {
return log.TRACE
}

func (lc *LogChecker) GetStacktraceLevel() log.Level {
return log.NONE
}

func (lc *LogChecker) GetName() string {
return lc.eventLoggerName
}

func (lc *LogChecker) ReleaseReopen() error {
return nil
}

var checkerIndex int64

func NewLogChecker(loggerName string) (*LogChecker, func()) {
logger := log.GetLogger(loggerName)
newCheckerIndex := atomic.AddInt64(&checkerIndex, 1)
lc := &LogChecker{
logger: logger,
loggerName: loggerName,
eventLoggerName: "TestLogChecker-" + strconv.FormatInt(newCheckerIndex, 10),
}

if err := logger.AddLogger(lc); err != nil {
panic(err) // it's impossible
}

return lc, func() {
_, _ = logger.DelLogger(lc.GetName())
}
}

// ExpectContains will make the `Check` function to check if these logs are outputted.
// we could refactor this function to accept user-defined functions to do more filter work, ex: Except(Contains("..."), NotContains("..."), ...)
func (lc *LogChecker) ExpectContains(msg string, msgs ...string) {
lc.expectedMessages = make([]string, 0, len(msg)+1)
lc.expectedMessages = append(lc.expectedMessages, msg)
lc.expectedMessages = append(lc.expectedMessages, msgs...)
}

// Check returns nil if the logs are expected. Otherwise it returns the error with reason.
func (lc *LogChecker) Check() error {
lastProcessedCount := lc.logger.GetProcessedCount()

// in case the LogEvent is still being processed, we should wait for a while to make sure our EventLogger could receive all events.
for i := 0; i < 100; i++ {
lc.mu.Lock()
if lc.expectedMessageIdx == len(lc.expectedMessages) {
return nil
}
lc.mu.Unlock()

// we assume that the MultiChannelledLog can process one log event in 10ms.
// if there is a long time that no more log is processed, then we are sure that there is really no more logs.
time.Sleep(10 * time.Millisecond)
currProcessedCount := lc.logger.GetProcessedCount()
if currProcessedCount == lastProcessedCount {
break
}
lastProcessedCount = currProcessedCount
}

lc.mu.Lock()
defer lc.mu.Unlock()
return fmt.Errorf("expect to see message: %q, but failed", lc.expectedMessages[lc.expectedMessageIdx])
}
34 changes: 34 additions & 0 deletions modules/testlog/testlog_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package testlog

import (
"testing"

"code.gitea.io/gitea/modules/log"

"github.com/stretchr/testify/assert"
)

func TestLogChecker(t *testing.T) {
_ = log.NewLogger(1000, "console", "console", `{"level":"info","stacktracelevel":"NONE","stderr":true}`)

lc, cleanup := NewLogChecker(log.DEFAULT)
defer cleanup()

lc.ExpectContains("First", "Third")

log.Info("test")
assert.Error(t, lc.Check())

log.Info("First")
assert.Error(t, lc.Check())

log.Info("Second")
assert.Error(t, lc.Check())

log.Info("Third")
assert.NoError(t, lc.Check())
}