## Summary Many tests proved a specific change when it landed but don't need to run on every CI build forever. This removes **202 test methods and 9 files: 313 test cases, 2,247 → 1,934**. UOContent.Tests drops from about 14s to about 5s locally. Removed: - **Tautologies.** Asserting a constant, a constructor readback or a trivial getter, or testing BCL behavior. - **Exact duplicates** of a surviving test on the same code path. - **Obsolete regressions** whose failure can no longer be expressed. - **Game-content values** shard owners customize (era drop rates, loot and skill tables, single-creature tuning). Each customization would otherwise force a test fix. ## How it was decided 1. Every test was classified, with a reason, by a per-shard sweep over all 287 test files. 2. Each proposed deletion was then checked by a second, adversarial reviewer against the surviving tests and the production code. 3. Anything that was the only coverage of a separate code path was **kept**, even where it looked like a duplicate. Examples: the unsigned `SpanReader`/`SpanWriter` methods, the at-location versus bounds enumerators, null-map enumerables, the security filters, archive-journal state transitions. About 130 proposed deletions were rejected this way. Whole files removed: - `FishingTests`, apart from 3 tests: the map-edge scan, serpent cleanup, and SOS chest consumption. The SOS test now stops at the first chest instead of measuring a catch rate. The multi-million-cast rate simulations are gone. - `LordOaksTests` and `MeerEnragedTests` (single-creature tests). - `AutoArchiveHelperTests`, `JsonDiscoverableTypeAttributeTests`, `DecimalSerializationTests` and `EnumConversionTests` (tautologies, or tests of their own local helpers). - `NetworkStatsTests`. - `MigratedDataLoadTests` (a subset of `AllSpawnFilesLoadTests`). - `SpawnerFixtureCapture`: a capture tool, not a test, and its `version == 12` assert can no longer pass. The `.bin` fixtures it produced stay. ## Tests - Server.Tests: 811 pass. - UOContent.Tests: 1,123 pass. A follow-up PR, stacked on this one, fixes how the remaining tests behave: shared state, CI-vacuous tests, harness duplication.
288 lines
8.7 KiB
C#
288 lines
8.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Server;
|
|
using Server.Mobiles;
|
|
using Xunit;
|
|
|
|
namespace UOContent.Tests.Mobiles.AI;
|
|
|
|
// Pet order handlers own the speed clocks; combat chases and herding keep their own pacing.
|
|
[Collection("Sequential UOContent Tests")]
|
|
public class PetPacingTests : IDisposable
|
|
{
|
|
private readonly List<Mobile> _created = new();
|
|
|
|
private (PlayerMobile master, PetTestStub pet) Spawn(Point3D masterLoc, Point3D petLoc)
|
|
{
|
|
var pair = PetTestSetup.SpawnControlledPet(masterLoc, petLoc);
|
|
_created.Add(pair.master);
|
|
_created.Add(pair.pet);
|
|
return pair;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (var m in _created)
|
|
{
|
|
m?.Delete();
|
|
}
|
|
|
|
_created.Clear();
|
|
}
|
|
|
|
// Movement orders run active, resting orders run passive; the move clock follows.
|
|
[Fact]
|
|
public void OrderIssue_SetsThinkClock()
|
|
{
|
|
var (master, pet) = Spawn(new Point3D(1000, 1000, 0), new Point3D(1001, 1000, 0));
|
|
pet.SetMoveSpeed(0.3, 0.9);
|
|
pet.SetCurrentSpeedToPassive();
|
|
|
|
pet.ControlOrder = OrderType.Come;
|
|
Assert.Equal(0.2, pet.CurrentSpeed);
|
|
Assert.Equal(0.3, pet.CurrentMoveSpeed); // verbatim active -> activeMove
|
|
|
|
pet.ControlOrder = OrderType.Stay;
|
|
Assert.Equal(0.4, pet.CurrentSpeed);
|
|
Assert.Equal(0.9, pet.CurrentMoveSpeed);
|
|
|
|
pet.ControlTarget = master;
|
|
pet.ControlOrder = OrderType.Follow;
|
|
Assert.Equal(0.2, pet.CurrentSpeed);
|
|
|
|
pet.ControlOrder = OrderType.Guard;
|
|
Assert.Equal(0.2, pet.CurrentSpeed);
|
|
}
|
|
|
|
// The follow pace caps the step delay and leaves the think clock on the active value.
|
|
[Fact]
|
|
public void FollowMaster_PacesStepsWithoutInflatingTheThinkClock()
|
|
{
|
|
var (master, pet) = Spawn(new Point3D(1000, 1000, 0), new Point3D(1001, 1000, 0));
|
|
pet.SetMoveSpeed(0.3, 0.9);
|
|
|
|
pet.ControlTarget = master;
|
|
pet.ControlOrder = OrderType.Follow; // fixture era is EJ
|
|
|
|
Assert.Equal(0.2, pet.CurrentSpeed); // active think, not the follow pace
|
|
Assert.Equal(0.1, pet.CurrentMoveSpeed); // capped at the follow pace
|
|
}
|
|
|
|
// A creature configured faster than the follow pace keeps its own.
|
|
[Fact]
|
|
public void FollowMaster_KeepsAFasterConfiguredPace()
|
|
{
|
|
var (master, pet) = Spawn(new Point3D(1000, 1000, 0), new Point3D(1001, 1000, 0));
|
|
pet.SetMoveSpeed(0.05, 0.9);
|
|
|
|
pet.ControlTarget = master;
|
|
pet.ControlOrder = OrderType.Follow;
|
|
|
|
Assert.Equal(0.05, pet.CurrentMoveSpeed);
|
|
}
|
|
|
|
// The move-clock override survives the order: it is capped while following, not overwritten.
|
|
[Fact]
|
|
public void FollowMaster_LeavesTheConfiguredMoveClockAlone()
|
|
{
|
|
var (master, pet) = Spawn(new Point3D(1000, 1000, 0), new Point3D(1001, 1000, 0));
|
|
pet.SetMoveSpeed(0.3, 0.9);
|
|
|
|
pet.ControlTarget = master;
|
|
pet.ControlOrder = OrderType.Follow;
|
|
pet.AIObject.Obey();
|
|
pet.ControlOrder = OrderType.Stay;
|
|
|
|
Assert.Equal(0.3, pet.ActiveMoveSpeed);
|
|
Assert.Equal(0.9, pet.PassiveMoveSpeed);
|
|
Assert.Equal(0.9, pet.CurrentMoveSpeed); // resting on its own passive pace again
|
|
}
|
|
|
|
// Pre-AOS pets follow at their own pace; nothing caps them.
|
|
private sealed class SprintingPet : PetTestStub
|
|
{
|
|
public override double FollowMoveSpeed => 0.125;
|
|
}
|
|
|
|
// A shard paces follows in any era by overriding the property, not by patching the AI.
|
|
[Fact]
|
|
public void FollowMoveSpeedOverride_PacesFollowsInAnyEra()
|
|
{
|
|
var previous = Core.Expansion;
|
|
|
|
try
|
|
{
|
|
Core.Expansion = Expansion.UOR;
|
|
var master = new PlayerMobile(World.NewMobile);
|
|
master.DefaultMobileInit();
|
|
master.MoveToWorld(new Point3D(1000, 1000, 0), Map.Felucca);
|
|
_created.Add(master);
|
|
|
|
var pet = new SprintingPet();
|
|
pet.MoveToWorld(new Point3D(1001, 1000, 0), Map.Felucca);
|
|
pet.SetControlMaster(master);
|
|
_created.Add(pet);
|
|
pet.SetMoveSpeed(0.3, 0.9);
|
|
|
|
pet.ControlTarget = master;
|
|
pet.ControlOrder = OrderType.Follow;
|
|
|
|
Assert.Equal(0.125, pet.CurrentMoveSpeed);
|
|
}
|
|
finally
|
|
{
|
|
Core.Expansion = previous;
|
|
}
|
|
}
|
|
|
|
// A guarding pet outside guard range closes at the follow pace, thinking on its active clock.
|
|
[Fact]
|
|
public void GuardReturn_PacesStepsWithoutInflatingTheThinkClock()
|
|
{
|
|
var (_, pet) = Spawn(new Point3D(1000, 1000, 0), new Point3D(1006, 1000, 0));
|
|
pet.SetMoveSpeed(0.3, 0.9);
|
|
|
|
pet.ControlOrder = OrderType.Guard;
|
|
|
|
Assert.Equal(0.2, pet.CurrentSpeed);
|
|
Assert.Equal(0.1, pet.CurrentMoveSpeed);
|
|
}
|
|
|
|
// Obeying the follow order must not write the pace into either clock.
|
|
[Fact]
|
|
public void FollowMaster_ObeyKeepsTheThinkClockActive()
|
|
{
|
|
var (master, pet) = Spawn(new Point3D(1000, 1000, 0), new Point3D(1001, 1000, 0));
|
|
pet.SetMoveSpeed(0.3, 0.9);
|
|
pet.AIObject.AITimer?.Stop();
|
|
|
|
pet.ControlTarget = master;
|
|
pet.ControlOrder = OrderType.Follow; // fixture era is EJ
|
|
pet.AIObject.Obey();
|
|
|
|
Assert.Equal(0.2, pet.CurrentSpeed);
|
|
Assert.Equal(0.1, pet.CurrentMoveSpeed);
|
|
}
|
|
|
|
// At the master's side a guarding pet stays active: no stale-warmode passive, no sprint.
|
|
[Fact]
|
|
public void GuardAtMastersSide_IsActive()
|
|
{
|
|
var (_, pet) = Spawn(new Point3D(1000, 1000, 0), new Point3D(1001, 1000, 0));
|
|
pet.SetMoveSpeed(0.3, 0.9);
|
|
pet.AIObject.AITimer?.Stop();
|
|
pet.SetCurrentSpeedToPassive();
|
|
|
|
pet.ControlOrder = OrderType.Guard;
|
|
pet.AIObject.Obey(); // nothing to guard against, master adjacent
|
|
|
|
Assert.Equal(0.2, pet.CurrentSpeed);
|
|
Assert.Equal(0.3, pet.CurrentMoveSpeed);
|
|
}
|
|
|
|
// A pet chasing a combatant keeps the move table.
|
|
// Herding overrides order pacing.
|
|
private sealed class ThinkProbe : PetTestStub
|
|
{
|
|
public int Thinks;
|
|
|
|
public override void OnThink()
|
|
{
|
|
Thinks++;
|
|
base.OnThink();
|
|
}
|
|
}
|
|
|
|
private (PlayerMobile master, ThinkProbe pet) SpawnProbe()
|
|
{
|
|
var master = new PlayerMobile(World.NewMobile);
|
|
master.DefaultMobileInit();
|
|
master.MoveToWorld(new Point3D(1000, 1000, 0), Map.Felucca);
|
|
_created.Add(master);
|
|
|
|
var pet = new ThinkProbe();
|
|
pet.MoveToWorld(new Point3D(1001, 1000, 0), Map.Felucca);
|
|
pet.SetControlMaster(master);
|
|
_created.Add(pet);
|
|
|
|
return (master, pet);
|
|
}
|
|
|
|
// Advances time in 8ms lockstep so the wheel and Core.TickCount stay in sync.
|
|
private static void RunFor(long ms)
|
|
{
|
|
var deadline = Core._tickCount + ms;
|
|
|
|
while (Core._tickCount < deadline)
|
|
{
|
|
Core._tickCount += 8;
|
|
Timer.Slice(Core._tickCount);
|
|
}
|
|
}
|
|
|
|
private static bool RunUntil(Func<bool> condition, long maxMs)
|
|
{
|
|
var deadline = Core._tickCount + maxMs;
|
|
|
|
while (Core._tickCount < deadline)
|
|
{
|
|
if (condition())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Core._tickCount += 8;
|
|
Timer.Slice(Core._tickCount);
|
|
}
|
|
|
|
return condition();
|
|
}
|
|
|
|
// Runs past the spawn stagger; returns right after a think with the next 0.4s away.
|
|
private ThinkProbe SettledProbe(out PlayerMobile master)
|
|
{
|
|
Core._tickCount = 0;
|
|
Timer.Init(0);
|
|
|
|
var (m, pet) = SpawnProbe();
|
|
master = m;
|
|
pet.ForceIdle = true; // no wandering; pure cadence
|
|
pet.ControlOrder = OrderType.Stay;
|
|
|
|
var settled = RunUntil(() => pet.Thinks >= 2, 8000);
|
|
Assert.True(settled, "the AI must reach a steady think cadence");
|
|
|
|
return pet;
|
|
}
|
|
|
|
[Fact]
|
|
public void OrderChange_WakesStaleThinkTimer()
|
|
{
|
|
var pet = SettledProbe(out var master);
|
|
var thinksBefore = pet.Thinks;
|
|
|
|
RunFor(200); // mid-wait, next think ~200ms out
|
|
Assert.Equal(thinksBefore, pet.Thinks);
|
|
|
|
pet.ControlTarget = master;
|
|
pet.ControlOrder = OrderType.Follow;
|
|
|
|
RunFor(80);
|
|
Assert.True(pet.Thinks > thinksBefore, "a fresh order must wake the AI promptly");
|
|
}
|
|
|
|
[Fact]
|
|
public void SpeedUp_ReschedulesPendingWake()
|
|
{
|
|
var pet = SettledProbe(out _);
|
|
var thinksBefore = pet.Thinks;
|
|
|
|
RunFor(200); // mid-wait, next think ~200ms out
|
|
Assert.Equal(thinksBefore, pet.Thinks);
|
|
|
|
pet.CurrentSpeed = 0.1;
|
|
|
|
RunFor(120);
|
|
Assert.True(pet.Thinks > thinksBefore, "a speed-up must reschedule the pending wake");
|
|
}
|
|
}
|