mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-15 23:02:34 +02:00
* Isolate net messages in integration tests. Integration tests don't use Lidgren to connect client and send, instead they just use some in-process channels to communicate. Because of this, the original implementation of net messages *directly* passed the net message instances between client and server instances. This caused issues whenever content would mutate data in a NetMessage after it "passed through". Now we run the messages through WriteToBuffer() and ReadFromBuffer() so they pass through binary serialization. This means there's no more implicit sharing of the objects. Note that this requires some trickery: Lidgren's message types have internal constructors. Really ideally we'd change the engine to make this more testable... but that's a content breaking change. Instead I just added InternalsVisibleTo to Lidgren so we can mess with it. We maintain the library ourselves anyways I can do what I want. Fixes #4836 * Register Robust.UnitTesting as assembly for reflection. This is necessary so that serialized types in the assembly can be picked up by NetSerializer. Have to disable automatic reflection on all entity systems/components that tests register manually right now, because otherwise tests break. * Stop shallow cloning specific net messages in integration tests. This isn't necessary anymore now that we have a thorough fix. * Wow I really forgot to copy-paste that line to the other side huh. * Add test that serializer hash matches. * Another test one I missed earlier. * Changelog
290 lines
11 KiB
C#
290 lines
11 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Threading.Tasks;
|
|
using NUnit.Framework;
|
|
using Robust.Shared;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Reflection;
|
|
using Robust.Shared.Serialization.Manager.Attributes;
|
|
|
|
namespace Robust.UnitTesting.Shared.GameState;
|
|
|
|
public sealed partial class ComponentStateTests : RobustIntegrationTest
|
|
{
|
|
/// <summary>
|
|
/// This tests performs a basic check to ensure that there is no issue with entity states referencing other
|
|
/// entities that the client is not yet aware of. It does this by spawning two entities that reference each other,
|
|
/// and then ensuring that they get sent to the client one at a time.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task UnknownEntityTest()
|
|
{
|
|
// Setup auto-comp-states. I hate this. Someone please fix reflection in RobustIntegrationTest
|
|
var serverOpts = new ServerIntegrationOptions { Pool = false };
|
|
var clientOpts = new ClientIntegrationOptions { Pool = false };
|
|
var server = StartServer(serverOpts);
|
|
var client = StartClient(clientOpts);
|
|
|
|
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
|
var netMan = client.ResolveDependency<IClientNetManager>();
|
|
var xforms = server.System<SharedTransformSystem>();
|
|
|
|
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
|
client.Post(() => netMan.ClientConnect(null!, 0, null!));
|
|
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true));
|
|
|
|
// Set up map.
|
|
EntityUid map = default;
|
|
await server.WaitPost(() =>
|
|
{
|
|
var mapId = server.MapMan.CreateMap();
|
|
map = server.MapMan.GetMapEntityId(mapId);
|
|
});
|
|
|
|
await RunTicks();
|
|
|
|
// Spawn entities
|
|
var coordsA = new EntityCoordinates(map, default);
|
|
var coordsB = new EntityCoordinates(map, new Vector2(100, 100));
|
|
EntityUid player = default;
|
|
EntityUid cPlayer = default;
|
|
EntityUid serverEntA = default;
|
|
EntityUid serverEntB = default;
|
|
NetEntity serverNetA = default;
|
|
NetEntity serverNetB = default;
|
|
|
|
await server.WaitPost(() =>
|
|
{
|
|
// Attach player.
|
|
player = server.EntMan.Spawn();
|
|
var session = server.PlayerMan.Sessions.First();
|
|
server.PlayerMan.SetAttachedEntity(session, player);
|
|
server.PlayerMan.JoinGame(session);
|
|
|
|
// Spawn test entities.
|
|
serverEntA = server.EntMan.SpawnAttachedTo(null, coordsA);
|
|
serverEntB = server.EntMan.SpawnAttachedTo(null, coordsB);
|
|
serverNetA = server.EntMan.GetNetEntity(serverEntA);
|
|
serverNetB = server.EntMan.GetNetEntity(serverEntB);
|
|
|
|
// Setup components
|
|
var cmp = server.EntMan.EnsureComponent<UnknownEntityTestComponent>(serverEntA);
|
|
cmp.Other = serverEntB;
|
|
server.EntMan.Dirty(serverEntA, cmp);
|
|
|
|
cmp = server.EntMan.EnsureComponent<UnknownEntityTestComponent>(serverEntB);
|
|
cmp.Other = serverEntA;
|
|
server.EntMan.Dirty(serverEntB, cmp);
|
|
});
|
|
|
|
await RunTicks();
|
|
|
|
// Check player got properly attached and only knows about the expected entities
|
|
await client.WaitPost(() =>
|
|
{
|
|
cPlayer = client.EntMan.GetEntity(server.EntMan.GetNetEntity(player));
|
|
Assert.That(client.AttachedEntity, Is.EqualTo(cPlayer));
|
|
Assert.That(client.EntMan.EntityExists(cPlayer));
|
|
Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetA)), Is.False);
|
|
Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetB)), Is.False);
|
|
});
|
|
|
|
// Move the player into PVS range of one of the entities.
|
|
await server.WaitPost(() => xforms.SetCoordinates(player, coordsB));
|
|
await RunTicks();
|
|
|
|
await client.WaitPost(() =>
|
|
{
|
|
var clientEntA = client.EntMan.GetEntity(serverNetA);
|
|
var clientEntB = client.EntMan.GetEntity(serverNetB);
|
|
Assert.That(client.EntMan.EntityExists(clientEntB), Is.True);
|
|
Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetA)), Is.False);
|
|
|
|
Assert.That(client.EntMan.TryGetComponent(clientEntB, out UnknownEntityTestComponent? cmp));
|
|
Assert.That(cmp?.Other, Is.EqualTo(clientEntA));
|
|
});
|
|
|
|
// Move the player into PVS range of the other entity
|
|
await server.WaitPost(() => xforms.SetCoordinates(player, coordsA));
|
|
await RunTicks();
|
|
|
|
await client.WaitPost(() =>
|
|
{
|
|
var clientEntA = client.EntMan.GetEntity(serverNetA);
|
|
var clientEntB = client.EntMan.GetEntity(serverNetB);
|
|
Assert.That(client.EntMan.EntityExists(clientEntB), Is.True);
|
|
Assert.That(client.EntMan.EntityExists(clientEntA), Is.True);
|
|
|
|
Assert.That(client.EntMan.TryGetComponent(clientEntB, out UnknownEntityTestComponent? cmp));
|
|
Assert.That(cmp?.Other, Is.EqualTo(clientEntA));
|
|
|
|
Assert.That(client.EntMan.TryGetComponent(clientEntA, out cmp));
|
|
Assert.That(cmp?.Other, Is.EqualTo(clientEntB));
|
|
});
|
|
|
|
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, false));
|
|
|
|
// wait for errors.
|
|
await RunTicks();
|
|
|
|
async Task RunTicks()
|
|
{
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
await server!.WaitRunTicks(1);
|
|
await client!.WaitRunTicks(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a variant of <see cref="UnknownEntityTest"/> that deletes one of the entities before the other entity gets sent.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task UnknownEntityDeleteTest()
|
|
{
|
|
// The first chunk of the test just follows UnknownEntityTest
|
|
var serverOpts = new ServerIntegrationOptions { Pool = false };
|
|
var clientOpts = new ClientIntegrationOptions { Pool = false };
|
|
var server = StartServer(serverOpts);
|
|
var client = StartClient(clientOpts);
|
|
|
|
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
|
var netMan = client.ResolveDependency<IClientNetManager>();
|
|
var xforms = server.System<SharedTransformSystem>();
|
|
|
|
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
|
client.Post(() => netMan.ClientConnect(null!, 0, null!));
|
|
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true));
|
|
|
|
// Set up map.
|
|
EntityUid map = default;
|
|
await server.WaitPost(() =>
|
|
{
|
|
var mapId = server.MapMan.CreateMap();
|
|
map = server.MapMan.GetMapEntityId(mapId);
|
|
});
|
|
|
|
await RunTicks();
|
|
|
|
// Spawn entities
|
|
var coordsA = new EntityCoordinates(map, default);
|
|
var coordsB = new EntityCoordinates(map, new Vector2(100, 100));
|
|
EntityUid player = default;
|
|
EntityUid cPlayer = default;
|
|
EntityUid serverEntA = default;
|
|
EntityUid serverEntB = default;
|
|
NetEntity serverNetA = default;
|
|
NetEntity serverNetB = default;
|
|
|
|
await server.WaitPost(() =>
|
|
{
|
|
// Attach player.
|
|
player = server.EntMan.Spawn();
|
|
var session = server.PlayerMan.Sessions.First();
|
|
server.PlayerMan.SetAttachedEntity(session, player);
|
|
server.PlayerMan.JoinGame(session);
|
|
|
|
// Spawn test entities.
|
|
serverEntA = server.EntMan.SpawnAttachedTo(null, coordsA);
|
|
serverEntB = server.EntMan.SpawnAttachedTo(null, coordsB);
|
|
serverNetA = server.EntMan.GetNetEntity(serverEntA);
|
|
serverNetB = server.EntMan.GetNetEntity(serverEntB);
|
|
|
|
// Setup components
|
|
var cmp = server.EntMan.EnsureComponent<UnknownEntityTestComponent>(serverEntA);
|
|
cmp.Other = serverEntB;
|
|
server.EntMan.Dirty(serverEntA, cmp);
|
|
|
|
cmp = server.EntMan.EnsureComponent<UnknownEntityTestComponent>(serverEntB);
|
|
cmp.Other = serverEntA;
|
|
server.EntMan.Dirty(serverEntB, cmp);
|
|
});
|
|
|
|
await RunTicks();
|
|
|
|
// Check player got properly attached and only knows about the expected entities
|
|
await client.WaitPost(() =>
|
|
{
|
|
cPlayer = client.EntMan.GetEntity(server.EntMan.GetNetEntity(player));
|
|
Assert.That(client.AttachedEntity, Is.EqualTo(cPlayer));
|
|
Assert.That(client.EntMan.EntityExists(cPlayer));
|
|
Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetA)), Is.False);
|
|
Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetB)), Is.False);
|
|
});
|
|
|
|
// Move the player into PVS range of one of the entities.
|
|
await server.WaitPost(() => xforms.SetCoordinates(player, coordsB));
|
|
await RunTicks();
|
|
|
|
await client.WaitPost(() =>
|
|
{
|
|
var clientEntA = client.EntMan.GetEntity(serverNetA);
|
|
var clientEntB = client.EntMan.GetEntity(serverNetB);
|
|
Assert.That(client.EntMan.EntityExists(clientEntB), Is.True);
|
|
Assert.That(client.EntMan.EntityExists(clientEntA), Is.False);
|
|
|
|
Assert.That(client.EntMan.TryGetComponent(clientEntB, out UnknownEntityTestComponent? cmp));
|
|
Assert.That(cmp?.Other, Is.EqualTo(clientEntA));
|
|
});
|
|
|
|
// This is where the test difffers from UnknownEntityTest:
|
|
// We delete the entity that the player knows about before it receives the entity that it references.
|
|
await server.WaitPost(() =>
|
|
{
|
|
server.EntMan.DeleteEntity(serverEntB);
|
|
var comp = server.EntMan.GetComponent<UnknownEntityTestComponent>(serverEntA);
|
|
comp.Other = EntityUid.Invalid;
|
|
server.EntMan.Dirty(serverEntA, comp);
|
|
});
|
|
await RunTicks();
|
|
|
|
await client.WaitPost(() =>
|
|
{
|
|
var clientEntA = client.EntMan.GetEntity(serverNetA);
|
|
var clientEntB = client.EntMan.GetEntity(serverNetB);
|
|
Assert.That(clientEntA, Is.EqualTo(EntityUid.Invalid));
|
|
Assert.That(clientEntB, Is.EqualTo(EntityUid.Invalid));
|
|
});
|
|
|
|
// Move the player into PVS range of the other entity
|
|
await server.WaitPost(() => xforms.SetCoordinates(player, coordsA));
|
|
await RunTicks();
|
|
|
|
await client.WaitPost(() =>
|
|
{
|
|
var clientEntA = client.EntMan.GetEntity(serverNetA);
|
|
var clientEntB = client.EntMan.GetEntity(serverNetB);
|
|
Assert.That(clientEntB, Is.EqualTo(EntityUid.Invalid));
|
|
Assert.That(client.EntMan.EntityExists(clientEntA), Is.True);
|
|
Assert.That(client.EntMan.TryGetComponent(clientEntA, out UnknownEntityTestComponent? cmp));
|
|
Assert.That(cmp?.Other, Is.EqualTo(EntityUid.Invalid));
|
|
});
|
|
|
|
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, false));
|
|
|
|
// wait for errors.
|
|
await RunTicks();
|
|
|
|
async Task RunTicks()
|
|
{
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
await server!.WaitRunTicks(1);
|
|
await client!.WaitRunTicks(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
|
public sealed partial class UnknownEntityTestComponent : Component
|
|
{
|
|
[DataField, AutoNetworkedField]
|
|
public EntityUid? Other;
|
|
}
|