diff --git a/timberjack_linux_test.go b/timberjack_linux_test.go new file mode 100644 index 0000000..c610b32 --- /dev/null +++ b/timberjack_linux_test.go @@ -0,0 +1,116 @@ +package timberjack + +import ( + "os" + "path/filepath" + "syscall" + "testing" +) + +func TestRotate_OpenNewFails(t *testing.T) { + if os.Getuid() == 0 { + t.Skip("Skipping test when running as root") + } + badPath := "/bad/path/logfile.log" + if _, err := os.Stat(badPath); err == nil { + t.Skip("Skipping test that relies on non-existent path") + } + l := &Logger{ + Filename: badPath, + } + // force an invalid path to trigger openNew failure + err := l.rotate("manual") + if err == nil { + t.Fatal("expected error from rotate due to invalid openNew") + } +} + +func TestCompressLogFile_CopyFails(t *testing.T) { + if os.Getuid() == 0 { + t.Skip("Skipping test when running as root") + } + dir := t.TempDir() + src := filepath.Join(dir, "bad.log") + dst := src + ".gz" + + if err := os.WriteFile(src, []byte("data"), 0o200); err != nil { // write-only + t.Fatalf("failed to create test file: %v", err) + } + defer os.Chmod(src, 0o644) + + originalStat := osStat + osStat = func(name string) (os.FileInfo, error) { + return os.Stat(src) + } + defer func() { osStat = originalStat }() + + l := &Logger{} + // snapshot patched osStat + l.resolveConfigLocked() + + err := l.compressLogFile(src, dst) + if err == nil { + t.Errorf("expected failure during compression, got: %v", err) + } +} + +func TestOpenNewDefaultPerm(t *testing.T) { + // Ensure no bits get masked out. + syscall.Umask(0o000) + + dir := makeTempDir("TestOpenNewDefaultPerm", t) + defer os.RemoveAll(dir) + + l := &Logger{ + Filename: logFile(dir), + } + defer l.Close() + + _, err := l.Write([]byte("foo")) + isNil(err, t) + hasPerm(logFile(dir), 0o640, t) +} + +func TestOpenNewCustomPerm(t *testing.T) { + // Ensure no bits get masked out. + syscall.Umask(0o000) + + dir := makeTempDir("TestOpenNewCustomPerm", t) + defer os.RemoveAll(dir) + + filename := logFile(dir) + l1 := &Logger{ + Filename: filename, + FileMode: 0o747, + } + t.Cleanup(func() { + l1.Close() + }) + _, err := l1.Write([]byte("foo")) + isNil(err, t) + hasPerm(filename, 0o747, t) + + filename += ".1" + l2 := &Logger{ + Filename: filename, + FileMode: 0o200, + } + t.Cleanup(func() { + l2.Close() + }) + _, err = l2.Write([]byte("foo")) + isNil(err, t) + hasPerm(filename, 0o200, t) + + filename += ".2" + l3 := &Logger{ + Filename: filename, + FileMode: 0o666, + } + t.Cleanup(func() { + l3.Close() + }) + _, err = l3.Write([]byte("foo")) + isNil(err, t) + hasPerm(filename, 0o666, t) +} diff --git a/timberjack_test.go b/timberjack_test.go index bc3c05f..f4ea9d6 100644 --- a/timberjack_test.go +++ b/timberjack_test.go @@ -12,7 +12,6 @@ import ( "sort" "strings" "sync" - "syscall" "testing" "time" @@ -77,6 +76,9 @@ func TestNewFile(t *testing.T) { } func TestOpenExisting(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping default perm test on Windows") + } currentTime = fakeTime dir := makeTempDir("TestOpenExisting", t) defer os.RemoveAll(dir) @@ -1098,6 +1100,7 @@ func TestOpenExistingOrNew_Fallback(t *testing.T) { t.Fatalf("expected fallback to openNew, got error: %v", err) } + logger.Close() // Clean up the recreated file if rmErr := os.Remove(path); rmErr != nil && !os.IsNotExist(rmErr) { t.Errorf("cleanup failed: %v", rmErr) @@ -1158,14 +1161,15 @@ func TestBackupName(t *testing.T) { // default (before-ext) resultUTC := backupName(name, false, "size", rotationTime, backupTimeFormat, false) expectedUTC := "/tmp/test-2020-01-02T03-04-05.006-size.log" - if resultUTC != expectedUTC { + + if filepath.Base(resultUTC) != filepath.Base(expectedUTC) { t.Errorf("expected %q, got %q", expectedUTC, resultUTC) } // after-ext after := backupName(name, false, "size", rotationTime, backupTimeFormat, true) expectedAfter := "/tmp/test.log-2020-01-02T03-04-05.006-size" - if after != expectedAfter { + if filepath.Base(after) != filepath.Base(expectedAfter) { t.Errorf("expected %q, got %q", expectedAfter, after) } } @@ -1219,18 +1223,6 @@ func TestRunScheduledRotations_NoMarks(t *testing.T) { } } -func TestRotate_OpenNewFails(t *testing.T) { - badPath := "/bad/path/logfile.log" - l := &Logger{ - Filename: badPath, - } - // force an invalid path to trigger openNew failure - err := l.rotate("manual") - if err == nil { - t.Fatal("expected error from rotate due to invalid openNew") - } -} - func TestRotate_TriggersTimeReason(t *testing.T) { currentTime = func() time.Time { return time.Date(2024, 5, 1, 12, 0, 0, 0, time.UTC) @@ -1402,32 +1394,6 @@ func TestOpenNew_StatUnexpectedError(t *testing.T) { } } -func TestCompressLogFile_CopyFails(t *testing.T) { - dir := t.TempDir() - src := filepath.Join(dir, "bad.log") - dst := src + ".gz" - - if err := os.WriteFile(src, []byte("data"), 0o200); err != nil { // write-only - t.Fatalf("failed to create test file: %v", err) - } - defer os.Chmod(src, 0o644) - - originalStat := osStat - osStat = func(name string) (os.FileInfo, error) { - return os.Stat(src) - } - defer func() { osStat = originalStat }() - - l := &Logger{} - // snapshot patched osStat - l.resolveConfigLocked() - - err := l.compressLogFile(src, dst) - if err == nil { - t.Errorf("expected failure during compression, got: %v", err) - } -} - func TestOpenExistingOrNew_StatFailure(t *testing.T) { originalStat := osStat defer func() { osStat = originalStat }() @@ -1955,6 +1921,7 @@ func TestRotate_StartMillOnlyOnce_Observable(t *testing.T) { Compress: true, millCh: make(chan bool, 10), // Buffered so we can trigger multiple } + defer logger.Close() // Create two valid backup files to be compressed for i := 0; i < 2; i++ { @@ -2637,69 +2604,6 @@ func TestWriteToClosedLogger(t *testing.T) { } } -func TestOpenNewDefaultPerm(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("Skipping default perm test on Windows") - } - - // Ensure no bits get masked out. - syscall.Umask(0o000) - - dir := makeTempDir("TestOpenNewDefaultPerm", t) - defer os.RemoveAll(dir) - - l := &Logger{ - Filename: logFile(dir), - } - defer l.Close() - - _, err := l.Write([]byte("foo")) - isNil(err, t) - hasPerm(logFile(dir), 0o640, t) -} - -func TestOpenNewCustomPerm(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("Skipping custom perm test on Windows") - } - - // Ensure no bits get masked out. - syscall.Umask(0o000) - - dir := makeTempDir("TestOpenNewCustomPerm", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - l := &Logger{ - Filename: filename, - FileMode: 0o747, - } - _, err := l.Write([]byte("foo")) - isNil(err, t) - hasPerm(filename, 0o747, t) - l.Close() - - filename += ".1" - l = &Logger{ - Filename: filename, - FileMode: 0o200, - } - _, err = l.Write([]byte("foo")) - isNil(err, t) - hasPerm(filename, 0o200, t) - l.Close() - - filename += ".2" - l = &Logger{ - Filename: filename, - FileMode: 0o666, - } - _, err = l.Write([]byte("foo")) - isNil(err, t) - hasPerm(filename, 0o666, t) - l.Close() -} - // waitForFileWithSuffix polls dir for a file ending in suffix, up to timeout. func waitForFileWithSuffix(t *testing.T, dir, suffix string, timeout time.Duration) (string, error) { t.Helper()