2026-10-30 15:02:09 +07:00
|
|
|
package common
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestGameClockSharedStateAndReset(t *testing.T) {
|
|
|
|
|
raw, err := os.ReadFile(filepath.Join("..", "..", "server.json"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var cfg RuntimeConfig
|
|
|
|
|
if err := json.Unmarshal(raw, &cfg); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
cfg.GameClock.StateFile = filepath.Join(dir, "clock.json")
|
|
|
|
|
configPath := filepath.Join(dir, "server.json")
|
|
|
|
|
raw, _ = json.Marshal(cfg)
|
|
|
|
|
if err := os.WriteFile(configPath, raw, 0600); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
t.Setenv("GAC_CONFIG", configPath)
|
|
|
|
|
t.Setenv("GAC_GAME_CLOCK_STATE_FILE", cfg.GameClock.StateFile)
|
|
|
|
|
target := time.Date(2025, 1, 2, 3, 4, 5, 0, time.UTC)
|
|
|
|
|
if _, err := SetGameTime(&target); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
now, err := GameTime()
|
|
|
|
|
if err != nil || now.Sub(target) < 0 || now.Sub(target) > time.Second {
|
|
|
|
|
t.Fatalf("target not preserved: %v %v", now, err)
|
|
|
|
|
}
|
|
|
|
|
before, _ := os.ReadFile(cfg.GameClock.StateFile)
|
|
|
|
|
if _, err := ReadGameClock(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
after, _ := os.ReadFile(cfg.GameClock.StateFile)
|
|
|
|
|
if string(before) != string(after) {
|
|
|
|
|
t.Fatal("read rewrote state")
|
|
|
|
|
}
|
2026-11-04 15:59:48 +07:00
|
|
|
begin := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
|
end := begin.Add(24 * time.Hour)
|
|
|
|
|
if _, err := SetGameTimeWindow(begin, end); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
window, err := ReadGameClock()
|
|
|
|
|
if err != nil || window.WindowBeginMS != begin.UnixMilli() || window.WindowEndMS != end.UnixMilli() {
|
|
|
|
|
t.Fatalf("window not saved: %+v %v", window, err)
|
|
|
|
|
}
|
|
|
|
|
// Simulate the clock having advanced beyond the end without waiting a day.
|
|
|
|
|
window.OffsetMS = end.Add(time.Hour).UnixMilli() - time.Now().UnixMilli()
|
|
|
|
|
raw, _ = json.Marshal(window)
|
|
|
|
|
if err := os.WriteFile(cfg.GameClock.StateFile, raw, 0600); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
now, err = GameTime()
|
|
|
|
|
if err != nil || now.Sub(begin.Add(time.Hour)) < 0 || now.Sub(begin.Add(time.Hour)) > time.Second {
|
|
|
|
|
t.Fatalf("window did not wrap: %v %v", now, err)
|
|
|
|
|
}
|
|
|
|
|
before, _ = os.ReadFile(cfg.GameClock.StateFile)
|
|
|
|
|
if _, err := GameTime(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
after, _ = os.ReadFile(cfg.GameClock.StateFile)
|
|
|
|
|
if string(before) != string(after) {
|
|
|
|
|
t.Fatal("window read rewrote state")
|
|
|
|
|
}
|
|
|
|
|
if _, err := SetGameTimeWindow(end, begin); err == nil {
|
|
|
|
|
t.Fatal("invalid window accepted")
|
|
|
|
|
}
|
2026-10-30 15:02:09 +07:00
|
|
|
if _, err := SetGameTime(nil); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
now, err = GameTime()
|
|
|
|
|
if err != nil || time.Since(now).Abs() > time.Second {
|
|
|
|
|
t.Fatalf("real time not restored: %v %v", now, err)
|
|
|
|
|
}
|
2026-11-04 15:59:48 +07:00
|
|
|
window, err = ReadGameClock()
|
|
|
|
|
if err != nil || window.WindowBeginMS != 0 || window.WindowEndMS != 0 {
|
|
|
|
|
t.Fatalf("reset retained window: %+v %v", window, err)
|
|
|
|
|
}
|
2026-10-30 15:02:09 +07:00
|
|
|
if err := os.WriteFile(cfg.GameClock.StateFile, []byte(`{"offset_ms":9223372036854775807}`), 0600); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := GameTime(); err == nil {
|
|
|
|
|
t.Fatal("overflowing offset accepted")
|
|
|
|
|
}
|
|
|
|
|
}
|