Skip to content

Commit a3ffae3

Browse files
committed
Add test log checker
1 parent a39287c commit a3ffae3

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed

modules/log/event.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package log
77
import (
88
"fmt"
99
"sync"
10+
"sync/atomic"
1011
"time"
1112
)
1213

@@ -152,6 +153,7 @@ type MultiChannelledLog struct {
152153
stacktraceLevel Level
153154
closed chan bool
154155
paused chan bool
156+
processedCount int64
155157
}
156158

157159
// NewMultiChannelledLog a new logger instance with given logger provider and config.
@@ -307,6 +309,7 @@ func (m *MultiChannelledLog) Start() {
307309
m.closeLoggers()
308310
return
309311
}
312+
atomic.AddInt64(&m.processedCount, 1)
310313
m.rwmutex.RLock()
311314
for _, logger := range m.loggers {
312315
err := logger.LogEvent(event)
@@ -346,6 +349,10 @@ func (m *MultiChannelledLog) LogEvent(event *Event) error {
346349
}
347350
}
348351

352+
func (m *MultiChannelledLog) GetProcessedCount() int64 {
353+
return atomic.LoadInt64(&m.processedCount)
354+
}
355+
349356
// Close this MultiChannelledLog
350357
func (m *MultiChannelledLog) Close() {
351358
m.close <- true
@@ -397,3 +404,7 @@ func (m *MultiChannelledLog) ResetLevel() Level {
397404
func (m *MultiChannelledLog) GetName() string {
398405
return m.name
399406
}
407+
408+
func (e *Event) GetMsg() string {
409+
return e.msg
410+
}

modules/testlog/testlog.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package testlog
6+
7+
import (
8+
"fmt"
9+
"strconv"
10+
"strings"
11+
"sync"
12+
"sync/atomic"
13+
"time"
14+
15+
"code.gitea.io/gitea/modules/log"
16+
)
17+
18+
type LogChecker struct {
19+
logger *log.MultiChannelledLogger
20+
loggerName string
21+
eventLoggerName string
22+
expectedMessages []string
23+
expectedMessageIdx int
24+
mu sync.Mutex
25+
}
26+
27+
func (lc *LogChecker) LogEvent(event *log.Event) error {
28+
lc.mu.Lock()
29+
defer lc.mu.Unlock()
30+
31+
if lc.expectedMessageIdx < len(lc.expectedMessages) {
32+
if strings.Contains(event.GetMsg(), lc.expectedMessages[lc.expectedMessageIdx]) {
33+
lc.expectedMessageIdx++
34+
}
35+
}
36+
return nil
37+
}
38+
39+
func (lc *LogChecker) Close() {
40+
}
41+
42+
func (lc *LogChecker) Flush() {
43+
}
44+
45+
func (lc *LogChecker) GetLevel() log.Level {
46+
return log.TRACE
47+
}
48+
49+
func (lc *LogChecker) GetStacktraceLevel() log.Level {
50+
return log.NONE
51+
}
52+
53+
func (lc *LogChecker) GetName() string {
54+
return lc.eventLoggerName
55+
}
56+
57+
func (lc *LogChecker) ReleaseReopen() error {
58+
return nil
59+
}
60+
61+
var checkerIndex int64
62+
63+
func NewLogChecker(loggerName string) (*LogChecker, func()) {
64+
logger := log.GetLogger(loggerName)
65+
newCheckerIndex := atomic.AddInt64(&checkerIndex, 1)
66+
lc := &LogChecker{
67+
logger: logger,
68+
loggerName: loggerName,
69+
eventLoggerName: "TestLogChecker-" + strconv.FormatInt(newCheckerIndex, 10),
70+
}
71+
72+
if err := logger.AddLogger(lc); err != nil {
73+
panic(err) // it's impossible
74+
}
75+
76+
return lc, func() {
77+
_, _ = logger.DelLogger(lc.GetName())
78+
}
79+
}
80+
81+
// ExpectContains will make the `Check` function to check if these logs are outputted.
82+
// we could refactor this function to accept user-defined functions to do more filter work, ex: Except(Contains("..."), NotContains("..."), ...)
83+
func (lc *LogChecker) ExpectContains(msg string, msgs ...string) {
84+
lc.expectedMessages = make([]string, 0, len(msg)+1)
85+
lc.expectedMessages = append(lc.expectedMessages, msg)
86+
lc.expectedMessages = append(lc.expectedMessages, msgs...)
87+
}
88+
89+
// Check returns nil if the logs are expected. Otherwise it returns the error with reason.
90+
func (lc *LogChecker) Check() error {
91+
lastProcessedCount := lc.logger.GetProcessedCount()
92+
93+
// in case the LogEvent is still being processed, we should wait for a while to make sure our EventLogger could receive all events.
94+
for i := 0; i < 100; i++ {
95+
lc.mu.Lock()
96+
if lc.expectedMessageIdx == len(lc.expectedMessages) {
97+
return nil
98+
}
99+
lc.mu.Unlock()
100+
101+
// we assume that the MultiChannelledLog can process one log event in 10ms.
102+
// if there is a long time that no more log is processed, then we are sure that there is really no more logs.
103+
time.Sleep(10 * time.Millisecond)
104+
currProcessedCount := lc.logger.GetProcessedCount()
105+
if currProcessedCount == lastProcessedCount {
106+
break
107+
}
108+
lastProcessedCount = currProcessedCount
109+
}
110+
111+
lc.mu.Lock()
112+
defer lc.mu.Unlock()
113+
return fmt.Errorf("expect to see message: %q, but failed", lc.expectedMessages[lc.expectedMessageIdx])
114+
}

modules/testlog/testlog_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package testlog
6+
7+
import (
8+
"testing"
9+
10+
"code.gitea.io/gitea/modules/log"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestLogChecker(t *testing.T) {
16+
_ = log.NewLogger(1000, "console", "console", `{"level":"info","stacktracelevel":"NONE","stderr":true}`)
17+
18+
lc, cleanup := NewLogChecker(log.DEFAULT)
19+
defer cleanup()
20+
21+
lc.ExpectContains("First", "Third")
22+
23+
log.Info("test")
24+
assert.Error(t, lc.Check())
25+
26+
log.Info("First")
27+
assert.Error(t, lc.Check())
28+
29+
log.Info("Second")
30+
assert.Error(t, lc.Check())
31+
32+
log.Info("Third")
33+
assert.NoError(t, lc.Check())
34+
}

0 commit comments

Comments
 (0)