## 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.
116 lines
4.1 KiB
C#
116 lines
4.1 KiB
C#
using System;
|
|
using Server.Mobiles;
|
|
using Server.Spells.Necromancy;
|
|
using Xunit;
|
|
|
|
namespace Server.Tests.Spells.Necromancy;
|
|
|
|
[Collection("Sequential UOContent Tests")]
|
|
public class BloodOathSpellTests
|
|
{
|
|
private static Mobile NewMobile()
|
|
{
|
|
var m = new Mobile(World.NewMobile);
|
|
m.DefaultMobileInit();
|
|
return m;
|
|
}
|
|
|
|
// Issue #1690: the real OSI duration is ((SpiritSpeak - Resist) / 8) + 8 seconds.
|
|
// ModernUO previously used /80 (matching the bugged in-game tooltip), which made
|
|
// Spirit Speak almost irrelevant to duration. RunUO/ServUO and the code's own
|
|
// fixed-point comment both use /8.
|
|
// Player caster: Publish 48 resist mitigation does NOT apply; the attacker takes the
|
|
// full reflected (original, un-bonused) damage.
|
|
// Creature caster (Publish 48 / SA+): ((Resist * 10) / 20) + 10 = % of reflected damage resisted.
|
|
[Theory]
|
|
[InlineData(40, 0.0, 36)] // 10% resisted -> 40 * 0.90
|
|
[InlineData(40, 100.0, 16)] // 60% resisted -> 40 * 0.40
|
|
[InlineData(40, 120.0, 12)] // 70% resisted -> 40 * 0.30
|
|
public void ComputeReflectedDamage_WithMitigation_ReducesByResist(int dmg, double resist, int expected)
|
|
{
|
|
Assert.Equal(expected, BloodOathSpell.ComputeReflectedDamage(dmg, resist, applyResistMitigation: true));
|
|
}
|
|
|
|
// The cursed target reflects to the caster; the caster attacking other mobiles must not reflect.
|
|
[Fact]
|
|
public void RegisterOath_BindsTargetToCaster_NotCasterToSelf()
|
|
{
|
|
var caster = NewMobile();
|
|
var target = NewMobile();
|
|
|
|
BloodOathSpell.RegisterOath(caster, target, TimeSpan.FromMinutes(5));
|
|
|
|
Assert.Equal(caster, BloodOathSpell.GetBloodOath(target));
|
|
Assert.Null(BloodOathSpell.GetBloodOath(caster));
|
|
|
|
BloodOathSpell.RemoveCurse(target);
|
|
caster.Delete();
|
|
target.Delete();
|
|
}
|
|
|
|
// Death/delete of either party removes the oath, so RemoveCurse must resolve from either
|
|
// the caster or the target key (the death hooks call it with `this`).
|
|
[Fact]
|
|
public void RemoveCurse_ByCaster_ClearsBothEntries()
|
|
{
|
|
var caster = NewMobile();
|
|
var target = NewMobile();
|
|
BloodOathSpell.RegisterOath(caster, target, TimeSpan.FromMinutes(5));
|
|
|
|
Assert.True(BloodOathSpell.RemoveCurse(caster));
|
|
|
|
Assert.Null(BloodOathSpell.GetBloodOath(target));
|
|
Assert.False(BloodOathSpell.RemoveCurse(target)); // already removed
|
|
|
|
caster.Delete();
|
|
target.Delete();
|
|
}
|
|
|
|
// Issue #1690: death/delete of either party must break the oath immediately. The oath is wired
|
|
// to the central PlayerMobile/BaseCreature death+delete events instead of a polling timer.
|
|
[Fact]
|
|
public void PlayerDeletedEvent_BreaksOath()
|
|
{
|
|
var caster = new PlayerMobile(World.NewMobile);
|
|
caster.DefaultMobileInit();
|
|
var target = new PlayerMobile(World.NewMobile);
|
|
target.DefaultMobileInit();
|
|
|
|
BloodOathSpell.RegisterOath(caster, target, TimeSpan.FromMinutes(5));
|
|
|
|
PlayerMobile.PlayerDeletedEvent(caster); // central handler breaks the oath from the caster side
|
|
|
|
Assert.Null(BloodOathSpell.GetBloodOath(target));
|
|
Assert.False(BloodOathSpell.RemoveCurse(target));
|
|
|
|
caster.Delete();
|
|
target.Delete();
|
|
}
|
|
|
|
[Fact]
|
|
public void CreatureDeletedEvent_BreaksOath()
|
|
{
|
|
var caster = new PlayerMobile(World.NewMobile);
|
|
caster.DefaultMobileInit();
|
|
var target = new TestCreature(World.NewMobile);
|
|
target.DefaultMobileInit();
|
|
|
|
BloodOathSpell.RegisterOath(caster, target, TimeSpan.FromMinutes(5));
|
|
|
|
CreatureEvents.CreatureDeletedEvent(target); // central handler breaks the oath from the target side
|
|
|
|
Assert.Null(BloodOathSpell.GetBloodOath(target));
|
|
Assert.False(BloodOathSpell.RemoveCurse(caster));
|
|
|
|
caster.Delete();
|
|
target.Delete();
|
|
}
|
|
|
|
private class TestCreature : BaseCreature
|
|
{
|
|
// Serial ctor skips AI/speed-table setup, which the test fixture does not configure.
|
|
public TestCreature(Serial serial) : base(serial)
|
|
{
|
|
}
|
|
}
|
|
}
|