## Summary - `AnchoredItemSerializationTests.LastMovedAndDecayReset_RoundTripExactly` never deleted its deserialized copy. `Item.Deserialize` enters the item into its map's sectors, so the copy stayed live on Felucca at (121,100). `ItemEnumerator_FiltersByBoundsAndOrder`, `ItemByDistanceEnumerator_Bounds_FindsItemsInBounds` and `ItemByDistanceEnumerator_Bounds_MakeBoundsInclusive` scan that area and found an extra item whenever the serialization test ran before them, so they failed depending on test order. The copy is now deleted in a `finally`. - Bump MailKit 4.17.0 → 4.18.0 and Microsoft.NET.Test.Sdk 18.10.0 → 18.10.1. ## Test plan - [x] Probe test: a deserialized copy shows up in `GetItemsInBounds` (reproduced), and after the fix the area is empty - [x] Server.Tests: 907/907 pass - [x] UOContent.Tests: 1126 pass, 2 skipped - [x] Solution builds with 0 errors
85 lines
2.5 KiB
C#
85 lines
2.5 KiB
C#
using System;
|
|
using Xunit;
|
|
|
|
namespace Server.Tests;
|
|
|
|
[Collection("Sequential Server Tests")]
|
|
public class AnchoredItemSerializationTests
|
|
{
|
|
private static byte[] SerializeItem(Item item)
|
|
{
|
|
var writer = new BufferWriter(new byte[256], true);
|
|
item.Serialize(writer);
|
|
return writer.Buffer[..(int)writer.Position];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Item v11 stores LastMoved and DecayResetTime as anchored time: the serialized bytes
|
|
/// are a function of item state only, not of when the save runs. Pre-v11 stored
|
|
/// minutes-since-moved and delta time, which rewrote the bytes on every save.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ItemBytes_AreStable_AcrossSavesAtDifferentTimes()
|
|
{
|
|
var start = Core._now;
|
|
|
|
try
|
|
{
|
|
var item = new Item(0x1F13);
|
|
item.MoveToWorld(new Point3D(120, 100, 0), Map.Felucca);
|
|
item.RestartDecay();
|
|
|
|
var first = SerializeItem(item);
|
|
|
|
// A save hours later, with no state change, must produce identical bytes.
|
|
Core._now = start + TimeSpan.FromHours(5);
|
|
var second = SerializeItem(item);
|
|
|
|
Assert.Equal(first, second);
|
|
|
|
item.Delete();
|
|
}
|
|
finally
|
|
{
|
|
Core._now = start;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pre-v11 LastMoved was stored at whole-minute precision relative to the save time and
|
|
/// could never round-trip exactly. Anchored storage is absolute and exact.
|
|
/// </summary>
|
|
[Fact]
|
|
public void LastMovedAndDecayReset_RoundTripExactly()
|
|
{
|
|
var item = new Item(0x1F13);
|
|
Item restored = null;
|
|
|
|
try
|
|
{
|
|
item.MoveToWorld(new Point3D(121, 100, 0), Map.Felucca);
|
|
|
|
// Sub-minute precision that the old minutes encoding would have destroyed.
|
|
var moved = Core.Now - TimeSpan.FromSeconds(90.5) - TimeSpan.FromMilliseconds(123);
|
|
item.LastMoved = moved;
|
|
|
|
item.RestartDecay();
|
|
var decayReset = item.DecayResetTime;
|
|
Assert.NotEqual(default(DateTime), decayReset);
|
|
|
|
var bytes = SerializeItem(item);
|
|
|
|
// Deserialize enters the copy into the map's sectors, so it must be deleted too.
|
|
restored = new Item((Serial)0x7ffff123u);
|
|
restored.Deserialize(new BufferReader(bytes));
|
|
|
|
Assert.Equal(moved, restored.LastMoved);
|
|
Assert.Equal(decayReset, restored.DecayResetTime);
|
|
}
|
|
finally
|
|
{
|
|
item.Delete();
|
|
restored?.Delete();
|
|
}
|
|
}
|
|
}
|