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) { runtime := defaultAsyncRuntime() runtime.resetForTest() t.Cleanup(runtime.resetForTest) queue := newStarChanStack(1) setAsyncRuntimeStateForTest(runtime, queue, true) if err := queue.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) { logger := NewStarlog(nil) runtime := resetLoggerAsyncRuntimeForTest(t, logger) 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") } if runtime.Metrics().Started { t.Fatalf("Shutdown should stop logger async runtime") } } func TestShutdownDoesNotStopOtherLoggerAsyncRuntime(t *testing.T) { loggerA := NewStarlog(nil) runtimeA := resetLoggerAsyncRuntimeForTest(t, loggerA) loggerA.SetShowStd(false) loggerA.SetHandler(func(LogData) {}) loggerB := NewStarlog(nil) runtimeB := resetLoggerAsyncRuntimeForTest(t, loggerB) loggerB.SetShowStd(false) loggerB.SetHandler(func(LogData) {}) if !runtimeA.Metrics().Started || !runtimeB.Metrics().Started { t.Fatalf("both logger runtimes should be started") } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if err := loggerA.Shutdown(ctx); err != nil { t.Fatalf("Shutdown failed: %v", err) } if runtimeA.Metrics().Started { t.Fatalf("Shutdown should stop only the target logger runtime") } if !runtimeB.Metrics().Started { t.Fatalf("Shutdown should not stop another logger runtime") } } 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) } }