mirror of
https://github.com/status-im/status-go.git
synced 2026-08-31 00:51:12 +00:00
The test wrote a file and assumed its mtime therefore landed after
processStartTime. Measured, that assumption has almost no margin:
gap between processStartTime and the test running:
full package run ~0.5ms
single test via -test.run ~0.1ms (what gotestsum --rerun-fails does)
filesystem mtime lag behind time.Now(), same machine:
-0.2ms to -1.3ms, several writes sharing one mtime
So the file routinely stats as older than the process, rotateLogFileForNewSession
archives it as a previous session's log, and the Stat assertion fails. Because
test-unit runs with UNIT_TEST_RERUN_FAILS=true, a first failure is re-run in
isolation -- the configuration with the *smallest* margin -- so the rerun
confirms rather than clears it.
Stamping the timestamp mirrors what the sibling test already does for its
"previous session" file, and makes this one assert the session logic instead of
the clock. 0/15 failures on a full coverpkg run and 0/10 on the isolated rerun,
against consistent failures before.
The production path is unaffected: rotateLogFileForNewSession runs once at
startup, when the file it inspects is either absent or genuinely from an earlier
session with a much older mtime.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmB4TDHDayGGK4Y6jyaV5R
117 lines
3.9 KiB
Go
117 lines
3.9 KiB
Go
package logutils
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
func TestRotateLogFileForNewSessionProducesLumberjackParseableName(t *testing.T) {
|
|
dir := t.TempDir()
|
|
file := filepath.Join(dir, "test.log")
|
|
require.NoError(t, os.WriteFile(file, []byte("x"), 0600))
|
|
old := time.Now().Add(-time.Hour)
|
|
require.NoError(t, os.Chtimes(file, old, old))
|
|
|
|
require.NoError(t, rotateLogFileForNewSession(file))
|
|
|
|
_, err := os.Stat(file)
|
|
require.True(t, os.IsNotExist(err))
|
|
|
|
matches, err := filepath.Glob(filepath.Join(dir, "test-*.log"))
|
|
require.NoError(t, err)
|
|
require.Len(t, matches, 1)
|
|
|
|
// The archive suffix must parse with lumberjack's backupTimeFormat,
|
|
// otherwise MaxBackups never prunes session archives.
|
|
suffix := strings.TrimSuffix(strings.TrimPrefix(filepath.Base(matches[0]), "test-"), ".log")
|
|
_, err = time.Parse(sessionArchiveTimeFormat, suffix)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestRotateLogFileForNewSessionKeepsActiveFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
file := filepath.Join(dir, "test.log")
|
|
require.NoError(t, os.WriteFile(file, []byte("x"), 0600))
|
|
// Stamp the mtime rather than relying on the write landing after processStartTime.
|
|
// That assumption has almost no margin: processStartTime is set when this package is
|
|
// initialised, and the test runs ~0.5ms later (~0.1ms when gotestsum --rerun-fails
|
|
// re-runs it alone). Filesystem mtimes are coarser than time.Now() and can lag it by
|
|
// more than that, so the file would stat as older than the process and be archived.
|
|
active := processStartTime.Add(time.Minute)
|
|
require.NoError(t, os.Chtimes(file, active, active))
|
|
|
|
require.NoError(t, rotateLogFileForNewSession(file))
|
|
|
|
_, err := os.Stat(file)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestRenameLegacySessionArchives(t *testing.T) {
|
|
dir := t.TempDir()
|
|
file := filepath.Join(dir, "pre_login.log")
|
|
for _, name := range []string{
|
|
"pre_login-2026-08-14T11-08-48Z.log", // legacy, to be renamed
|
|
"0x4d..2f40-2026-08-14T13-37-56Z.log", // legacy, but different base: untouched
|
|
"pre_login-2026-08-14T12-00-00Z.log", // legacy, collides with existing target
|
|
"pre_login-2026-08-14T12-00-00.000.log", // already in the new layout
|
|
} {
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, name), []byte("x"), 0600))
|
|
}
|
|
|
|
require.NoError(t, renameLegacySessionArchives(file))
|
|
|
|
for name, expectExists := range map[string]bool{
|
|
"pre_login-2026-08-14T11-08-48Z.log": false,
|
|
"pre_login-2026-08-14T11-08-48.000.log": true,
|
|
"0x4d..2f40-2026-08-14T13-37-56Z.log": true,
|
|
"pre_login-2026-08-14T12-00-00Z.log": true, // left alone due to collision
|
|
"pre_login-2026-08-14T12-00-00.000.log": true,
|
|
} {
|
|
_, err := os.Stat(filepath.Join(dir, name))
|
|
if expectExists {
|
|
require.NoError(t, err, name)
|
|
} else {
|
|
require.True(t, os.IsNotExist(err), name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSessionArchivesPrunedToDefaultMaxBackups(t *testing.T) {
|
|
dir := t.TempDir()
|
|
file := filepath.Join(dir, "test.log")
|
|
base := time.Now().Add(-24 * time.Hour).UTC()
|
|
for i := range DefaultLogMaxBackups + 5 {
|
|
ts := base.Add(time.Duration(i) * time.Minute).Format(sessionArchiveTimeFormat)
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "test-"+ts+".log"), []byte("old"), 0600))
|
|
}
|
|
|
|
core := NewCore(
|
|
defaultEncoder(),
|
|
zapcore.AddSync(io.Discard),
|
|
zap.NewAtomicLevelAt(zap.InfoLevel),
|
|
)
|
|
filteringCore := newNamespaceFilteringCore(core)
|
|
// MaxBackups left at 0 => DefaultLogMaxBackups fallback.
|
|
require.NoError(t, overrideCoreWithConfig(filteringCore, LogSettings{
|
|
Enabled: true,
|
|
Level: "info",
|
|
File: file,
|
|
}))
|
|
|
|
// Lumberjack prunes asynchronously on write.
|
|
zap.New(filteringCore).Info("trigger mill")
|
|
|
|
require.Eventually(t, func() bool {
|
|
matches, err := filepath.Glob(filepath.Join(dir, "test-*.log"))
|
|
return err == nil && len(matches) <= DefaultLogMaxBackups
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
}
|