Decompose test functions to clear complexity findings (step 7)

TestSaveRestoreRoundTrip, TestNewGameRandomizesAppearances, and
TestNewLevelInvariants split their assertion blocks into t.Helper()
sub-checks. The lint run is now completely clean (0 issues).
This commit is contained in:
2026-07-22 22:11:10 +07:00
parent 5c14a829aa
commit 5b7e258195
3 changed files with 157 additions and 82 deletions

View File

@@ -38,75 +38,106 @@ func TestNewLevelInvariants(t *testing.T) {
for _, seed := range []int32{1, 12345, 2026, 99999} { for _, seed := range []int32{1, 12345, 2026, 99999} {
g := genLevel(t, seed) g := genLevel(t, seed)
// The staircase is somewhere real. checkHeroPlacement(t, g, seed)
st := g.Level.Stairs checkRoomsDrawn(t, g, seed)
if g.Level.Char(st.Y, st.X) != Stairs { checkMonstersPlaced(t, g, seed)
t.Errorf("seed %d: no staircase at recorded stairs position", seed) checkObjectsPlaced(t, g, seed)
checkStartingKit(t, g, seed)
}
}
// checkHeroPlacement verifies the staircase and hero landed on valid,
// unoccupied cells.
func checkHeroPlacement(t *testing.T, g *RogueGame, seed int32) {
t.Helper()
// The staircase is somewhere real.
st := g.Level.Stairs
if g.Level.Char(st.Y, st.X) != Stairs {
t.Errorf("seed %d: no staircase at recorded stairs position", seed)
}
// The hero stands on a walkable, monster-free cell.
hp := g.Player.Pos
if !stepOk(g.Level.Char(hp.Y, hp.X)) {
t.Errorf("seed %d: hero on unwalkable cell %q", seed,
g.Level.Char(hp.Y, hp.X))
}
if g.Level.MonsterAt(hp.Y, hp.X) != nil {
t.Errorf("seed %d: hero standing on a monster", seed)
}
if g.Player.Room == nil {
t.Errorf("seed %d: hero not in any room", seed)
}
}
// checkRoomsDrawn verifies rooms and floor/passages appear on the map.
func checkRoomsDrawn(t *testing.T, g *RogueGame, seed int32) {
t.Helper()
m := renderMap(g)
if !strings.Contains(m, "|") || !strings.Contains(m, "-") {
t.Errorf("seed %d: no room walls drawn", seed)
}
if !strings.Contains(m, ".") && !strings.Contains(m, "#") {
t.Errorf("seed %d: no floor or passages drawn", seed)
}
}
// checkMonstersPlaced verifies every monster is indexed on the map and
// placed in a room.
func checkMonstersPlaced(t *testing.T, g *RogueGame, seed int32) {
t.Helper()
for _, mon := range g.Level.Monsters {
if g.Level.MonsterAt(mon.Pos.Y, mon.Pos.X) != mon {
t.Errorf("seed %d: monster %c not indexed at its position",
seed, mon.Type)
} }
// The hero stands on a walkable, monster-free cell. if mon.Room == nil {
hp := g.Player.Pos t.Errorf("seed %d: monster %c has no room", seed, mon.Type)
if !stepOk(g.Level.Char(hp.Y, hp.X)) {
t.Errorf("seed %d: hero on unwalkable cell %q", seed,
g.Level.Char(hp.Y, hp.X))
} }
}
}
if g.Level.MonsterAt(hp.Y, hp.X) != nil { // checkObjectsPlaced verifies every level object sits on a cell
t.Errorf("seed %d: hero standing on a monster", seed) // displaying its type (items can share cells only with monsters standing
// on them).
func checkObjectsPlaced(t *testing.T, g *RogueGame, seed int32) {
t.Helper()
for _, obj := range g.Level.Objects {
ch := g.Level.Char(obj.Pos.Y, obj.Pos.X)
if ch != obj.Kind.Glyph() &&
g.Level.MonsterAt(obj.Pos.Y, obj.Pos.X) == nil {
t.Errorf("seed %d: object %v at (%d,%d) but map shows %q",
seed, obj.Kind, obj.Pos.Y, obj.Pos.X, ch)
} }
}
}
if g.Player.Room == nil { // checkStartingKit verifies the player has her starting kit: food, armor,
t.Errorf("seed %d: hero not in any room", seed) // mace, bow, arrows.
} func checkStartingKit(t *testing.T, g *RogueGame, seed int32) {
t.Helper()
// Some rooms exist and are drawn. if len(g.Player.Pack) != 5 {
m := renderMap(g) t.Errorf("seed %d: starting pack has %d items, want 5",
if !strings.Contains(m, "|") || !strings.Contains(m, "-") { seed, len(g.Player.Pack))
t.Errorf("seed %d: no room walls drawn", seed) }
}
if !strings.Contains(m, ".") && !strings.Contains(m, "#") { if g.Player.CurWeapon == nil ||
t.Errorf("seed %d: no floor or passages drawn", seed) g.Player.CurWeapon.WeaponKind() != WeaponMace {
} t.Errorf("seed %d: not wielding the starting mace", seed)
}
// Every monster is indexed on the map and placed in a room. if g.Player.CurArmor == nil ||
for _, mon := range g.Level.Monsters { g.Player.CurArmor.ArmorKind() != ArmorRingMail {
if g.Level.MonsterAt(mon.Pos.Y, mon.Pos.X) != mon { t.Errorf("seed %d: not wearing the starting ring mail", seed)
t.Errorf("seed %d: monster %c not indexed at its position",
seed, mon.Type)
}
if mon.Room == nil {
t.Errorf("seed %d: monster %c has no room", seed, mon.Type)
}
}
// Every level object sits on a cell displaying its type (items can
// share cells only with monsters standing on them).
for _, obj := range g.Level.Objects {
ch := g.Level.Char(obj.Pos.Y, obj.Pos.X)
if ch != obj.Kind.Glyph() &&
g.Level.MonsterAt(obj.Pos.Y, obj.Pos.X) == nil {
t.Errorf("seed %d: object %v at (%d,%d) but map shows %q",
seed, obj.Kind, obj.Pos.Y, obj.Pos.X, ch)
}
}
// The player has her starting kit: food, armor, mace, bow, arrows.
if len(g.Player.Pack) != 5 {
t.Errorf("seed %d: starting pack has %d items, want 5",
seed, len(g.Player.Pack))
}
if g.Player.CurWeapon == nil ||
g.Player.CurWeapon.WeaponKind() != WeaponMace {
t.Errorf("seed %d: not wielding the starting mace", seed)
}
if g.Player.CurArmor == nil ||
g.Player.CurArmor.ArmorKind() != ArmorRingMail {
t.Errorf("seed %d: not wearing the starting ring mail", seed)
}
} }
} }

