starlog/lifecycle_test.go

171 lines
3.7 KiB
Go
Raw Normal View History

2026-03-19 16:37:57 +08:00
package starlog
import (
"bytes"
"context"
"errors"
"path/filepath"
"sync/atomic"
"testing"
"time"
)
type syncWriteBuffer struct {
bytes.Buffer
syncCalls uint64
}
func (buffer *syncWriteBuffer) Sync() error {
atomic.AddUint64(&buffer.syncCalls, 1)
return nil
}
type closeSink struct {
closeCalls uint64
}
func (sink *closeSink) Write(data []byte) error {
_ = data
return nil
}
func (sink *closeSink) Close() error {
atomic.AddUint64(&sink.closeCalls, 1)
return nil
}
func TestFlushDrainsPendingWrites(t *testing.T) {
var output bytes.Buffer
logger := newStructuredTestLogger(&output)
logger.SetSwitching(true)
logger.Infoln("pending")
if output.Len() != 0 {
t.Fatalf("pending logs should not be written while switching=true")
}
if err := logger.Flush(); err != nil {
t.Fatalf("Flush failed: %v", err)
}
if output.Len() == 0 {
t.Fatalf("Flush should write buffered logs")
}
}
func TestSyncCallsWriterSync(t *testing.T) {
var writer syncWriteBuffer
logger := NewStarlog(&writer)
logger.SetShowStd(false)
logger.SetShowColor(false)
logger.SetShowOriginFile(false)
logger.SetShowFuncName(false)
logger.SetShowFlag(false)
logger.Infoln("sync")
if err := logger.Sync(); err != nil {
t.Fatalf("Sync failed: %v", err)
}
if atomic.LoadUint64(&writer.syncCalls) == 0 {
t.Fatalf("Sync should call underlying writer Sync method")
}
}
func TestCloseClosesSinkAndStopsWrite(t *testing.T) {
sink := &closeSink{}
logger := NewStarlog(nil)
logger.SetShowStd(false)
logger.SetShowColor(false)
logger.SetShowOriginFile(false)
logger.SetShowFuncName(false)
logger.SetShowFlag(false)
logger.SetSink(sink)
if err := logger.Close(); err != nil {
t.Fatalf("Close failed: %v", err)
}
if atomic.LoadUint64(&sink.closeCalls) == 0 {
t.Fatalf("Close should call sink.Close")
}
if !logger.IsWriteStopped() {
t.Fatalf("Close should stop writer")
}
}
func TestWaitAsyncDrainContextTimeout(t *testing.T) {
stackMu.Lock()
stackStarted = true
stacks = newStarChanStack(1)
stackStopChan = nil
stackDoneChan = nil
stackMu.Unlock()
defer func() {
stackMu.Lock()
if stacks != nil {
_ = stacks.Close()
}
stackStarted = false
stacks = nil
stackStopChan = nil
stackDoneChan = nil
stackMu.Unlock()
}()
if err := stacks.Push("x"); err != nil {
t.Fatalf("prepare queue failed: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
defer cancel()
err := WaitAsyncDrain(ctx)
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("WaitAsyncDrain should return context deadline, got %v", err)
}
}
func TestShutdownStopsAsyncStacks(t *testing.T) {
resetAsyncMetricsForTest()
defer func() {
resetAsyncMetricsForTest()
StopStacks()
}()
logger := NewStarlog(nil)
logger.SetShowStd(false)
handled := make(chan struct{}, 1)
logger.SetHandler(func(LogData) {
select {
case handled <- struct{}{}:
default:
}
})
logger.Infoln("shutdown")
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := logger.Shutdown(ctx); err != nil {
t.Fatalf("Shutdown failed: %v", err)
}
select {
case <-handled:
case <-time.After(200 * time.Millisecond):
t.Fatalf("async handler should complete before shutdown")
}
stackMu.Lock()
started := stackStarted
stackMu.Unlock()
if started {
t.Fatalf("Shutdown should stop async stacks")
}
}
func TestCloseManagedLogFileNoDoubleCloseError(t *testing.T) {
logger := NewStarlog(nil)
logger.SetShowStd(false)
logPath := filepath.Join(testBinDir(t), "lifecycle_close.log")
if err := SetLogFile(logPath, logger, false); err != nil {
t.Fatalf("SetLogFile failed: %v", err)
}
if err := logger.Close(); err != nil {
t.Fatalf("Close should not fail for managed log file: %v", err)
}
}