mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Add dummy sessions for integration tests (#5202)
* Add dummy sessions * if FULL_RELEASE
This commit is contained in:
@@ -39,7 +39,7 @@ END TEMPLATE-->
|
||||
|
||||
### New features
|
||||
|
||||
*None yet*
|
||||
* `ServerIntegrationInstance` has new methods for adding dummy player sessions for tests that require multiple players.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -51,8 +51,8 @@ END TEMPLATE-->
|
||||
|
||||
### Internal
|
||||
|
||||
*None yet*
|
||||
|
||||
* Added `DummySession` and `DummyChannel` classes for use in integration tests and benchmarks to fool the server into thinking that there are multiple players connected.
|
||||
* Added `ICommonSessionInternal` and updated `CommonSession` so that the internal setters now go through that interface.
|
||||
|
||||
## 224.0.1
|
||||
|
||||
|
||||
@@ -261,8 +261,8 @@ namespace Robust.Client.Player
|
||||
{
|
||||
// This is a new userid, so we create a new session.
|
||||
DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
|
||||
var newSession = (CommonSession) CreateAndAddSession(state.UserId, state.Name);
|
||||
newSession.Ping = state.Ping;
|
||||
var newSession = (ICommonSessionInternal)CreateAndAddSession(state.UserId, state.Name);
|
||||
newSession.SetPing(state.Ping);
|
||||
SetStatus(newSession, state.Status);
|
||||
SetAttachedEntity(newSession, controlled, out _, true);
|
||||
dirty = true;
|
||||
@@ -279,9 +279,9 @@ namespace Robust.Client.Player
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
var local = (CommonSession) session;
|
||||
local.Name = state.Name;
|
||||
local.Ping = state.Ping;
|
||||
var local = (ICommonSessionInternal)session;
|
||||
local.SetName(state.Name);
|
||||
local.SetPing(state.Ping);
|
||||
SetStatus(local, state.Status);
|
||||
SetAttachedEntity(local, controlled, out _, true);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -42,7 +43,7 @@ internal sealed partial class PvsSystem
|
||||
|
||||
// PVS benchmarks use dummy sessions.
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (session.Channel != null)
|
||||
if (session.Channel is not DummyChannel)
|
||||
{
|
||||
_netMan.ServerSendMessage(msg, session.Channel);
|
||||
if (msg.ShouldSendReliably())
|
||||
|
||||
@@ -90,18 +90,18 @@ namespace Robust.Server.Player
|
||||
_cfg.SyncConnectingClient(args.Channel);
|
||||
}
|
||||
|
||||
private void EndSession(object? sender, NetChannelArgs args)
|
||||
{
|
||||
EndSession(args.Channel.UserId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends a clients session, and disconnects them.
|
||||
/// </summary>
|
||||
private void EndSession(object? sender, NetChannelArgs args)
|
||||
internal void EndSession(NetUserId user)
|
||||
{
|
||||
if (!TryGetSessionByChannel(args.Channel, out var session))
|
||||
{
|
||||
if (!TryGetSessionById(user, out var session))
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure nothing got messed up during the life of the session
|
||||
DebugTools.Assert(session.Channel == args.Channel);
|
||||
|
||||
SetStatus(session, SessionStatus.Disconnected);
|
||||
SetAttachedEntity(session, null, out _, true);
|
||||
@@ -159,5 +159,32 @@ namespace Robust.Server.Player
|
||||
session = actor.PlayerSession;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal ICommonSession AddDummySession(NetUserId user, string name)
|
||||
{
|
||||
#if FULL_RELEASE
|
||||
// Lets not make it completely trivial to fake player counts.
|
||||
throw new NotSupportedException();
|
||||
#endif
|
||||
Lock.EnterWriteLock();
|
||||
DummySession session;
|
||||
try
|
||||
{
|
||||
UserIdMap[name] = user;
|
||||
if (!PlayerData.TryGetValue(user, out var data))
|
||||
PlayerData[user] = data = new(user, name);
|
||||
|
||||
session = new DummySession(user, name, data);
|
||||
InternalSessions.Add(user, session);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
UpdateState(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Prometheus;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Player;
|
||||
|
||||
internal sealed class CommonSession : ICommonSession
|
||||
internal sealed class CommonSession : ICommonSessionInternal
|
||||
{
|
||||
[ViewVariables]
|
||||
public EntityUid? AttachedEntity { get; set; }
|
||||
@@ -17,10 +17,10 @@ internal sealed class CommonSession : ICommonSession
|
||||
public NetUserId UserId { get; }
|
||||
|
||||
[ViewVariables]
|
||||
public string Name { get; internal set; } = "<Unknown>";
|
||||
public string Name { get; set; } = "<Unknown>";
|
||||
|
||||
[ViewVariables]
|
||||
public short Ping { get; internal set; }
|
||||
public short Ping { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public DateTime ConnectedTime { get; set; }
|
||||
@@ -42,9 +42,6 @@ internal sealed class CommonSession : ICommonSession
|
||||
[ViewVariables]
|
||||
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
public int VisibilityMask { get; set; } = 1;
|
||||
|
||||
[ViewVariables]
|
||||
public LoginType AuthType => Channel?.AuthType ?? default;
|
||||
|
||||
@@ -56,4 +53,29 @@ internal sealed class CommonSession : ICommonSession
|
||||
Name = name;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public void SetStatus(SessionStatus status)
|
||||
{
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public void SetAttachedEntity(EntityUid? uid)
|
||||
{
|
||||
AttachedEntity = uid;
|
||||
}
|
||||
|
||||
public void SetPing(short ping)
|
||||
{
|
||||
Ping = ping;
|
||||
}
|
||||
|
||||
public void SetName(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void SetChannel(INetChannel channel)
|
||||
{
|
||||
Channel = channel;
|
||||
}
|
||||
}
|
||||
|
||||
127
Robust.Shared/Player/DummySession.cs
Normal file
127
Robust.Shared/Player/DummySession.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Shared.Player;
|
||||
|
||||
/// <summary>
|
||||
/// This is a mock session for use with integration tests and benchmarks. It uses a <see cref="DummyChannel"/> as
|
||||
/// its <see cref="INetChannel"/>, which doesn't support actually sending any messages.
|
||||
/// </summary>
|
||||
internal sealed class DummySession : ICommonSessionInternal
|
||||
{
|
||||
public EntityUid? AttachedEntity {get; set; }
|
||||
public SessionStatus Status { get; set; } = SessionStatus.Connecting;
|
||||
public NetUserId UserId => UserData.UserId;
|
||||
public string Name => UserData.UserName;
|
||||
|
||||
public short Ping { get; set; }
|
||||
|
||||
public INetChannel Channel
|
||||
{
|
||||
get => DummyChannel;
|
||||
[Obsolete]
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public LoginType AuthType { get; set; } = LoginType.GuestAssigned;
|
||||
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
|
||||
public DateTime ConnectedTime { get; set; }
|
||||
public SessionState State { get; set; } = new();
|
||||
public SessionData Data { get; set; }
|
||||
public bool ClientSide { get; set; }
|
||||
public NetUserData UserData { get; set; }
|
||||
|
||||
public DummyChannel DummyChannel;
|
||||
|
||||
public DummySession(NetUserId userId, string userName, SessionData data)
|
||||
{
|
||||
Data = data;
|
||||
UserData = new(userId, userName)
|
||||
{
|
||||
HWId = ImmutableArray<byte>.Empty
|
||||
};
|
||||
DummyChannel = new(this);
|
||||
}
|
||||
|
||||
public void SetStatus(SessionStatus status)
|
||||
{
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public void SetAttachedEntity(EntityUid? uid)
|
||||
{
|
||||
AttachedEntity = uid;
|
||||
}
|
||||
|
||||
public void SetPing(short ping)
|
||||
{
|
||||
Ping = ping;
|
||||
}
|
||||
|
||||
public void SetName(string name)
|
||||
{
|
||||
UserData = new(UserData.UserId, name)
|
||||
{
|
||||
HWId = UserData.HWId
|
||||
};
|
||||
}
|
||||
|
||||
public void SetChannel(INetChannel channel)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A mock NetChannel for use in integration tests and benchmarks.
|
||||
/// </summary>
|
||||
internal sealed class DummyChannel(DummySession session) : INetChannel
|
||||
{
|
||||
public readonly DummySession Session = session;
|
||||
public NetUserData UserData => Session.UserData;
|
||||
public short Ping => Session.Ping;
|
||||
public string UserName => Session.Name;
|
||||
public LoginType AuthType => Session.AuthType;
|
||||
public NetUserId UserId => Session.UserId;
|
||||
|
||||
public int CurrentMtu { get; set; } = default;
|
||||
public long ConnectionId { get; set; } = default;
|
||||
public TimeSpan RemoteTimeOffset { get; set; } = default;
|
||||
public TimeSpan RemoteTime { get; set; } = default;
|
||||
public bool IsConnected { get; set; } = true;
|
||||
public bool IsHandshakeComplete { get; set; } = true;
|
||||
|
||||
// This is just pilfered from IntegrationNetChannel
|
||||
public IPEndPoint RemoteEndPoint { get; } = new(IPAddress.Loopback, 1212);
|
||||
|
||||
// Only used on server, contains the encryption to use for this channel.
|
||||
public NetEncryption? Encryption { get; set; }
|
||||
|
||||
public INetManager NetPeer => throw new NotImplementedException();
|
||||
|
||||
public T CreateNetMessage<T>() where T : NetMessage, new()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SendMessage(NetMessage message)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Disconnect(string reason)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Disconnect(string reason, bool sendBye)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -43,9 +43,8 @@ public interface ICommonSession
|
||||
/// <remarks>
|
||||
/// On the Server every player has a network channel,
|
||||
/// on the Client only the LocalPlayer has a network channel, and that channel points to the server.
|
||||
/// Unless you know what you are doing, you shouldn't be modifying this directly.
|
||||
/// </remarks>
|
||||
INetChannel Channel { get; set; }
|
||||
INetChannel Channel { get; [Obsolete] set; }
|
||||
|
||||
LoginType AuthType { get; }
|
||||
|
||||
@@ -75,3 +74,12 @@ public interface ICommonSession
|
||||
/// </summary>
|
||||
bool ClientSide { get; set; }
|
||||
}
|
||||
|
||||
internal interface ICommonSessionInternal : ICommonSession
|
||||
{
|
||||
public void SetStatus(SessionStatus status);
|
||||
public void SetAttachedEntity(EntityUid? uid);
|
||||
public void SetPing(short ping);
|
||||
public void SetName(string name);
|
||||
void SetChannel(INetChannel channel);
|
||||
}
|
||||
|
||||
@@ -101,8 +101,8 @@ internal abstract partial class SharedPlayerManager
|
||||
|
||||
public ICommonSession CreateAndAddSession(INetChannel channel)
|
||||
{
|
||||
var session = CreateAndAddSession(channel.UserId, channel.UserName);
|
||||
session.Channel = channel;
|
||||
var session = (ICommonSessionInternal)CreateAndAddSession(channel.UserId, channel.UserName);
|
||||
session.SetChannel(channel);
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ internal abstract partial class SharedPlayerManager
|
||||
if (session.AttachedEntity is not {} uid)
|
||||
return;
|
||||
|
||||
((CommonSession) session).AttachedEntity = null;
|
||||
((ICommonSessionInternal) session).SetAttachedEntity(null);
|
||||
UpdateState(session);
|
||||
|
||||
if (EntManager.TryGetComponent(uid, out ActorComponent? actor) && actor.LifeStage <= ComponentLifeStage.Running)
|
||||
@@ -215,7 +215,7 @@ internal abstract partial class SharedPlayerManager
|
||||
if (session.AttachedEntity != null)
|
||||
Detach(session);
|
||||
|
||||
((CommonSession) session).AttachedEntity = uid;
|
||||
((ICommonSessionInternal) session).SetAttachedEntity(uid);
|
||||
actor.PlayerSession = session;
|
||||
UpdateState(session);
|
||||
EntManager.EventBus.RaiseLocalEvent(uid, new PlayerAttachedEvent(uid, session), true);
|
||||
@@ -228,7 +228,7 @@ internal abstract partial class SharedPlayerManager
|
||||
return;
|
||||
|
||||
var old = session.Status;
|
||||
((CommonSession) session).Status = status;
|
||||
((ICommonSessionInternal) session).SetStatus(status);
|
||||
|
||||
UpdateState(session);
|
||||
PlayerStatusChanged?.Invoke(this, new SessionStatusEventArgs(session, old, status));
|
||||
@@ -236,13 +236,13 @@ internal abstract partial class SharedPlayerManager
|
||||
|
||||
public void SetPing(ICommonSession session, short ping)
|
||||
{
|
||||
((CommonSession) session).Ping = ping;
|
||||
((ICommonSessionInternal) session).SetPing(ping);
|
||||
UpdateState(session);
|
||||
}
|
||||
|
||||
public void SetName(ICommonSession session, string name)
|
||||
{
|
||||
((CommonSession) session).Name = name;
|
||||
((ICommonSessionInternal) session).SetName(name);
|
||||
UpdateState(session);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -258,6 +259,9 @@ namespace Robust.UnitTesting
|
||||
{
|
||||
DebugTools.Assert(IsServer);
|
||||
|
||||
if (recipient is DummyChannel)
|
||||
return;
|
||||
|
||||
var channel = (IntegrationNetChannel) recipient;
|
||||
channel.OtherChannel.TryWrite(SerializeNetMessage(message, channel.RemoteUid));
|
||||
}
|
||||
@@ -443,6 +447,7 @@ namespace Robust.UnitTesting
|
||||
public int CurrentMtu => 1000; // Arbitrary.
|
||||
|
||||
// TODO: Should this port value make sense?
|
||||
// See also the DummyChannel class
|
||||
public IPEndPoint RemoteEndPoint { get; } = new(IPAddress.Loopback, 1212);
|
||||
public NetUserId UserId => UserData.UserId;
|
||||
public string UserName => UserData.UserName;
|
||||
|
||||
@@ -26,6 +26,7 @@ using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -761,6 +762,69 @@ namespace Robust.UnitTesting
|
||||
pvs.SendGameStates(players);
|
||||
Timing.CurTick += 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple dummy players to the server.
|
||||
/// </summary>
|
||||
public async Task<ICommonSession[]> AddDummySessions(int count)
|
||||
{
|
||||
var sessions = new ICommonSession[count];
|
||||
for (var i = 0; i < sessions.Length; i++)
|
||||
{
|
||||
sessions[i] = await AddDummySession();
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a dummy player to the server.
|
||||
/// </summary>
|
||||
public async Task<ICommonSession> AddDummySession(string? userName = null)
|
||||
{
|
||||
userName ??= $"integration_dummy_{DummyUsers.Count}";
|
||||
Log.Info($"Adding dummy session {userName}");
|
||||
if (!_dummyUsers.TryGetValue(userName, out var userId))
|
||||
_dummyUsers[userName] = userId = new(Guid.NewGuid());
|
||||
|
||||
var man = (Robust.Server.Player.PlayerManager) PlayerMan;
|
||||
var session = man.AddDummySession(userId, userName);
|
||||
_dummySessions.Add(userId, session);
|
||||
|
||||
session.ConnectedTime = DateTime.UtcNow;
|
||||
await WaitPost(() => man.SetStatus(session, SessionStatus.Connected));
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a dummy player from the server.
|
||||
/// </summary>
|
||||
public async Task RemoveDummySession(ICommonSession session, bool removeUser = false)
|
||||
{
|
||||
Log.Info($"Removing dummy session {session.Name}");
|
||||
_dummySessions.Remove(session.UserId);
|
||||
var man = (Robust.Server.Player.PlayerManager) PlayerMan;
|
||||
await WaitPost(() => man.EndSession(session.UserId));
|
||||
if (removeUser)
|
||||
_dummyUsers.Remove(session.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all dummy players from the server.
|
||||
/// </summary>
|
||||
public async Task RemoveAllDummySessions()
|
||||
{
|
||||
foreach (var session in _dummySessions.Values)
|
||||
{
|
||||
await RemoveDummySession(session);
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, NetUserId> _dummyUsers = new();
|
||||
private Dictionary<NetUserId, ICommonSession> _dummySessions = new();
|
||||
public IReadOnlyDictionary<string, NetUserId> DummyUsers => _dummyUsers;
|
||||
public IReadOnlyDictionary<NetUserId, ICommonSession> DummySessions => _dummySessions;
|
||||
}
|
||||
|
||||
public sealed class ClientIntegrationInstance : IntegrationInstance
|
||||
|
||||
Reference in New Issue
Block a user