View File

@@ -39,6 +39,15 @@ func TestSaveRestoreRoundTrip(t *testing.T) {
t.Error("save file not deleted on restore (C anti-restart rule)") t.Error("save file not deleted on restore (C anti-restart rule)")
} }
checkRestoredState(t, g, h)
checkRestoredMonsters(t, g, h)
checkEquipmentAliasing(t, g, h)
}
// checkRestoredState verifies the scalar state survived the round trip.
func checkRestoredState(t *testing.T, g, h *RogueGame) {
t.Helper()
if h.Player.Purse != 123 || h.Player.FoodLeft != 777 { if h.Player.Purse != 123 || h.Player.FoodLeft != 777 {
t.Errorf("player state lost: purse=%d food=%d", t.Errorf("player state lost: purse=%d food=%d",
h.Player.Purse, h.Player.FoodLeft) h.Player.Purse, h.Player.FoodLeft)
@@ -67,28 +76,41 @@ func TestSaveRestoreRoundTrip(t *testing.T) {
if renderMap(h) != renderMap(g) { if renderMap(h) != renderMap(g) {
t.Error("restored level map differs") t.Error("restored level map differs")
} }
}
// checkRestoredMonsters verifies the monster list and its pointer fixups
// survived the round trip.
func checkRestoredMonsters(t *testing.T, g, h *RogueGame) {
t.Helper()
if len(h.Level.Monsters) != len(g.Level.Monsters) { if len(h.Level.Monsters) != len(g.Level.Monsters) {
t.Fatalf("monster count %d != %d", t.Fatalf("monster count %d != %d",
len(h.Level.Monsters), len(g.Level.Monsters)) len(h.Level.Monsters), len(g.Level.Monsters))
} }
if len(g.Level.Monsters) > 0 { if len(g.Level.Monsters) == 0 {
m := h.Level.Monsters[0] return
if m.Dest != &h.Player.Pos {
t.Error("monster chase target not re-aliased to the hero")
}
if h.Level.MonsterAt(m.Pos.Y, m.Pos.X) != m {
t.Error("map monster index not rebuilt")
}
if m.Room == nil {
t.Error("monster room pointer not rebuilt")
}
} }
// Equipment aliasing: the wielded mace must be the same *Object as the
// one in the pack. m := h.Level.Monsters[0]
if m.Dest != &h.Player.Pos {
t.Error("monster chase target not re-aliased to the hero")
}
if h.Level.MonsterAt(m.Pos.Y, m.Pos.X) != m {
t.Error("map monster index not rebuilt")
}
if m.Room == nil {
t.Error("monster room pointer not rebuilt")
}
}
// checkEquipmentAliasing verifies the wielded mace is the same *Object as
// the one in the pack.
func checkEquipmentAliasing(t *testing.T, g, h *RogueGame) {
t.Helper()
st := g.snapshot() st := g.snapshot()
t.Logf("snapshot indices: weapon=%d armor=%d rings=%v packlen=%d", t.Logf("snapshot indices: weapon=%d armor=%d rings=%v packlen=%d",
st.Player.CurWeapon, st.Player.CurArmor, st.Player.CurRing, st.Player.CurWeapon, st.Player.CurArmor, st.Player.CurRing,

View File

@@ -48,6 +48,22 @@ func TestInitProbsCumulative(t *testing.T) {
func TestNewGameRandomizesAppearances(t *testing.T) { func TestNewGameRandomizesAppearances(t *testing.T) {
g := NewGame(Config{Seed: 12345}) g := NewGame(Config{Seed: 12345})
checkPotionColors(t, g)
checkScrollNames(t, g)
checkWandMaterials(t, g)
// Determinism: same seed, same appearances.
h := NewGame(Config{Seed: 12345})
if h.Items != g.Items {
t.Error("two games with the same seed produced different item lore")
}
}
// checkPotionColors verifies every potion has a distinct color.
func checkPotionColors(t *testing.T, g *RogueGame) {
t.Helper()
seen := map[string]bool{} seen := map[string]bool{}
for i, c := range g.Items.PotColors { for i, c := range g.Items.PotColors {
@@ -61,6 +77,12 @@ func TestNewGameRandomizesAppearances(t *testing.T) {
seen[c] = true seen[c] = true
} }
}
// checkScrollNames verifies every scroll has a name within the C buffer
// limit.
func checkScrollNames(t *testing.T, g *RogueGame) {
t.Helper()
for i, n := range g.Items.ScrNames { for i, n := range g.Items.ScrNames {
if n == "" { if n == "" {
@@ -71,9 +93,15 @@ func TestNewGameRandomizesAppearances(t *testing.T) {
t.Errorf("scroll name %q longer than C buffer allows", n) t.Errorf("scroll name %q longer than C buffer allows", n)
} }
} }
}
// checkWandMaterials verifies every stick has a wand/staff type and a
// material.
func checkWandMaterials(t *testing.T, g *RogueGame) {
t.Helper()
for i := range g.Items.WandType { for i := range g.Items.WandType {
if g.Items.WandType[i] != "wand" && g.Items.WandType[i] != "staff" { if g.Items.WandType[i] != wandName && g.Items.WandType[i] != staffName {
t.Errorf("stick %d has type %q", i, g.Items.WandType[i]) t.Errorf("stick %d has type %q", i, g.Items.WandType[i])
} }
@@ -81,12 +109,6 @@ func TestNewGameRandomizesAppearances(t *testing.T) {
t.Errorf("stick %d has no material", i) t.Errorf("stick %d has no material", i)
} }
} }
// Determinism: same seed, same appearances.
h := NewGame(Config{Seed: 12345})
if h.Items != g.Items {
t.Error("two games with the same seed produced different item lore")
}
} }
func TestMonsterTable(t *testing.T) { func TestMonsterTable(t *testing.T) {