tests: a test that sets time.Local does it through testenv.Zone, which first waits out the stand-ins' first runs — their os.Stat reads time.Local, and TestDsh -race alone raced it every run
testenv.StandIns (06420a71) starts each stand-in once in the background on
macOS and records it with os.Stat, which reads time.Local. internal/
sessions' inZone, internal/usage's TestDayFilterDST and the root package's
sessions CLI setup assigned time.Local while those runs could still be
under way. `go test -race -run '^TestDsh$' ./internal/sessions` reported
the race 6 of 6 runs on main (stats_test.go:14 against program.go:140).
testenv.Zone(t, loc) waits on the same WaitGroup RemovePrograms waits on,
sets time.Local and restores it in a Cleanup; the three helpers use it.
The root setup registers Zone before its own Cleanup, so the zone still
goes back after sessions.Reset's index write, as before.
After: the same command 0 races in 6 runs; -race -count=20 of
Dsh|DST over sessions and usage, -race -count=3 of the root Sessions
tests, -race of sessions/testenv/usage/root, go vet, gofmt pass. Tests only.
This commit is contained in:
1 parent
2c2137beb7
commit
395c4bc51e
4 files changed
+22
-9
No files matched your search
@@ -6,13 +6,13 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/yetone/magpie/internal/testenv"
|
||||
)
|
||||
|
||||
// inZone runs the test with the local time zone at a fixed offset.
|
||||
func inZone(t *testing.T, hours int) {
|
||||
old := time.Local
|
||||
time.Local = time.FixedZone("test", hours*3600)
|
||||
t.Cleanup(func() { time.Local = old })
|
||||
testenv.Zone(t, time.FixedZone("test", hours*3600))
|
||||
}
|
||||
|
||||
// ccMsg is a line of a Claude Code reply at a time.
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/yetone/magpie/internal/agentenv"
|
||||
)
|
||||
@@ -216,6 +217,16 @@ func StandIns(bin string, names []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Zone sets time.Local for the test, and puts it back after. It first
|
||||
// waits out the stand-ins' first runs: they os.Stat what ran, which reads
|
||||
// time.Local, so a test that swaps it while they are under way races them.
|
||||
func Zone(t testing.TB, loc *time.Location) {
|
||||
warming.Wait()
|
||||
old := time.Local
|
||||
time.Local = loc
|
||||
t.Cleanup(func() { time.Local = old })
|
||||
}
|
||||
|
||||
// warming is the stand-ins' first runs under way.
|
||||
var warming sync.WaitGroup
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/yetone/magpie/internal/testenv"
|
||||
)
|
||||
|
||||
func TestRequestPageDay(t *testing.T) {
|
||||
@@ -63,9 +65,7 @@ func TestDayFilterDST(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
old := time.Local
|
||||
time.Local = loc
|
||||
t.Cleanup(func() { time.Local = old })
|
||||
testenv.Zone(t, loc)
|
||||
for _, day := range []string{"2026-03-08", "2026-11-01"} {
|
||||
start, err := time.ParseInLocation(time.DateOnly, day, loc)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/yetone/magpie/internal/catalog"
|
||||
"github.com/yetone/magpie/internal/sessions"
|
||||
"github.com/yetone/magpie/internal/settings"
|
||||
"github.com/yetone/magpie/internal/testenv"
|
||||
)
|
||||
|
||||
// sessionsHome is a sandbox HOME with the sessions package's fixtures as
|
||||
@@ -40,8 +41,9 @@ func sessionsHome(t *testing.T) time.Time {
|
||||
}
|
||||
t.Setenv(env, dir)
|
||||
}
|
||||
oldZone, oldPrice := time.Local, sessions.PriceOf
|
||||
time.Local = time.UTC
|
||||
// put back after the Reset below, whose index write reads the zone
|
||||
testenv.Zone(t, time.UTC)
|
||||
oldPrice := sessions.PriceOf
|
||||
sessions.PriceOf = func(_ settings.Settings, m string) (catalog.Price, bool) {
|
||||
switch m {
|
||||
case "claude-opus-5-5":
|
||||
@@ -55,7 +57,7 @@ func sessionsHome(t *testing.T) time.Time {
|
||||
// the index is written behind the page: let that write finish before
|
||||
// the zone it reads (a stat of the file it writes) goes back
|
||||
sessions.Reset()
|
||||
time.Local, sessions.PriceOf = oldZone, oldPrice
|
||||
sessions.PriceOf = oldPrice
|
||||
})
|
||||
sessions.Reset()
|
||||
return time.Date(2026, 9, 28, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
Reference in new issue
Block a user