package log import ( "fmt" "io" "os" "path/filepath" "strings" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func resetLoggerForTest(t *testing.T) { t.Helper() home := t.TempDir() for _, key := range []string{"HOME", "USERPROFILE", "APPDATA", "LOCALAPPDATA"} { t.Setenv(key, home) } logMutex.Lock() logger = nil once = sync.Once{} verboseMode = false debugMode = false alwaysStderr = false logMutex.Unlock() t.Cleanup(func() { Close() logMutex.Lock() logger = nil once = sync.Once{} logMutex.Unlock() }) } func TestConcurrentWriteFlushAndClose(t *testing.T) { resetLoggerForTest(t) Init() path := GetFilePath() require.NotEmpty(t, path) var writers sync.WaitGroup for i := 0; i < 20; i++ { writers.Add(1) go func() { defer writers.Done() for n := 0; n < 100; n++ { Info("concurrent message") } }() } Flush() Close() writers.Wait() Close() // idempotent content, err := os.ReadFile(path) require.NoError(t, err) assert.Contains(t, string(content), "MPV.Rocks Installer Log Started") assert.Contains(t, string(content), "MPV.Rocks Installer Log Ended") } func TestModeGetters(t *testing.T) { resetLoggerForTest(t) t.Cleanup(func() { SetVerboseMode(false) SetDebugMode(false) logMutex.Lock() alwaysStderr = false logMutex.Unlock() }) assert.False(t, IsVerboseMode()) assert.False(t, IsDebugMode()) SetVerboseMode(true) assert.True(t, IsVerboseMode()) assert.False(t, IsDebugMode()) SetDebugMode(true) assert.True(t, IsDebugMode()) } func TestVerboseModeMirrorsLogsToStderr(t *testing.T) { resetLoggerForTest(t) readEnd, writeEnd, err := os.Pipe() require.NoError(t, err) originalStderr := os.Stderr os.Stderr = writeEnd t.Cleanup(func() { os.Stderr = originalStderr }) SetVerboseMode(true) Init() Info("visible verbose message") require.NoError(t, writeEnd.Close()) output, err := io.ReadAll(readEnd) require.NoError(t, err) require.NoError(t, readEnd.Close()) assert.Contains(t, string(output), "visible verbose message") } func TestRotateLogIfOversize(t *testing.T) { path := filepath.Join(t.TempDir(), "mpv-manager.log") require.NoError(t, os.WriteFile(path, []byte("old"), 0600)) require.NoError(t, os.Truncate(path, maxLogFileBytes+1)) rotateLogIfOversize(path) assert.NoFileExists(t, path) info, err := os.Stat(path + ".1") require.NoError(t, err) assert.Equal(t, maxLogFileBytes+1, info.Size()) } func TestLongSessionRotatesWithoutRestart(t *testing.T) { resetLoggerForTest(t) Init() path := GetFilePath() message := strings.Repeat("x", 512<<10) for index := 0; index < 45; index++ { Write(fmt.Sprintf("record-%02d %s", index, message)) } Flush() Close() for _, name := range []string{path, path + ".1"} { info, err := os.Stat(name) require.NoError(t, err) require.LessOrEqual(t, info.Size(), maxLogFileBytes) } current, err := os.ReadFile(path) require.NoError(t, err) assert.Contains(t, string(current), "record-44") assert.NotContains(t, string(current), "record-00") } func TestOversizedLogRecordIsBounded(t *testing.T) { resetLoggerForTest(t) Init() Write(strings.Repeat("x", int(maxLogFileBytes)+1)) Flush() info, err := os.Stat(GetFilePath()) require.NoError(t, err) assert.LessOrEqual(t, info.Size(), maxLogFileBytes) content, err := os.ReadFile(GetFilePath()) require.NoError(t, err) assert.Contains(t, string(content), "log record truncated at file size limit") } func TestClearUsesWriterFileOffset(t *testing.T) { resetLoggerForTest(t) Init() path := GetFilePath() Write("before clear") require.NoError(t, Clear()) Write("after clear") Flush() Close() content, err := os.ReadFile(path) require.NoError(t, err) assert.NotContains(t, string(content), "before clear") assert.Contains(t, string(content), "after clear") } func TestClearAfterRotationPreservesBackupAndAppendWrites(t *testing.T) { resetLoggerForTest(t) Init() path := GetFilePath() message := strings.Repeat("x", 512<<10) for index := 0; index < 24; index++ { Write(fmt.Sprintf("record-%02d %s", index, message)) } Flush() backup, err := os.ReadFile(path + ".1") require.NoError(t, err) require.NoError(t, Clear()) info, err := os.Stat(path) require.NoError(t, err) require.Zero(t, info.Size()) Write("first record after clear") Flush() require.NoError(t, Clear()) Write("second record after clear") Flush() Close() content, err := os.ReadFile(path) require.NoError(t, err) assert.NotContains(t, string(content), "first record after clear") assert.Contains(t, string(content), "second record after clear") assert.NotContains(t, string(content), "\x00") assert.Less(t, len(content), 1024) retained, err := os.ReadFile(path + ".1") require.NoError(t, err) assert.Equal(t, backup, retained) } func TestBurstWritesAreNotDropped(t *testing.T) { resetLoggerForTest(t) Init() path := GetFilePath() const count = 750 for index := 0; index < count; index++ { Write(fmt.Sprintf("burst-record-%04d", index)) } Flush() Close() content, err := os.ReadFile(path) require.NoError(t, err) assert.Equal(t, count, strings.Count(string(content), "burst-record-")) }