first commit
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
using Nitrox.Test.Client.Communication;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication;
|
||||
|
||||
[TestClass]
|
||||
public class DeferredPacketReceiverTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void NonActionPacket()
|
||||
{
|
||||
// Arrange
|
||||
const ushort PLAYER_ID = 1;
|
||||
TestNonActionPacket packet = new(PLAYER_ID);
|
||||
PacketReceiver packetReceiver = new();
|
||||
|
||||
// Act
|
||||
packetReceiver.Add(packet);
|
||||
Packet storedPacket = packetReceiver.GetNextPacket();
|
||||
|
||||
// Assert
|
||||
storedPacket.Should().NotBeNull();
|
||||
packetReceiver.GetNextPacket().Should().BeNull();
|
||||
storedPacket.Should().Be(packet);
|
||||
packet.PlayerId.Should().Be(PLAYER_ID);
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NSubstitute;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
[TestClass]
|
||||
public class SessionJoinedStateTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void NegotiateShouldThrowInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
SessionJoined connectionState = new SessionJoined();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void JoinSessionShouldThrowInvalidOperationExcepion()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
SessionJoined connectionState = new SessionJoined();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.JoinSession(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldStopTheClient()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionJoined connectionState = new SessionJoined();
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
serverClient.Received().Stop();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldResetTheConnectionContext()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionJoined connectionState = new SessionJoined();
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().ClearSessionState();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldTransitionToDisconnectedState()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionJoined connection = new SessionJoined();
|
||||
|
||||
// Act
|
||||
connection.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<Disconnected>());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NSubstitute;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
[TestClass]
|
||||
public class SessionReservationRejectedStateTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void NegotiateShouldThrowInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
SessionReservationRejected connectionState = new SessionReservationRejected();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void JoinSessionShouldThrowInvalidOperationExcepion()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
SessionReservationRejected connectionState = new SessionReservationRejected();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.JoinSession(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldStopTheClient()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionReservationRejected connectionState = new SessionReservationRejected();
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
serverClient.Received().Stop();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldResetTheConnectionContext()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionReservationRejected connectionState = new SessionReservationRejected();
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().ClearSessionState();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldTransitionToDisconnectedState()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionReservationRejected connection = new SessionReservationRejected();
|
||||
|
||||
// Act
|
||||
connection.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<Disconnected>());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Nitrox.Test.Client.Communication.MultiplayerSession;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
using NSubstitute;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
[TestClass]
|
||||
public class SessionReservedStateTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void NegotiateShouldThrowInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
SessionReserved connectionState = new SessionReserved();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void JoinSessionShouldSendPlayerJoiningMultiplayerSessionPacket()
|
||||
{
|
||||
// Arrange
|
||||
MultiplayerSessionReservation successfulReservation = new MultiplayerSessionReservation(
|
||||
TestConstants.TEST_CORRELATION_ID,
|
||||
TestConstants.TEST_PLAYER_ID,
|
||||
TestConstants.TEST_RESERVATION_KEY);
|
||||
|
||||
IClient client = Substitute.For<IClient>();
|
||||
client.IsConnected.Returns(true);
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Reservation.Returns(successfulReservation);
|
||||
connectionContext.Client.Returns(client);
|
||||
|
||||
SessionReserved connectionState = new SessionReserved();
|
||||
|
||||
// Act
|
||||
connectionState.JoinSession(connectionContext);
|
||||
|
||||
// Assert
|
||||
client.Received().Send(Arg.Any<PlayerJoiningMultiplayerSession>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void JoinSessionShouldTransitionToSessionJoinedState()
|
||||
{
|
||||
// Arrange
|
||||
MultiplayerSessionReservation successfulReservation = new MultiplayerSessionReservation(
|
||||
TestConstants.TEST_CORRELATION_ID,
|
||||
TestConstants.TEST_PLAYER_ID,
|
||||
TestConstants.TEST_RESERVATION_KEY);
|
||||
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
serverClient.IsConnected.Returns(true);
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Reservation.Returns(successfulReservation);
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionReserved connection = new SessionReserved();
|
||||
|
||||
// Act
|
||||
connection.JoinSession(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<SessionJoined>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldStopTheClient()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionReserved connectionState = new SessionReserved();
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
serverClient.Received().Stop();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldResetTheConnectionContext()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionReserved connectionState = new SessionReserved();
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().ClearSessionState();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldTransitionToDisconnectedState()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
SessionReserved connection = new SessionReserved();
|
||||
|
||||
// Act
|
||||
connection.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<Disconnected>());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Nitrox.Test.Client.Communication.MultiplayerSession;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets;
|
||||
using NSubstitute;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
[TestClass]
|
||||
public class AwaitingReservationCredentialsStateTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void NegotiateShouldSendServerAuthorityReservationRequest()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
serverClient.IsConnected.Returns(true);
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
connectionContext.PlayerSettings.Returns(TestConstants.TEST_PLAYER_SETTINGS);
|
||||
connectionContext.AuthenticationContext.Returns(TestConstants.TEST_AUTHENTICATION_CONTEXT);
|
||||
|
||||
AwaitingReservationCredentials connectionState = new AwaitingReservationCredentials();
|
||||
|
||||
// Act
|
||||
connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
serverClient.Received().Send(Arg.Any<MultiplayerSessionReservationRequest>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldTransitionToAwaitingSessionReservationState()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
serverClient.IsConnected.Returns(true);
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
connectionContext.PlayerSettings.Returns(TestConstants.TEST_PLAYER_SETTINGS);
|
||||
connectionContext.AuthenticationContext.Returns(TestConstants.TEST_AUTHENTICATION_CONTEXT);
|
||||
|
||||
AwaitingReservationCredentials connectionState = new AwaitingReservationCredentials();
|
||||
|
||||
// Act
|
||||
connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<AwaitingSessionReservation>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldThrowInvalidOperationExceptionWhenPlayerSettingsIsNull()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
serverClient.IsConnected.Returns(true);
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.PlayerSettings.Returns((PlayerSettings)null);
|
||||
connectionContext.AuthenticationContext.Returns(TestConstants.TEST_AUTHENTICATION_CONTEXT);
|
||||
|
||||
AwaitingReservationCredentials connectionState = new AwaitingReservationCredentials();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldThrowInvalidOperationExceptionWhenAuthenticationContextIsNull()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
serverClient.IsConnected.Returns(true);
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.SessionPolicy.Returns(TestConstants.TEST_SESSION_POLICY);
|
||||
connectionContext.AuthenticationContext.Returns((AuthenticationContext)null);
|
||||
|
||||
AwaitingReservationCredentials connectionState = new AwaitingReservationCredentials();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void JoinSessionShouldThrowInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
AwaitingReservationCredentials connectionState = new AwaitingReservationCredentials();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.JoinSession(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldStopTheClient()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
AwaitingReservationCredentials connectionState = new AwaitingReservationCredentials();
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
serverClient.Received().Stop();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldResetTheConnectionContext()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
AwaitingReservationCredentials connectionState = new AwaitingReservationCredentials();
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().ClearSessionState();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldTransitionToDisconnectedState()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
AwaitingReservationCredentials connectionState = new AwaitingReservationCredentials();
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<Disconnected>());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Nitrox.Test.Client.Communication.MultiplayerSession;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Packets.Exceptions;
|
||||
using NSubstitute;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
[TestClass]
|
||||
public class AwaitingSessionReservationStateTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void NegotiateShouldTransitionToSessionRevervedAfterReceivingSuccessfulReservation()
|
||||
{
|
||||
// Arrange
|
||||
MultiplayerSessionReservation successfulReservation = new MultiplayerSessionReservation(
|
||||
TestConstants.TEST_CORRELATION_ID,
|
||||
TestConstants.TEST_PLAYER_ID,
|
||||
TestConstants.TEST_RESERVATION_KEY);
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Reservation.Returns(successfulReservation);
|
||||
|
||||
AwaitingSessionReservation connectionState = new AwaitingSessionReservation(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<SessionReserved>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldThrowUncorrelatedPacketExceptionWhenTheReservationHasTheWrongCorrelationId()
|
||||
{
|
||||
// Arrange
|
||||
MultiplayerSessionReservation successfulReservation = new MultiplayerSessionReservation(
|
||||
"wrong",
|
||||
TestConstants.TEST_PLAYER_ID,
|
||||
TestConstants.TEST_RESERVATION_KEY);
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Reservation.Returns(successfulReservation);
|
||||
|
||||
AwaitingSessionReservation connectionState = new AwaitingSessionReservation(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<UncorrelatedPacketException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldTransitionToSessionReservationRejectedAfterReceivingRejectedReservation()
|
||||
{
|
||||
// Arrange
|
||||
MultiplayerSessionReservation rejectedReservation = new MultiplayerSessionReservation(TestConstants.TEST_CORRELATION_ID, TestConstants.TEST_REJECTION_STATE);
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Reservation.Returns(rejectedReservation);
|
||||
|
||||
AwaitingSessionReservation connectionState = new AwaitingSessionReservation(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<SessionReservationRejected>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldThrowInvalidOperationExceptionWhenTheReservationIsNull()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Reservation.Returns((MultiplayerSessionReservation)null);
|
||||
|
||||
AwaitingSessionReservation connectionState = new AwaitingSessionReservation(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void JoinSessionShouldThrowInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
AwaitingSessionReservation connectionState = new AwaitingSessionReservation(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.JoinSession(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldStopTheClient()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
AwaitingSessionReservation connectionState = new AwaitingSessionReservation(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
serverClient.Received().Stop();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldResetTheConnectionContext()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
AwaitingSessionReservation connectionState = new AwaitingSessionReservation(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().ClearSessionState();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldTransitionToDisconnectedState()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
AwaitingSessionReservation connectionState = new AwaitingSessionReservation(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<Disconnected>());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Nitrox.Test.Client.Communication.MultiplayerSession;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Packets.Exceptions;
|
||||
using NSubstitute;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
[TestClass]
|
||||
public class EstablishingSessionPolicyStateTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void NegotiateShouldTransitionToAwaitingReservationCredentialsState()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.SessionPolicy.Returns(new MultiplayerSessionPolicy(TestConstants.TEST_CORRELATION_ID, false, TestConstants.TEST_MAX_PLAYER_CONNECTIONS, false));
|
||||
|
||||
EstablishingSessionPolicy connectionState = new EstablishingSessionPolicy(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<AwaitingReservationCredentials>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldTransitionToAwaitingReservationCredentialsStateWithPassword()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.SessionPolicy.Returns(new MultiplayerSessionPolicy(TestConstants.TEST_CORRELATION_ID, false, TestConstants.TEST_MAX_PLAYER_CONNECTIONS, true));
|
||||
|
||||
EstablishingSessionPolicy connectionState = new EstablishingSessionPolicy(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<AwaitingReservationCredentials>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldThrowUncorrelatedPacketExceptionWhenThePolicyHasTheWrongCorrelationId()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.SessionPolicy.Returns(new MultiplayerSessionPolicy("wrong", false, TestConstants.TEST_MAX_PLAYER_CONNECTIONS, false));
|
||||
|
||||
EstablishingSessionPolicy connectionState = new EstablishingSessionPolicy(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<UncorrelatedPacketException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldThrowInvalidOperationExceptionIfTheSessionPolicyIsNull()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.SessionPolicy.Returns((MultiplayerSessionPolicy)null);
|
||||
|
||||
EstablishingSessionPolicy connectionState = new EstablishingSessionPolicy(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void JoinSessionShouldThrowInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
EstablishingSessionPolicy connectionState = new EstablishingSessionPolicy(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.JoinSession(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldStopTheClient()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
EstablishingSessionPolicy connectionState = new EstablishingSessionPolicy(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
serverClient.Received().Stop();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldResetTheConnectionContext()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
EstablishingSessionPolicy connectionState = new EstablishingSessionPolicy(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().ClearSessionState();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldTransitionToDisconnectedState()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
|
||||
EstablishingSessionPolicy connectionState = new EstablishingSessionPolicy(TestConstants.TEST_CORRELATION_ID);
|
||||
|
||||
// Act
|
||||
connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<Disconnected>());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Nitrox.Test.Client.Communication.MultiplayerSession;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
using NSubstitute;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
[TestClass]
|
||||
public class DisconnectedStateTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void NegotiateShouldStartTheClientOnTheContext()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
serverClient.IsConnected.Returns(false);
|
||||
serverClient
|
||||
.When(client => client.StartAsync(Arg.Any<string>(), TestConstants.TEST_SERVER_PORT))
|
||||
.Do(info => serverClient.IsConnected.Returns(true));
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
connectionContext.ServerPort.Returns(TestConstants.TEST_SERVER_PORT);
|
||||
|
||||
Disconnected connectionState = new Disconnected();
|
||||
|
||||
// Act
|
||||
connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
serverClient.IsConnected.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldSendMultiplayerSessionPolicyRequestPacketToClient()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
serverClient.IsConnected.Returns(false);
|
||||
serverClient
|
||||
.When(client => client.StartAsync(Arg.Any<string>(), TestConstants.TEST_SERVER_PORT))
|
||||
.Do(info => serverClient.IsConnected.Returns(true));
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
connectionContext.ServerPort.Returns(TestConstants.TEST_SERVER_PORT);
|
||||
|
||||
Disconnected connectionState = new Disconnected();
|
||||
|
||||
// Act
|
||||
connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
serverClient.Received().Send(Arg.Any<MultiplayerSessionPolicyRequest>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NegotiateShouldTransitionToEstablishingSessionPolicyState()
|
||||
{
|
||||
// Arrange
|
||||
IClient serverClient = Substitute.For<IClient>();
|
||||
serverClient.IsConnected.Returns(false);
|
||||
serverClient
|
||||
.When(client => client.StartAsync(Arg.Any<string>(), TestConstants.TEST_SERVER_PORT))
|
||||
.Do(info => serverClient.IsConnected.Returns(true));
|
||||
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(serverClient);
|
||||
connectionContext.ServerPort.Returns(TestConstants.TEST_SERVER_PORT);
|
||||
|
||||
Disconnected connectionState = new Disconnected();
|
||||
|
||||
// Act
|
||||
connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
connectionContext.Received().UpdateConnectionState(Arg.Any<EstablishingSessionPolicy>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task NegotiateShouldThrowInvalidOperationExceptionWhenClientIsNull()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns((IClient)null);
|
||||
connectionContext.IpAddress.Returns(TestConstants.TEST_IP_ADDRESS);
|
||||
|
||||
Disconnected connectionState = new Disconnected();
|
||||
|
||||
// Act
|
||||
Func<Task> action = async () => await connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task NegotiateShouldThrowInvalidOperationExceptionWhenIpAddressIsNull()
|
||||
{
|
||||
// Arrange
|
||||
IClient client = Substitute.For<IClient>();
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
connectionContext.Client.Returns(client);
|
||||
connectionContext.IpAddress.Returns((string)null);
|
||||
|
||||
Disconnected connectionState = new Disconnected();
|
||||
|
||||
// Act
|
||||
Func<Task> action = async () => await connectionState.NegotiateReservationAsync(connectionContext);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void JoinSessionShouldThrowInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
Disconnected connectionState = new Disconnected();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.JoinSession(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisconnectShouldThrowInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
IMultiplayerSessionConnectionContext connectionContext = Substitute.For<IMultiplayerSessionConnectionContext>();
|
||||
Disconnected connectionState = new Disconnected();
|
||||
|
||||
// Act
|
||||
Action action = () => connectionState.Disconnect(connectionContext);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions.Events;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Nitrox.Test.Client.Communication.MultiplayerSession;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
using NSubstitute;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession
|
||||
{
|
||||
[TestClass]
|
||||
public class MultiplayerSessionMangerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ManagerShouldInitializeInDisconnectedStage()
|
||||
{
|
||||
// Arrange
|
||||
IClient client = Substitute.For<IClient>();
|
||||
|
||||
// Act
|
||||
IMultiplayerSession multiplayerSession = new MultiplayerSessionManager(client);
|
||||
|
||||
// Assert
|
||||
multiplayerSession.CurrentState.CurrentStage.Should().Be(MultiplayerSessionConnectionStage.DISCONNECTED);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ManagerShouldInitializeWithClient()
|
||||
{
|
||||
// Arrange
|
||||
IClient client = Substitute.For<IClient>();
|
||||
|
||||
// Act
|
||||
IMultiplayerSession multiplayerSession = new MultiplayerSessionManager(client);
|
||||
|
||||
// Assert
|
||||
multiplayerSession.Client.Should().Be(client);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConnectShouldSetIpAddress()
|
||||
{
|
||||
// Arrange
|
||||
IClient client = Substitute.For<IClient>();
|
||||
IMultiplayerSession multiplayerSession = new MultiplayerSessionManager(client, TestConstants.TEST_CONNECTION_STATE);
|
||||
|
||||
// Act
|
||||
multiplayerSession.ConnectAsync(TestConstants.TEST_IP_ADDRESS, TestConstants.TEST_SERVER_PORT);
|
||||
|
||||
// Assert
|
||||
multiplayerSession.IpAddress.Should().Be(TestConstants.TEST_IP_ADDRESS);
|
||||
multiplayerSession.ServerPort.Should().Be(TestConstants.TEST_SERVER_PORT);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessSessionPolicyShouldSetThePolicy()
|
||||
{
|
||||
// Arrange
|
||||
IClient client = Substitute.For<IClient>();
|
||||
IMultiplayerSession multiplayerSession = new MultiplayerSessionManager(client, TestConstants.TEST_CONNECTION_STATE);
|
||||
|
||||
// Act
|
||||
multiplayerSession.ProcessSessionPolicy(TestConstants.TEST_SESSION_POLICY);
|
||||
|
||||
// Assert
|
||||
multiplayerSession.SessionPolicy.Should().Be(TestConstants.TEST_SESSION_POLICY);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RequestSessionReservationShouldSetSettingsAndAuthContext()
|
||||
{
|
||||
// Arrange
|
||||
IClient client = Substitute.For<IClient>();
|
||||
IMultiplayerSession multiplayerSession = new MultiplayerSessionManager(client, TestConstants.TEST_CONNECTION_STATE);
|
||||
|
||||
// Act
|
||||
multiplayerSession.RequestSessionReservation(TestConstants.TEST_PLAYER_SETTINGS, TestConstants.TEST_AUTHENTICATION_CONTEXT);
|
||||
|
||||
// Assert
|
||||
multiplayerSession.PlayerSettings.Should().Be(TestConstants.TEST_PLAYER_SETTINGS);
|
||||
multiplayerSession.AuthenticationContext.Should().Be(TestConstants.TEST_AUTHENTICATION_CONTEXT);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessReservationResponsePacketShouldSetTheReservation()
|
||||
{
|
||||
// Arrange
|
||||
MultiplayerSessionReservation successfulReservation = new MultiplayerSessionReservation(
|
||||
TestConstants.TEST_CORRELATION_ID,
|
||||
TestConstants.TEST_PLAYER_ID,
|
||||
TestConstants.TEST_RESERVATION_KEY);
|
||||
|
||||
IClient client = Substitute.For<IClient>();
|
||||
IMultiplayerSession multiplayerSession = new MultiplayerSessionManager(client, TestConstants.TEST_CONNECTION_STATE);
|
||||
|
||||
// Act
|
||||
multiplayerSession.ProcessReservationResponsePacket(successfulReservation);
|
||||
|
||||
// Assert
|
||||
multiplayerSession.Reservation.Should().Be(successfulReservation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateStateShouldRaiseEvent()
|
||||
{
|
||||
// Arrange
|
||||
IClient client = Substitute.For<IClient>();
|
||||
IMultiplayerSession multiplayerSession = new MultiplayerSessionManager(client);
|
||||
IMultiplayerSessionConnectionContext connectionContext = (IMultiplayerSessionConnectionContext)multiplayerSession;
|
||||
IMonitor<IMultiplayerSession> monitor = multiplayerSession.Monitor();
|
||||
|
||||
// Act
|
||||
connectionContext.UpdateConnectionState(TestConstants.TEST_CONNECTION_STATE);
|
||||
|
||||
// Assert
|
||||
monitor.Should().Raise("ConnectionStateChanged");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Nitrox.Test.Client.Communication.MultiplayerSession
|
||||
{
|
||||
internal static class TestConstants
|
||||
{
|
||||
public const string TEST_IP_ADDRESS = "#.#.#.#";
|
||||
public const int TEST_SERVER_PORT = 11000;
|
||||
public const ushort TEST_PLAYER_ID = 1;
|
||||
public const string TEST_PLAYER_NAME = "TEST";
|
||||
public const string TEST_RESERVATION_KEY = "@#*(&";
|
||||
public const string TEST_CORRELATION_ID = "CORRELATED";
|
||||
public const int TEST_MAX_PLAYER_CONNECTIONS = 100;
|
||||
public const MultiplayerSessionReservationState TEST_REJECTION_STATE = MultiplayerSessionReservationState.REJECTED | MultiplayerSessionReservationState.UNIQUE_PLAYER_NAME_CONSTRAINT_VIOLATED;
|
||||
public static readonly AuthenticationContext TEST_AUTHENTICATION_CONTEXT = new AuthenticationContext(TEST_PLAYER_NAME, Optional.Empty);
|
||||
public static readonly MultiplayerSessionPolicy TEST_SESSION_POLICY = new MultiplayerSessionPolicy(TEST_CORRELATION_ID, false, TEST_MAX_PLAYER_CONNECTIONS, false);
|
||||
public static readonly PlayerSettings TEST_PLAYER_SETTINGS = new PlayerSettings(RandomColorGenerator.GenerateColor());
|
||||
public static readonly IMultiplayerSessionConnectionState TEST_CONNECTION_STATE = Substitute.For<IMultiplayerSessionConnectionState>();
|
||||
}
|
||||
}
|
53
Nitrox.Test/Client/Communication/PacketSuppressorTest.cs
Normal file
53
Nitrox.Test/Client/Communication/PacketSuppressorTest.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication;
|
||||
|
||||
[TestClass]
|
||||
public class PacketSuppressorTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void SingleSuppress()
|
||||
{
|
||||
Assert.IsFalse(PacketSuppressor<BedEnter>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODAssetPacket>.IsSuppressed);
|
||||
|
||||
using (PacketSuppressor<BedEnter>.Suppress())
|
||||
{
|
||||
Assert.IsTrue(PacketSuppressor<BedEnter>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODAssetPacket>.IsSuppressed);
|
||||
}
|
||||
|
||||
Assert.IsFalse(PacketSuppressor<BedEnter>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODAssetPacket>.IsSuppressed);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MultipleSuppress()
|
||||
{
|
||||
Assert.IsFalse(PacketSuppressor<BedEnter>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODAssetPacket>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODEventInstancePacket>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODCustomEmitterPacket>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODCustomLoopingEmitterPacket>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODStudioEmitterPacket>.IsSuppressed);
|
||||
|
||||
using (PacketSuppressor<FMODAssetPacket, FMODEventInstancePacket, FMODCustomEmitterPacket, FMODCustomLoopingEmitterPacket, FMODStudioEmitterPacket>.Suppress())
|
||||
{
|
||||
Assert.IsFalse(PacketSuppressor<BedEnter>.IsSuppressed);
|
||||
|
||||
Assert.IsTrue(PacketSuppressor<FMODAssetPacket>.IsSuppressed);
|
||||
Assert.IsTrue(PacketSuppressor<FMODEventInstancePacket>.IsSuppressed);
|
||||
Assert.IsTrue(PacketSuppressor<FMODCustomEmitterPacket>.IsSuppressed);
|
||||
Assert.IsTrue(PacketSuppressor<FMODCustomLoopingEmitterPacket>.IsSuppressed);
|
||||
Assert.IsTrue(PacketSuppressor<FMODStudioEmitterPacket>.IsSuppressed);
|
||||
}
|
||||
|
||||
Assert.IsFalse(PacketSuppressor<BedEnter>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODAssetPacket>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODEventInstancePacket>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODCustomEmitterPacket>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODCustomLoopingEmitterPacket>.IsSuppressed);
|
||||
Assert.IsFalse(PacketSuppressor<FMODStudioEmitterPacket>.IsSuppressed);
|
||||
}
|
||||
}
|
16
Nitrox.Test/Client/Communication/TestNonActionPacket.cs
Normal file
16
Nitrox.Test/Client/Communication/TestNonActionPacket.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace Nitrox.Test.Client.Communication
|
||||
{
|
||||
[Serializable]
|
||||
public class TestNonActionPacket : Packet
|
||||
{
|
||||
public ushort PlayerId { get; }
|
||||
|
||||
public TestNonActionPacket(ushort playerId)
|
||||
{
|
||||
PlayerId = playerId;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NitroxModel.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NitroxClient.GameLogic.Helper;
|
||||
|
||||
[TestClass]
|
||||
public class BaseSerializationHelperTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestBytesRestoring()
|
||||
{
|
||||
List<int> lengths = new() { 10000, 100000, 300000, 500000 };
|
||||
foreach (int length in lengths)
|
||||
{
|
||||
TestSerialization(GenerateRealisticBytes(length));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestAllZeroBytes()
|
||||
{
|
||||
List<int> lengths = new() { 10000, 100000, 300000, 500000 };
|
||||
foreach (int length in lengths)
|
||||
{
|
||||
byte[] data = new byte[length];
|
||||
TestSerialization(data);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestAllMaxBytes()
|
||||
{
|
||||
List<int> lengths = new() { 10000, 100000, 300000, 500000 };
|
||||
foreach (int length in lengths)
|
||||
{
|
||||
byte[] data = new byte[length];
|
||||
data.AsSpan().Fill(byte.MaxValue);
|
||||
TestSerialization(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates bytes to emulate Base data arrays
|
||||
/// </summary>
|
||||
private static byte[] GenerateRealisticBytes(int length)
|
||||
{
|
||||
byte[] generated = new byte[length];
|
||||
byte[] randomBytes = new byte[length];
|
||||
int randomIndex = 0;
|
||||
Random random = new();
|
||||
random.NextBytes(randomBytes);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (random.Next(100) < 3)
|
||||
{
|
||||
generated[i] = randomBytes[randomIndex++];
|
||||
}
|
||||
else
|
||||
{
|
||||
generated[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return generated;
|
||||
}
|
||||
|
||||
private static void TestSerialization(byte[] original)
|
||||
{
|
||||
byte[] compressed = BaseSerializationHelper.CompressBytes(original);
|
||||
byte[] decompressed = BaseSerializationHelper.DecompressBytes(compressed, original.Length);
|
||||
Log.Info($"Size: [Original: {original.Length}, Compressed: {compressed.Length}, Decompressed: {decompressed.Length}]");
|
||||
Log.Info($"Original: {string.Join(", ", original.Take(100))}");
|
||||
Log.Info($"Compressed: {string.Join(", ", compressed.Take(100))}");
|
||||
Log.Info($"Decompressed: {string.Join(", ", decompressed.Take(100))}\n");
|
||||
|
||||
Assert.AreEqual(original.Length, decompressed.Length);
|
||||
for (int i = 0; i < original.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(original[i], decompressed[i]);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Nitrox.Test.Client.Communication.MultiplayerSession;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using NSubstitute;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.PlayerLogic.PlayerPreferences
|
||||
{
|
||||
[TestClass]
|
||||
public class PlayerPreferenceManagerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldBeAbleToRetrieveANewPreferenceEntry()
|
||||
{
|
||||
//Given
|
||||
PlayerPreferenceState playerPreferenceState = new PlayerPreferenceState();
|
||||
playerPreferenceState.Preferences = new Dictionary<string, PlayerPreference>();
|
||||
|
||||
IPreferenceStateProvider stateProvider = Substitute.For<IPreferenceStateProvider>();
|
||||
stateProvider.GetPreferenceState().Returns(playerPreferenceState);
|
||||
|
||||
PlayerPreferenceManager playerPreferenceManager = new PlayerPreferenceManager(stateProvider);
|
||||
|
||||
PlayerPreference playerPreference = new PlayerPreference(TestConstants.TEST_PLAYER_NAME, RandomColorGenerator.GenerateColor().ToUnity());
|
||||
Color preferredColor = playerPreference.PreferredColor();
|
||||
|
||||
//When
|
||||
playerPreferenceManager.SetPreference(TestConstants.TEST_IP_ADDRESS, playerPreference);
|
||||
PlayerPreference result = playerPreferenceManager.GetPreference(TestConstants.TEST_IP_ADDRESS);
|
||||
|
||||
//Then
|
||||
result.PlayerName.Should().Be(TestConstants.TEST_PLAYER_NAME);
|
||||
result.RedAdditive.Should().Be(preferredColor.r);
|
||||
result.GreenAdditive.Should().Be(preferredColor.g);
|
||||
result.BlueAdditive.Should().Be(preferredColor.b);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeAbleToRetrieveUpdatedPreferencesForAnExistingIpAddress()
|
||||
{
|
||||
//Given
|
||||
PlayerPreferenceState playerPreferenceState = new PlayerPreferenceState();
|
||||
playerPreferenceState.Preferences = new Dictionary<string, PlayerPreference>();
|
||||
|
||||
IPreferenceStateProvider stateProvider = Substitute.For<IPreferenceStateProvider>();
|
||||
stateProvider.GetPreferenceState().Returns(playerPreferenceState);
|
||||
|
||||
PlayerPreferenceManager playerPreferenceManager = new PlayerPreferenceManager(stateProvider);
|
||||
|
||||
PlayerPreference playerPreference = new PlayerPreference(TestConstants.TEST_PLAYER_NAME, RandomColorGenerator.GenerateColor().ToUnity());
|
||||
|
||||
Color newColor = RandomColorGenerator.GenerateColor().ToUnity();
|
||||
PlayerPreference newPlayerPreference = new PlayerPreference(TestConstants.TEST_PLAYER_NAME, newColor);
|
||||
|
||||
//When
|
||||
playerPreferenceManager.SetPreference(TestConstants.TEST_IP_ADDRESS, playerPreference);
|
||||
playerPreferenceManager.SetPreference(TestConstants.TEST_IP_ADDRESS, newPlayerPreference);
|
||||
PlayerPreference result = playerPreferenceManager.GetPreference(TestConstants.TEST_IP_ADDRESS);
|
||||
|
||||
//Then
|
||||
result.RedAdditive.Should().Be(newColor.r);
|
||||
result.GreenAdditive.Should().Be(newColor.g);
|
||||
result.BlueAdditive.Should().Be(newColor.b);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetPreferenceShouldThrowExceptionWhenGivenANullIpAddress()
|
||||
{
|
||||
//Arrange
|
||||
PlayerPreferenceState playerPreferenceState = new PlayerPreferenceState();
|
||||
playerPreferenceState.Preferences = new Dictionary<string, PlayerPreference>();
|
||||
|
||||
IPreferenceStateProvider stateProvider = Substitute.For<IPreferenceStateProvider>();
|
||||
stateProvider.GetPreferenceState().Returns(playerPreferenceState);
|
||||
|
||||
PlayerPreferenceManager playerPreferenceManager = new PlayerPreferenceManager(stateProvider);
|
||||
|
||||
PlayerPreference playerPreference = new PlayerPreference(TestConstants.TEST_PLAYER_NAME, RandomColorGenerator.GenerateColor().ToUnity());
|
||||
|
||||
//Act
|
||||
Action action = () => playerPreferenceManager.SetPreference(null, playerPreference);
|
||||
|
||||
//Assert
|
||||
action.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetPreferenceShouldThrowExceptionWhenGivenANullJoinServerSettingsReference()
|
||||
{
|
||||
//Arrange
|
||||
PlayerPreferenceState playerPreferenceState = new PlayerPreferenceState();
|
||||
playerPreferenceState.Preferences = new Dictionary<string, PlayerPreference>();
|
||||
|
||||
IPreferenceStateProvider stateProvider = Substitute.For<IPreferenceStateProvider>();
|
||||
stateProvider.GetPreferenceState().Returns(playerPreferenceState);
|
||||
|
||||
PlayerPreferenceManager playerPreferenceManager = new PlayerPreferenceManager(stateProvider);
|
||||
|
||||
//Act
|
||||
Action action = () => playerPreferenceManager.SetPreference(TestConstants.TEST_IP_ADDRESS, null);
|
||||
|
||||
//Assert
|
||||
action.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGetTheLastSetPlayerPreferenceWhenJoiningANewServer()
|
||||
{
|
||||
//Given
|
||||
PlayerPreferenceState playerPreferenceState = new PlayerPreferenceState();
|
||||
playerPreferenceState.Preferences = new Dictionary<string, PlayerPreference>();
|
||||
|
||||
IPreferenceStateProvider stateProvider = Substitute.For<IPreferenceStateProvider>();
|
||||
stateProvider.GetPreferenceState().Returns(playerPreferenceState);
|
||||
|
||||
PlayerPreferenceManager playerPreferenceManager = new PlayerPreferenceManager(stateProvider);
|
||||
|
||||
PlayerPreference firstPreference = new PlayerPreference(TestConstants.TEST_PLAYER_NAME, RandomColorGenerator.GenerateColor().ToUnity());
|
||||
|
||||
string firstIpAddress = "127.0.0.1";
|
||||
playerPreferenceManager.SetPreference(firstIpAddress, firstPreference);
|
||||
|
||||
PlayerPreference secondPreference = new PlayerPreference(TestConstants.TEST_PLAYER_NAME, RandomColorGenerator.GenerateColor().ToUnity());
|
||||
|
||||
string secondIpAddress = "123.456.789.321";
|
||||
playerPreferenceManager.SetPreference(secondIpAddress, secondPreference);
|
||||
|
||||
PlayerPreference thirdPreference = new PlayerPreference(TestConstants.TEST_PLAYER_NAME, RandomColorGenerator.GenerateColor().ToUnity());
|
||||
Color expectedColor = thirdPreference.PreferredColor();
|
||||
|
||||
string thirdIpAddress = "000.000.000.000";
|
||||
playerPreferenceManager.SetPreference(thirdIpAddress, thirdPreference);
|
||||
|
||||
//When
|
||||
PlayerPreference result = playerPreferenceManager.GetPreference(TestConstants.TEST_IP_ADDRESS);
|
||||
|
||||
//Then
|
||||
result.PlayerName.Should().Be(thirdPreference.PlayerName);
|
||||
result.RedAdditive.Should().Be(expectedColor.r);
|
||||
result.GreenAdditive.Should().Be(expectedColor.g);
|
||||
result.BlueAdditive.Should().Be(expectedColor.b);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeAbleToRetrieveADefaultPreferenceWhenThePlayerHasNone()
|
||||
{
|
||||
//Given
|
||||
PlayerPreferenceState playerPreferenceState = new PlayerPreferenceState();
|
||||
playerPreferenceState.Preferences = new Dictionary<string, PlayerPreference>();
|
||||
|
||||
IPreferenceStateProvider stateProvider = Substitute.For<IPreferenceStateProvider>();
|
||||
stateProvider.GetPreferenceState().Returns(playerPreferenceState);
|
||||
|
||||
PlayerPreferenceManager playerPreferenceManager = new PlayerPreferenceManager(stateProvider);
|
||||
|
||||
//When
|
||||
PlayerPreference result = playerPreferenceManager.GetPreference(TestConstants.TEST_IP_ADDRESS);
|
||||
|
||||
//Then
|
||||
result.Should().NotBeNull();
|
||||
result.PlayerName.Should().BeNullOrEmpty();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetPreferenceShouldThrowExceptionWhenGivenNullIpAddress()
|
||||
{
|
||||
//Arrange
|
||||
PlayerPreferenceState playerPreferenceState = new PlayerPreferenceState();
|
||||
playerPreferenceState.Preferences = new Dictionary<string, PlayerPreference>();
|
||||
|
||||
IPreferenceStateProvider stateProvider = Substitute.For<IPreferenceStateProvider>();
|
||||
stateProvider.GetPreferenceState().Returns(playerPreferenceState);
|
||||
|
||||
PlayerPreferenceManager playerPreferenceManager = new PlayerPreferenceManager(stateProvider);
|
||||
|
||||
//Act
|
||||
Action action = () => playerPreferenceManager.GetPreference(null);
|
||||
|
||||
//Assert
|
||||
action.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
}
|
||||
}
|
10
Nitrox.Test/GlobalUsings.cs
Normal file
10
Nitrox.Test/GlobalUsings.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
global using static FluentAssertions.FluentActions;
|
||||
global using FluentAssertions;
|
||||
global using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
global using System;
|
||||
global using System.Collections;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Reflection;
|
||||
global using System.Threading;
|
54
Nitrox.Test/Helper/AssertHelper.cs
Normal file
54
Nitrox.Test/Helper/AssertHelper.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Nitrox.Test.Helper;
|
||||
|
||||
public static class AssertHelper
|
||||
{
|
||||
public static void IsListEqual<TSource>(IOrderedEnumerable<TSource> first, IOrderedEnumerable<TSource> second, Action<TSource, TSource> assertComparer)
|
||||
{
|
||||
Assert.IsNotNull(first);
|
||||
Assert.IsNotNull(second);
|
||||
|
||||
List<TSource> firstList = first.ToList();
|
||||
List<TSource> secondList = second.ToList();
|
||||
|
||||
Assert.AreEqual(firstList.Count, secondList.Count);
|
||||
|
||||
for (int index = 0; index < firstList.Count; index++)
|
||||
{
|
||||
assertComparer(firstList[index], secondList[index]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void IsDictionaryEqual<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second)
|
||||
{
|
||||
Assert.IsNotNull(first);
|
||||
Assert.IsNotNull(second);
|
||||
Assert.AreEqual(first.Count, second.Count);
|
||||
|
||||
for (int index = 0; index < first.Count; index++)
|
||||
{
|
||||
KeyValuePair<TKey, TValue> firstKeyValuePair = first.ElementAt(index);
|
||||
Assert.IsTrue(second.TryGetValue(firstKeyValuePair.Key, out TValue secondValue), $"Second dictionary didn't contain {firstKeyValuePair.Key}");
|
||||
Assert.AreEqual(firstKeyValuePair.Value, secondValue, $"Values didn't match with the same key: {firstKeyValuePair.Key}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void IsDictionaryEqual<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second, Action<KeyValuePair<TKey, TValue>, KeyValuePair<TKey, TValue>> assertComparer)
|
||||
{
|
||||
Assert.IsNotNull(first);
|
||||
Assert.IsNotNull(second);
|
||||
Assert.AreEqual(first.Count, second.Count);
|
||||
|
||||
for (int index = 0; index < first.Count; index++)
|
||||
{
|
||||
KeyValuePair<TKey, TValue> firstKeyValuePair = first.ElementAt(index);
|
||||
Assert.IsTrue(second.TryGetValue(firstKeyValuePair.Key, out TValue secondValue), $"Second dictionary didn't contain {firstKeyValuePair.Key}");
|
||||
|
||||
assertComparer(firstKeyValuePair, new KeyValuePair<TKey, TValue>(firstKeyValuePair.Key, secondValue));
|
||||
}
|
||||
}
|
||||
}
|
70
Nitrox.Test/Helper/Faker/NitroxAbstractFaker.cs
Normal file
70
Nitrox.Test/Helper/Faker/NitroxAbstractFaker.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NitroxModel_Subnautica.Logger;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Packets.Processors.Abstract;
|
||||
using NitroxServer;
|
||||
using NitroxServer_Subnautica;
|
||||
using NitroxServer.ConsoleCommands.Abstract;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxAbstractFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private static readonly Dictionary<Type, Type[]> subtypesByBaseType;
|
||||
|
||||
static NitroxAbstractFaker()
|
||||
{
|
||||
Assembly[] assemblies = { typeof(Packet).Assembly, typeof(SubnauticaInGameLogger).Assembly, typeof(ServerAutoFacRegistrar).Assembly, typeof(SubnauticaServerAutoFacRegistrar).Assembly };
|
||||
HashSet<Type> blacklistedTypes = new() { typeof(Packet), typeof(CorrelatedPacket), typeof(Command), typeof(PacketProcessor) };
|
||||
|
||||
List<Type> types = new();
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
types.AddRange(assembly.GetTypes());
|
||||
}
|
||||
|
||||
subtypesByBaseType = types.Where(type => type.IsAbstract && !type.IsSealed && !blacklistedTypes.Contains(type))
|
||||
.ToDictionary(type => type, type => types.Where(t => type.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface).ToArray())
|
||||
.Where(dict => dict.Value.Length > 0)
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
}
|
||||
|
||||
public readonly int AssignableTypesCount;
|
||||
private readonly Queue<INitroxFaker> assignableFakers = new();
|
||||
|
||||
public NitroxAbstractFaker(Type type)
|
||||
{
|
||||
if (!type.IsAbstract)
|
||||
{
|
||||
throw new ArgumentException("Argument is not abstract", nameof(type));
|
||||
}
|
||||
|
||||
if (!subtypesByBaseType.TryGetValue(type, out Type[] subTypes))
|
||||
{
|
||||
throw new ArgumentException($"Argument is not contained in {nameof(subtypesByBaseType)}", nameof(type));
|
||||
}
|
||||
|
||||
OutputType = type;
|
||||
AssignableTypesCount = subTypes.Length;
|
||||
FakerByType.Add(type, this);
|
||||
foreach (Type subType in subTypes)
|
||||
{
|
||||
assignableFakers.Enqueue(GetOrCreateFaker(subType));
|
||||
}
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => assignableFakers.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Selects an implementing type in a round-robin fashion of the abstract type of this faker. Then creates an instance of it.
|
||||
/// </summary>
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree)
|
||||
{
|
||||
INitroxFaker assignableFaker = assignableFakers.Dequeue();
|
||||
assignableFakers.Enqueue(assignableFaker);
|
||||
return assignableFaker.GenerateUnsafe(typeTree);
|
||||
}
|
||||
}
|
19
Nitrox.Test/Helper/Faker/NitroxActionFaker.cs
Normal file
19
Nitrox.Test/Helper/Faker/NitroxActionFaker.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxActionFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private readonly Func<Bogus.Faker, object> generateAction;
|
||||
|
||||
public NitroxActionFaker(Type type, Func<Bogus.Faker, object> action)
|
||||
{
|
||||
OutputType = type;
|
||||
generateAction = action;
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => Array.Empty<INitroxFaker>();
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> _) => generateAction.Invoke(Faker);
|
||||
}
|
239
Nitrox.Test/Helper/Faker/NitroxAutoFaker.cs
Normal file
239
Nitrox.Test/Helper/Faker/NitroxAutoFaker.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using BinaryPack.Attributes;
|
||||
using NitroxModel.Logger;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxAutoFaker<T> : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private readonly ConstructorInfo constructor;
|
||||
private readonly MemberInfo[] memberInfos;
|
||||
private readonly INitroxFaker[] parameterFakers;
|
||||
|
||||
public NitroxAutoFaker()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
if (!IsValidType(type))
|
||||
{
|
||||
throw new InvalidOperationException($"{type.Name} is not a valid type for {nameof(NitroxAutoFaker<T>)}");
|
||||
}
|
||||
|
||||
OutputType = type;
|
||||
FakerByType.Add(type, this);
|
||||
|
||||
if (type.GetCustomAttributes(typeof(DataContractAttribute), false).Length > 0)
|
||||
{
|
||||
memberInfos = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.Where(member => member.GetCustomAttributes<DataMemberAttribute>().Any()).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
memberInfos = type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(member => member.MemberType is MemberTypes.Field or MemberTypes.Property &&
|
||||
!member.GetCustomAttributes<IgnoredMemberAttribute>().Any())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
if (!TryGetConstructorForType(type, memberInfos, out constructor) &&
|
||||
!TryGetConstructorForType(type, Array.Empty<MemberInfo>(), out constructor))
|
||||
{
|
||||
throw new NullReferenceException($"Could not find a constructor with no parameters for {type}");
|
||||
}
|
||||
|
||||
parameterFakers = new INitroxFaker[memberInfos.Length];
|
||||
|
||||
Type[] constructorArgumentTypes = constructor.GetParameters().Select(p => p.ParameterType).ToArray();
|
||||
for (int i = 0; i < memberInfos.Length; i++)
|
||||
{
|
||||
Type dataMemberType = constructorArgumentTypes.Length == memberInfos.Length ? constructorArgumentTypes[i] : memberInfos[i].GetMemberType();
|
||||
|
||||
if (FakerByType.TryGetValue(dataMemberType, out INitroxFaker memberFaker))
|
||||
{
|
||||
parameterFakers[i] = memberFaker;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameterFakers[i] = CreateFaker(dataMemberType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateFakerTree()
|
||||
{
|
||||
List<INitroxFaker> fakerTree = new();
|
||||
|
||||
void ValidateFaker(INitroxFaker nitroxFaker)
|
||||
{
|
||||
if (fakerTree.Contains(nitroxFaker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fakerTree.Add(nitroxFaker);
|
||||
|
||||
if (nitroxFaker is NitroxAbstractFaker abstractFaker)
|
||||
{
|
||||
NitroxCollectionFaker collectionFaker = (NitroxCollectionFaker)fakerTree.LastOrDefault(f => f.GetType() == typeof(NitroxCollectionFaker));
|
||||
if (collectionFaker != null)
|
||||
{
|
||||
collectionFaker.GenerateSize = Math.Max(collectionFaker.GenerateSize, abstractFaker.AssignableTypesCount);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (INitroxFaker subFaker in nitroxFaker.GetSubFakers())
|
||||
{
|
||||
ValidateFaker(subFaker);
|
||||
}
|
||||
|
||||
fakerTree.Remove(nitroxFaker);
|
||||
}
|
||||
|
||||
foreach (INitroxFaker parameterFaker in parameterFakers)
|
||||
{
|
||||
ValidateFaker(parameterFaker);
|
||||
}
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => parameterFakers;
|
||||
|
||||
public T Generate()
|
||||
{
|
||||
ValidateFakerTree();
|
||||
return (T)GenerateUnsafe(new HashSet<Type>());
|
||||
}
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree)
|
||||
{
|
||||
object[] parameterValues = new object[parameterFakers.Length];
|
||||
|
||||
for (int i = 0; i < parameterValues.Length; i++)
|
||||
{
|
||||
INitroxFaker parameterFaker = parameterFakers[i];
|
||||
|
||||
if (typeTree.Contains(parameterFaker.OutputType))
|
||||
{
|
||||
if (parameterFaker is NitroxCollectionFaker collectionFaker)
|
||||
{
|
||||
parameterValues[i] = Activator.CreateInstance(collectionFaker.OutputCollectionType);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameterValues[i] = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
typeTree.Add(parameterFaker.OutputType);
|
||||
parameterValues[i] = parameterFakers[i].GenerateUnsafe(typeTree);
|
||||
typeTree.Remove(parameterFaker.OutputType);
|
||||
}
|
||||
}
|
||||
|
||||
if (constructor.GetParameters().Length == parameterValues.Length)
|
||||
{
|
||||
return (T)constructor.Invoke(parameterValues);
|
||||
}
|
||||
|
||||
T obj = (T)constructor.Invoke(Array.Empty<object>());
|
||||
for (int index = 0; index < memberInfos.Length; index++)
|
||||
{
|
||||
MemberInfo memberInfo = memberInfos[index];
|
||||
switch (memberInfo.MemberType)
|
||||
{
|
||||
case MemberTypes.Field:
|
||||
((FieldInfo)memberInfo).SetValue(obj, parameterValues[index]);
|
||||
break;
|
||||
case MemberTypes.Property:
|
||||
PropertyInfo propertyInfo = (PropertyInfo)memberInfo;
|
||||
|
||||
if (!propertyInfo.CanWrite &&
|
||||
NitroxCollectionFaker.IsCollection(parameterValues[index].GetType(), out NitroxCollectionFaker.CollectionType collectionType))
|
||||
{
|
||||
dynamic origColl = propertyInfo.GetValue(obj);
|
||||
|
||||
switch (collectionType)
|
||||
{
|
||||
case NitroxCollectionFaker.CollectionType.ARRAY:
|
||||
for (int i = 0; i < ((Array)parameterValues[index]).Length; i++)
|
||||
{
|
||||
origColl[i] = ((Array)parameterValues[index]).GetValue(i);
|
||||
}
|
||||
|
||||
break;
|
||||
case NitroxCollectionFaker.CollectionType.LIST:
|
||||
case NitroxCollectionFaker.CollectionType.DICTIONARY:
|
||||
case NitroxCollectionFaker.CollectionType.SET:
|
||||
foreach (dynamic createdValue in ((IEnumerable)parameterValues[index]))
|
||||
{
|
||||
origColl.Add(createdValue);
|
||||
}
|
||||
|
||||
break;
|
||||
case NitroxCollectionFaker.CollectionType.QUEUE:
|
||||
foreach (dynamic createdValue in ((IEnumerable)parameterValues[index]))
|
||||
{
|
||||
origColl.Enqueue(createdValue);
|
||||
}
|
||||
|
||||
break;
|
||||
case NitroxCollectionFaker.CollectionType.NONE:
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
else if(propertyInfo.CanWrite)
|
||||
{
|
||||
propertyInfo.SetValue(obj, parameterValues[index]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Regex backingFieldNameRegex = new($"\\A<{propertyInfo.Name}>k__BackingField\\Z");
|
||||
FieldInfo backingField = propertyInfo.DeclaringType.GetRuntimeFields().FirstOrDefault(a => backingFieldNameRegex.IsMatch(a.Name));
|
||||
|
||||
if (backingField == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{propertyInfo.DeclaringType}.{propertyInfo.Name} is not accessible for writing");
|
||||
}
|
||||
|
||||
backingField.SetValue(obj, parameterValues[index]);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static bool TryGetConstructorForType(Type type, MemberInfo[] dataMembers, out ConstructorInfo constructorInfo)
|
||||
{
|
||||
foreach (ConstructorInfo constructor in type.GetConstructors())
|
||||
{
|
||||
if (constructor.GetParameters().Length != dataMembers.Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool parameterValid = constructor.GetParameters()
|
||||
.All(parameter => dataMembers.Any(d => d.GetMemberType() == parameter.ParameterType &&
|
||||
d.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
if (parameterValid)
|
||||
{
|
||||
constructorInfo = constructor;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
constructorInfo = null;
|
||||
return false;
|
||||
}
|
||||
}
|
202
Nitrox.Test/Helper/Faker/NitroxCollectionFaker.cs
Normal file
202
Nitrox.Test/Helper/Faker/NitroxCollectionFaker.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using NitroxModel.DataStructures;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxCollectionFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
public enum CollectionType
|
||||
{
|
||||
NONE,
|
||||
ARRAY,
|
||||
LIST,
|
||||
DICTIONARY,
|
||||
SET,
|
||||
QUEUE
|
||||
}
|
||||
|
||||
private const int DEFAULT_SIZE = 2;
|
||||
|
||||
public static bool IsCollection(Type t, out CollectionType collectionType)
|
||||
{
|
||||
if (t.IsArray && t.GetArrayRank() == 1)
|
||||
{
|
||||
collectionType = CollectionType.ARRAY;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (t.IsGenericType)
|
||||
{
|
||||
Type[] genericInterfacesDefinition = t.GetInterfaces()
|
||||
.Where(i => i.IsGenericType)
|
||||
.Select(i => i.GetGenericTypeDefinition())
|
||||
.ToArray();
|
||||
|
||||
if (genericInterfacesDefinition.Any(i => i == typeof(IList<>)))
|
||||
{
|
||||
collectionType = CollectionType.LIST;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (genericInterfacesDefinition.Any(i => i == typeof(IDictionary<,>)))
|
||||
{
|
||||
collectionType = CollectionType.DICTIONARY;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (genericInterfacesDefinition.Any(i => i == typeof(ISet<>)))
|
||||
{
|
||||
collectionType = CollectionType.SET;
|
||||
return true;
|
||||
}
|
||||
|
||||
Type genericTypeDefinition = t.GetGenericTypeDefinition();
|
||||
if (genericTypeDefinition == typeof(Queue<>) || genericTypeDefinition == typeof(ThreadSafeQueue<>)) // Queue has no defining interface
|
||||
{
|
||||
collectionType = CollectionType.QUEUE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
collectionType = CollectionType.NONE;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetCollectionTypes(Type type, out Type[] types)
|
||||
{
|
||||
if (!IsCollection(type, out CollectionType collectionType))
|
||||
{
|
||||
types = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (collectionType == CollectionType.ARRAY)
|
||||
{
|
||||
types = [type.GetElementType()];
|
||||
return true;
|
||||
}
|
||||
|
||||
types = type.GenericTypeArguments;
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GenerateSize = DEFAULT_SIZE;
|
||||
public Type OutputCollectionType;
|
||||
|
||||
private readonly INitroxFaker[] subFakers;
|
||||
private readonly Func<HashSet<Type>, object> generateAction;
|
||||
|
||||
public NitroxCollectionFaker(Type type, CollectionType collectionType)
|
||||
{
|
||||
OutputCollectionType = type;
|
||||
INitroxFaker elementFaker;
|
||||
|
||||
switch (collectionType)
|
||||
{
|
||||
case CollectionType.ARRAY:
|
||||
Type arrayType = OutputType = type.GetElementType();
|
||||
elementFaker = GetOrCreateFaker(arrayType);
|
||||
subFakers = [elementFaker];
|
||||
|
||||
generateAction = typeTree =>
|
||||
{
|
||||
typeTree.Add(arrayType);
|
||||
Array array = Array.CreateInstance(arrayType, GenerateSize);
|
||||
|
||||
for (int i = 0; i < GenerateSize; i++)
|
||||
{
|
||||
array.SetValue(elementFaker.GenerateUnsafe(typeTree), i);
|
||||
}
|
||||
|
||||
typeTree.Remove(arrayType);
|
||||
return array;
|
||||
};
|
||||
break;
|
||||
case CollectionType.LIST:
|
||||
case CollectionType.SET:
|
||||
Type listType = OutputType = type.GenericTypeArguments[0];
|
||||
elementFaker = GetOrCreateFaker(listType);
|
||||
subFakers = [elementFaker];
|
||||
|
||||
generateAction = typeTree =>
|
||||
{
|
||||
typeTree.Add(listType);
|
||||
dynamic list = Activator.CreateInstance(type);
|
||||
|
||||
for (int i = 0; i < GenerateSize; i++)
|
||||
{
|
||||
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
|
||||
dynamic castedObject = castMethod.Invoke(null, [elementFaker.GenerateUnsafe(typeTree)]);
|
||||
list.Add(castedObject);
|
||||
}
|
||||
|
||||
typeTree.Remove(listType);
|
||||
return list;
|
||||
};
|
||||
break;
|
||||
case CollectionType.DICTIONARY:
|
||||
Type[] dicType = type.GenericTypeArguments;
|
||||
OutputType = dicType[1]; // A little hacky but should work as we don't use circular dependencies as keys
|
||||
INitroxFaker keyFaker = GetOrCreateFaker(dicType[0]);
|
||||
INitroxFaker valueFaker = GetOrCreateFaker(dicType[1]);
|
||||
subFakers = [keyFaker, valueFaker];
|
||||
|
||||
generateAction = typeTree =>
|
||||
{
|
||||
typeTree.Add(dicType[0]);
|
||||
typeTree.Add(dicType[1]);
|
||||
IDictionary dict = (IDictionary)Activator.CreateInstance(type);
|
||||
for (int i = 0; i < GenerateSize; i++)
|
||||
{
|
||||
for (int tries = 0; tries < 10; tries++)
|
||||
{
|
||||
object key = keyFaker.GenerateUnsafe(typeTree);
|
||||
|
||||
if (!dict.Contains(key))
|
||||
{
|
||||
dict.Add(key, valueFaker.GenerateUnsafe(typeTree));
|
||||
break;
|
||||
}
|
||||
|
||||
if (tries == 9)
|
||||
{
|
||||
throw new InvalidOperationException($"While generating action for filling Dictionary an unique key of {dicType[0]} couldn't be generated even after 10 tries");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typeTree.Remove(dicType[0]);
|
||||
typeTree.Remove(dicType[1]);
|
||||
return dict;
|
||||
};
|
||||
break;
|
||||
case CollectionType.QUEUE:
|
||||
Type queueType = OutputType = type.GenericTypeArguments[0];
|
||||
elementFaker = GetOrCreateFaker(queueType);
|
||||
subFakers = [elementFaker];
|
||||
|
||||
generateAction = typeTree =>
|
||||
{
|
||||
typeTree.Add(queueType);
|
||||
dynamic queue = Activator.CreateInstance(type);
|
||||
|
||||
for (int i = 0; i < GenerateSize; i++)
|
||||
{
|
||||
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
|
||||
dynamic castedObject = castMethod.Invoke(null, [elementFaker.GenerateUnsafe(typeTree)]);
|
||||
queue!.Enqueue(castedObject);
|
||||
}
|
||||
|
||||
typeTree.Remove(queueType);
|
||||
return queue;
|
||||
};
|
||||
break;
|
||||
case CollectionType.NONE:
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(collectionType), collectionType, null);
|
||||
}
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => subFakers;
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree) => generateAction.Invoke(typeTree);
|
||||
}
|
119
Nitrox.Test/Helper/Faker/NitroxFaker.cs
Normal file
119
Nitrox.Test/Helper/Faker/NitroxFaker.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public interface INitroxFaker
|
||||
{
|
||||
public Type OutputType { get; }
|
||||
public INitroxFaker[] GetSubFakers();
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree);
|
||||
}
|
||||
|
||||
public abstract class NitroxFaker
|
||||
{
|
||||
public Type OutputType { get; protected init; }
|
||||
protected static readonly Bogus.Faker Faker;
|
||||
|
||||
static NitroxFaker()
|
||||
{
|
||||
Faker = new Bogus.Faker();
|
||||
}
|
||||
|
||||
protected static readonly Dictionary<Type, INitroxFaker> FakerByType = new()
|
||||
{
|
||||
// Basic types
|
||||
{ typeof(bool), new NitroxActionFaker(typeof(bool), f => f.Random.Bool()) },
|
||||
{ typeof(byte), new NitroxActionFaker(typeof(byte), f => f.Random.Byte()) },
|
||||
{ typeof(sbyte), new NitroxActionFaker(typeof(sbyte), f => f.Random.SByte()) },
|
||||
{ typeof(short), new NitroxActionFaker(typeof(short), f => f.Random.Short()) },
|
||||
{ typeof(ushort), new NitroxActionFaker(typeof(ushort), f => f.Random.UShort()) },
|
||||
{ typeof(int), new NitroxActionFaker(typeof(int), f => f.Random.Int()) },
|
||||
{ typeof(uint), new NitroxActionFaker(typeof(uint), f => f.Random.UInt()) },
|
||||
{ typeof(long), new NitroxActionFaker(typeof(long), f => f.Random.Long()) },
|
||||
{ typeof(ulong), new NitroxActionFaker(typeof(ulong), f => f.Random.ULong()) },
|
||||
{ typeof(decimal), new NitroxActionFaker(typeof(decimal), f => f.Random.Decimal()) },
|
||||
{ typeof(float), new NitroxActionFaker(typeof(float), f => f.Random.Float()) },
|
||||
{ typeof(double), new NitroxActionFaker(typeof(double), f => f.Random.Double()) },
|
||||
{ typeof(char), new NitroxActionFaker(typeof(char), f => f.Random.Char()) },
|
||||
{ typeof(string), new NitroxActionFaker(typeof(string), f => f.Random.Word()) },
|
||||
|
||||
// Nitrox types
|
||||
{ typeof(NitroxTechType), new NitroxActionFaker(typeof(NitroxTechType), f => new NitroxTechType(f.PickRandom<TechType>().ToString())) },
|
||||
{ typeof(NitroxId), new NitroxActionFaker(typeof(NitroxId), f => new NitroxId(f.Random.Guid())) },
|
||||
};
|
||||
|
||||
public static INitroxFaker GetOrCreateFaker(Type t)
|
||||
{
|
||||
return FakerByType.TryGetValue(t, out INitroxFaker nitroxFaker) ? nitroxFaker : CreateFaker(t);
|
||||
}
|
||||
|
||||
protected static INitroxFaker CreateFaker(Type type)
|
||||
{
|
||||
if (type.IsAbstract)
|
||||
{
|
||||
return new NitroxAbstractFaker(type);
|
||||
}
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
return new NitroxActionFaker(type, f =>
|
||||
{
|
||||
string[] selection = Enum.GetNames(type);
|
||||
if (selection.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("There are no enum values after exclusion to choose from.");
|
||||
}
|
||||
|
||||
string val = f.Random.ArrayElement(selection);
|
||||
return Enum.Parse(type, val);
|
||||
});
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>))
|
||||
{
|
||||
return new NitroxOptionalFaker(type);
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
return new NitroxNullableFaker(type);
|
||||
}
|
||||
|
||||
if (NitroxCollectionFaker.IsCollection(type, out NitroxCollectionFaker.CollectionType collectionType))
|
||||
{
|
||||
return new NitroxCollectionFaker(type, collectionType);
|
||||
}
|
||||
|
||||
ConstructorInfo constructor = typeof(NitroxAutoFaker<>).MakeGenericType(type).GetConstructor(Array.Empty<Type>());
|
||||
|
||||
if (constructor == null)
|
||||
{
|
||||
throw new NullReferenceException($"Could not get generic constructor for {type}");
|
||||
}
|
||||
|
||||
return (INitroxFaker)constructor.Invoke(Array.Empty<object>());
|
||||
}
|
||||
|
||||
protected static bool IsValidType(Type type)
|
||||
{
|
||||
return FakerByType.ContainsKey(type) ||
|
||||
type.GetCustomAttributes(typeof(DataContractAttribute), false).Length >= 1 ||
|
||||
type.GetCustomAttributes(typeof(SerializableAttribute), false).Length >= 1 ||
|
||||
(NitroxCollectionFaker.TryGetCollectionTypes(type, out Type[] collectionTypes) && collectionTypes.All(IsValidType)) ||
|
||||
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
|
||||
}
|
||||
|
||||
protected static readonly MethodInfo CastMethodBase = typeof(NitroxFaker).GetMethod(nameof(Cast), BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
protected static T Cast<T>(object o)
|
||||
{
|
||||
return (T)o;
|
||||
}
|
||||
}
|
27
Nitrox.Test/Helper/Faker/NitroxNullableFaker.cs
Normal file
27
Nitrox.Test/Helper/Faker/NitroxNullableFaker.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxNullableFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private readonly Func<HashSet<Type>, object> generateAction;
|
||||
|
||||
public NitroxNullableFaker(Type type)
|
||||
{
|
||||
OutputType = type.GenericTypeArguments[0];
|
||||
generateAction = (typeTree) =>
|
||||
{
|
||||
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
|
||||
object castedObject = castMethod.Invoke(null, new[] { GetOrCreateFaker(OutputType).GenerateUnsafe(typeTree) });
|
||||
|
||||
Type nullableType = typeof(Nullable<>).MakeGenericType(OutputType);
|
||||
return Activator.CreateInstance(nullableType, castedObject);
|
||||
};
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => new[] { GetOrCreateFaker(OutputType) };
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree) => generateAction.Invoke(typeTree);
|
||||
}
|
28
Nitrox.Test/Helper/Faker/NitroxOptionalFaker.cs
Normal file
28
Nitrox.Test/Helper/Faker/NitroxOptionalFaker.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxOptionalFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private readonly Func<HashSet<Type>, object> generateAction;
|
||||
|
||||
public NitroxOptionalFaker(Type type)
|
||||
{
|
||||
OutputType = type.GenericTypeArguments[0];
|
||||
generateAction = (typeTree) =>
|
||||
{
|
||||
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
|
||||
object castedObject = castMethod.Invoke(null, new[] { GetOrCreateFaker(OutputType).GenerateUnsafe(typeTree) });
|
||||
|
||||
Type optionalType = typeof(Optional<>).MakeGenericType(OutputType);
|
||||
return Activator.CreateInstance(optionalType, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance, null, new[] { castedObject }, null);
|
||||
};
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => new[] { GetOrCreateFaker(OutputType) };
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree) => generateAction.Invoke(typeTree);
|
||||
}
|
22
Nitrox.Test/Helper/TypeExtension.cs
Normal file
22
Nitrox.Test/Helper/TypeExtension.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Nitrox.Test.Helper;
|
||||
|
||||
public static class TypeExtension
|
||||
{
|
||||
public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
|
||||
{
|
||||
if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Type givenBaseType = givenType.BaseType;
|
||||
if (givenBaseType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsAssignableToGenericType(givenBaseType, genericType);
|
||||
}
|
||||
}
|
148
Nitrox.Test/Model/Core/DependencyInjectionTests.cs
Normal file
148
Nitrox.Test/Model/Core/DependencyInjectionTests.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using Autofac;
|
||||
|
||||
namespace NitroxModel.Core;
|
||||
|
||||
[TestClass]
|
||||
public class DependencyInjectionTests
|
||||
{
|
||||
[TestInitialize]
|
||||
public void Init()
|
||||
{
|
||||
NitroxServiceLocator.InitializeDependencyContainer(new DependencyInjectionTestsAutoFacRegistrar());
|
||||
NitroxServiceLocator.BeginNewLifetimeScope();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldResolveDependencyPolymorphically()
|
||||
{
|
||||
// Arrange
|
||||
IRootDependency polymorphicallyResolvedDependency = NitroxServiceLocator.LocateService<IRootDependency>();
|
||||
|
||||
// Assert
|
||||
polymorphicallyResolvedDependency.Should().NotBeNull();
|
||||
polymorphicallyResolvedDependency.Should().BeOfType<RootDependency>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldResolveConcreteType()
|
||||
{
|
||||
// Arrange
|
||||
DependencyWithRootDependency directConcreteTypeDependency = NitroxServiceLocator.LocateService<DependencyWithRootDependency>();
|
||||
|
||||
// Assert
|
||||
directConcreteTypeDependency.Should().NotBeNull();
|
||||
directConcreteTypeDependency.RootDependency.Should().NotBeNull();
|
||||
directConcreteTypeDependency.RootDependency.Should().BeOfType<RootDependency>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldResolveGenericDependencies()
|
||||
{
|
||||
// Arrange
|
||||
IServicer<ServiceRecipientA> servicerA = NitroxServiceLocator.LocateService<IServicer<ServiceRecipientA>>();
|
||||
IServicer<ServiceRecipientB> servicerB = NitroxServiceLocator.LocateService<IServicer<ServiceRecipientB>>();
|
||||
|
||||
// Assert
|
||||
servicerA.Should().NotBeNull();
|
||||
servicerA.Should().BeOfType<ServiceAProvider>();
|
||||
Invoking(() => servicerA.PerformService(null)).Should().Throw<NotImplementedException>();
|
||||
|
||||
servicerB.Should().NotBeNull();
|
||||
servicerB.Should().BeOfType<ServiceBProvider>();
|
||||
Invoking(() => servicerB.PerformService(null)).Should().Throw<NotImplementedException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldResolveGenericDependenciesFromManuallyConstructedTypeInstances()
|
||||
{
|
||||
// Arrange
|
||||
Type servicerInstanceType = typeof(IServicer<>);
|
||||
Type recipientAType = typeof(ServiceRecipientA);
|
||||
Type recipientBType = typeof(ServiceRecipientB);
|
||||
Type servicerAType = servicerInstanceType.MakeGenericType(recipientAType);
|
||||
Type servicerBType = servicerInstanceType.MakeGenericType(recipientBType);
|
||||
|
||||
// Act
|
||||
IServicer<ServiceRecipientA> servicerA = (BaseServiceProvider<ServiceRecipientA>)NitroxServiceLocator.LocateService(servicerAType);
|
||||
IServicer<ServiceRecipientB> servicerB = (BaseServiceProvider<ServiceRecipientB>)NitroxServiceLocator.LocateService(servicerBType);
|
||||
|
||||
// Assert
|
||||
servicerA.Should().NotBeNull();
|
||||
servicerA.Should().BeOfType<ServiceAProvider>();
|
||||
Invoking(() => servicerA.PerformService(null)).Should().Throw<NotImplementedException>();
|
||||
|
||||
servicerB.Should().NotBeNull();
|
||||
servicerB.Should().BeOfType<ServiceBProvider>();
|
||||
Invoking(() => servicerB.PerformService(null)).Should().Throw<NotImplementedException>();
|
||||
}
|
||||
|
||||
private class DependencyInjectionTestsAutoFacRegistrar : IAutoFacRegistrar
|
||||
{
|
||||
public void RegisterDependencies(ContainerBuilder containerBuilder)
|
||||
{
|
||||
containerBuilder.RegisterType<RootDependency>().As<IRootDependency>();
|
||||
containerBuilder.RegisterType<DependencyWithRootDependency>();
|
||||
|
||||
containerBuilder.RegisterAssemblyTypes(Assembly.GetAssembly(GetType()))
|
||||
.AsClosedTypesOf(typeof(IServicer<>));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IRootDependency
|
||||
{
|
||||
}
|
||||
|
||||
public class RootDependency : IRootDependency
|
||||
{
|
||||
}
|
||||
|
||||
public class DependencyWithRootDependency
|
||||
{
|
||||
public IRootDependency RootDependency { get; }
|
||||
|
||||
public DependencyWithRootDependency(IRootDependency rootDependency)
|
||||
{
|
||||
RootDependency = rootDependency;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IServiced
|
||||
{
|
||||
}
|
||||
|
||||
public interface IServicer<T>
|
||||
where T : IServiced
|
||||
{
|
||||
void PerformService(T serviced);
|
||||
}
|
||||
|
||||
public class ServiceRecipientA : IServiced
|
||||
{
|
||||
}
|
||||
|
||||
public class ServiceRecipientB : IServiced
|
||||
{
|
||||
}
|
||||
|
||||
public abstract class BaseServiceProvider<TServiced> : IServicer<TServiced>
|
||||
where TServiced : IServiced
|
||||
{
|
||||
public abstract void PerformService(TServiced serviced);
|
||||
}
|
||||
|
||||
public class ServiceAProvider : BaseServiceProvider<ServiceRecipientA>
|
||||
{
|
||||
public override void PerformService(ServiceRecipientA serviced)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class ServiceBProvider : BaseServiceProvider<ServiceRecipientB>
|
||||
{
|
||||
public override void PerformService(ServiceRecipientB serviced)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
117
Nitrox.Test/Model/DataStructures/CircularBufferTest.cs
Normal file
117
Nitrox.Test/Model/DataStructures/CircularBufferTest.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures;
|
||||
|
||||
[TestClass]
|
||||
public class CircularBufferTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldLimitSizeToMaxSize()
|
||||
{
|
||||
CircularBuffer<string> buffer = new(1);
|
||||
buffer.Count.Should().Be(0);
|
||||
buffer.Add("1");
|
||||
buffer.Count.Should().Be(1);
|
||||
buffer.Add("2");
|
||||
buffer.Count.Should().Be(1);
|
||||
|
||||
buffer = new CircularBuffer<string>(5);
|
||||
buffer.Count.Should().Be(0);
|
||||
buffer.Add("1");
|
||||
buffer.Count.Should().Be(1);
|
||||
buffer.Add("2");
|
||||
buffer.Count.Should().Be(2);
|
||||
buffer.Add("3");
|
||||
buffer.Count.Should().Be(3);
|
||||
buffer.Add("4");
|
||||
buffer.Count.Should().Be(4);
|
||||
buffer.Add("5");
|
||||
buffer.Count.Should().Be(5);
|
||||
buffer.Add("6");
|
||||
buffer.Count.Should().Be(5);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldOverwriteOldestItemInBufferWhenCapped()
|
||||
{
|
||||
CircularBuffer<string> buffer = new(3);
|
||||
buffer.Add("1");
|
||||
buffer[0].Should().Be("1");
|
||||
buffer.Add("2");
|
||||
buffer[1].Should().Be("2");
|
||||
buffer.Add("3");
|
||||
buffer[2].Should().Be("3");
|
||||
buffer.Add("4");
|
||||
buffer[0].Should().Be("4");
|
||||
buffer.Add("5");
|
||||
buffer[1].Should().Be("5");
|
||||
buffer[2].Should().Be("3");
|
||||
buffer.Add("6");
|
||||
buffer[2].Should().Be("6");
|
||||
buffer.Add("7");
|
||||
buffer.Should().ContainInOrder("7", "5", "6");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldDiscardAddIfCapacityReached()
|
||||
{
|
||||
CircularBuffer<string> buffer = new(0);
|
||||
buffer.Count.Should().Be(0);
|
||||
buffer.Add("1");
|
||||
buffer.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeEmptyWhenCleared()
|
||||
{
|
||||
CircularBuffer<string> buffer = new(10);
|
||||
buffer.Count.Should().Be(0);
|
||||
buffer.Add("1");
|
||||
buffer.Add("1");
|
||||
buffer.Add("1");
|
||||
buffer.Count.Should().Be(3);
|
||||
buffer.Clear();
|
||||
buffer.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldGiveLastChanged()
|
||||
{
|
||||
CircularBuffer<int> buffer = new(3);
|
||||
buffer.LastChangedIndex.Should().Be(-1);
|
||||
buffer.Add(1);
|
||||
buffer.LastChangedIndex.Should().Be(0);
|
||||
buffer.Add(2);
|
||||
buffer.LastChangedIndex.Should().Be(1);
|
||||
buffer.Add(3);
|
||||
buffer.LastChangedIndex.Should().Be(2);
|
||||
buffer.Add(4);
|
||||
buffer.LastChangedIndex.Should().Be(0);
|
||||
buffer.Add(5);
|
||||
buffer.LastChangedIndex.Should().Be(1);
|
||||
buffer.Add(6);
|
||||
buffer.LastChangedIndex.Should().Be(2);
|
||||
buffer.Add(7);
|
||||
buffer.LastChangedIndex.Should().Be(0);
|
||||
buffer.Add(8);
|
||||
buffer.LastChangedIndex.Should().Be(1);
|
||||
buffer.Clear();
|
||||
buffer.LastChangedIndex.Should().Be(-1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldReverseOrderWithNegativeIndex()
|
||||
{
|
||||
CircularBuffer<int> buffer = new(6);
|
||||
buffer.AddRange(1, 2, 3, 4, 5, 6);
|
||||
buffer[-1].Should().Be(6);
|
||||
buffer[-2].Should().Be(5);
|
||||
buffer[-3].Should().Be(4);
|
||||
buffer[-4].Should().Be(3);
|
||||
buffer[-5].Should().Be(2);
|
||||
buffer[-6].Should().Be(1);
|
||||
buffer[-7].Should().Be(6);
|
||||
buffer[-8].Should().Be(5);
|
||||
}
|
||||
}
|
45
Nitrox.Test/Model/DataStructures/NitroxIdTest.cs
Normal file
45
Nitrox.Test/Model/DataStructures/NitroxIdTest.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures;
|
||||
|
||||
[TestClass]
|
||||
public class NitroxIdTest
|
||||
{
|
||||
private NitroxId id1;
|
||||
private NitroxId id2;
|
||||
|
||||
[TestMethod]
|
||||
public void SameGuidEquality()
|
||||
{
|
||||
Guid guid = Guid.NewGuid();
|
||||
id1 = new(guid);
|
||||
id2 = new(guid);
|
||||
|
||||
(id1 == id2).Should().BeTrue();
|
||||
id1.Equals(id2).Should().BeTrue();
|
||||
(id1 != id2).Should().BeFalse();
|
||||
(!id1.Equals(id2)).Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NullGuidEquality()
|
||||
{
|
||||
id1 = new();
|
||||
id2 = null;
|
||||
|
||||
(id1 == id2).Should().BeFalse();
|
||||
id1.Equals(id2).Should().BeFalse();
|
||||
(id1 != id2).Should().BeTrue();
|
||||
(!id1.Equals(id2)).Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BothNullEquality()
|
||||
{
|
||||
id1 = id2 = null;
|
||||
(id1 != id2).Should().BeFalse();
|
||||
(id1 == id2).Should().BeTrue();
|
||||
}
|
||||
}
|
39
Nitrox.Test/Model/DataStructures/NitroxInt3Test.cs
Normal file
39
Nitrox.Test/Model/DataStructures/NitroxInt3Test.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures
|
||||
{
|
||||
[TestClass]
|
||||
public class NitroxInt3Test
|
||||
{
|
||||
private NitroxInt3 int3;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
int3 = new NitroxInt3(5, 10, 15);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Equals()
|
||||
{
|
||||
NitroxInt3 other1 = new NitroxInt3(5, 10, 15);
|
||||
NitroxInt3 other2 = new NitroxInt3(15, 10, 5);
|
||||
|
||||
int3.Equals(other1).Should().BeTrue();
|
||||
int3.Equals(other2).Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Floor()
|
||||
{
|
||||
NitroxInt3.Floor(5.1f, 10.4f, 15.5f).Should().Be(int3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ceil()
|
||||
{
|
||||
NitroxInt3.Ceil(4.1f, 9.4f, 14.5f).Should().Be(int3);
|
||||
}
|
||||
}
|
||||
}
|
29
Nitrox.Test/Model/DataStructures/NitroxVersionTest.cs
Normal file
29
Nitrox.Test/Model/DataStructures/NitroxVersionTest.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures;
|
||||
|
||||
[TestClass]
|
||||
public class NitroxVersionTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void Equals()
|
||||
{
|
||||
NitroxVersion a = new(2, 1);
|
||||
NitroxVersion b = new(1, 15);
|
||||
|
||||
NitroxVersion source = new(2, 1);
|
||||
source.Equals(a).Should().BeTrue();
|
||||
source.Equals(b).Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Compare()
|
||||
{
|
||||
NitroxVersion source = new(2, 1);
|
||||
source.CompareTo(new(2, 1)).Should().Be(0);
|
||||
source.CompareTo(new(1, 15)).Should().Be(1);
|
||||
source.CompareTo(new (2, 2)).Should().Be(-1);
|
||||
source.CompareTo(new (3, 1)).Should().Be(-1);
|
||||
}
|
||||
}
|
78
Nitrox.Test/Model/DataStructures/PriorityQueueTest.cs
Normal file
78
Nitrox.Test/Model/DataStructures/PriorityQueueTest.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures
|
||||
{
|
||||
using StringPriorityQueue = PriorityQueue<string>;
|
||||
|
||||
[TestClass]
|
||||
public class PriorityQueueTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void SameOrder()
|
||||
{
|
||||
StringPriorityQueue queue = new StringPriorityQueue();
|
||||
queue.Enqueue(0, "First");
|
||||
queue.Enqueue(0, "Second");
|
||||
queue.Enqueue(0, "Third");
|
||||
|
||||
Assert.AreEqual("First", queue.Dequeue());
|
||||
Assert.AreEqual("Second", queue.Dequeue());
|
||||
Assert.AreEqual("Third", queue.Dequeue());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DifferentOrder()
|
||||
{
|
||||
StringPriorityQueue queue = new StringPriorityQueue();
|
||||
queue.Enqueue(3, "First");
|
||||
queue.Enqueue(2, "Second");
|
||||
queue.Enqueue(1, "Third");
|
||||
|
||||
Assert.AreEqual("First", queue.Dequeue());
|
||||
Assert.AreEqual("Second", queue.Dequeue());
|
||||
Assert.AreEqual("Third", queue.Dequeue());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SomeAreSameOrder()
|
||||
{
|
||||
StringPriorityQueue queue = new StringPriorityQueue();
|
||||
queue.Enqueue(2, "First");
|
||||
queue.Enqueue(2, "Second");
|
||||
queue.Enqueue(0, "Third");
|
||||
|
||||
Assert.AreEqual("First", queue.Dequeue());
|
||||
Assert.AreEqual("Second", queue.Dequeue());
|
||||
Assert.AreEqual("Third", queue.Dequeue());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PrioritySanity()
|
||||
{
|
||||
StringPriorityQueue queue = new StringPriorityQueue();
|
||||
queue.Enqueue(2, "Second");
|
||||
queue.Enqueue(3, "First");
|
||||
queue.Enqueue(1, "Third");
|
||||
|
||||
Assert.AreEqual("First", queue.Dequeue());
|
||||
Assert.AreEqual("Second", queue.Dequeue());
|
||||
Assert.AreEqual("Third", queue.Dequeue());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CountSanity()
|
||||
{
|
||||
StringPriorityQueue queue = new StringPriorityQueue();
|
||||
queue.Enqueue(2, "Second");
|
||||
queue.Enqueue(3, "First");
|
||||
queue.Enqueue(1, "Third");
|
||||
|
||||
Assert.AreEqual(3, queue.Count);
|
||||
|
||||
queue.Dequeue();
|
||||
queue.Dequeue();
|
||||
|
||||
Assert.AreEqual(1, queue.Count);
|
||||
}
|
||||
}
|
||||
}
|
162
Nitrox.Test/Model/DataStructures/ThreadSafeListTest.cs
Normal file
162
Nitrox.Test/Model/DataStructures/ThreadSafeListTest.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures
|
||||
{
|
||||
[TestClass]
|
||||
public class ThreadSafeListTest
|
||||
{
|
||||
private ThreadSafeList<string> list;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
list = new ThreadSafeList<string>();
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
list.Add($"test {i}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Insert()
|
||||
{
|
||||
list.Insert(5, "derp");
|
||||
list[5].Should().Be("derp");
|
||||
list[0] = "Hello world!";
|
||||
list[0].Should().Be("Hello world!");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveAt()
|
||||
{
|
||||
list.RemoveAt(5);
|
||||
foreach (string item in list)
|
||||
{
|
||||
item.Should().NotBe("test 5");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Remove()
|
||||
{
|
||||
list.Remove("test 0");
|
||||
list[0].Should().Be("test 1");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Find()
|
||||
{
|
||||
list.Find(s => s == "test 1").Should().Be("test 1");
|
||||
list.Find(s => s == "tesT 1").Should().BeNull();
|
||||
list.Find(s => s == "test 1361").Should().BeNull();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAndWriteSimultaneous()
|
||||
{
|
||||
int iterations = 500000;
|
||||
|
||||
ThreadSafeList<int> comeGetMe = new(iterations);
|
||||
List<long> countsRead = new();
|
||||
long addCount = 0;
|
||||
|
||||
Random r = new Random();
|
||||
DoReaderWriter(() =>
|
||||
{
|
||||
countsRead.Add(Interlocked.Read(ref addCount));
|
||||
},
|
||||
i =>
|
||||
{
|
||||
comeGetMe.Add(r.Next());
|
||||
Interlocked.Increment(ref addCount);
|
||||
},
|
||||
iterations);
|
||||
|
||||
addCount.Should().Be(iterations);
|
||||
countsRead.Count.Should().BeGreaterThan(0);
|
||||
countsRead.Last().Should().Be(iterations);
|
||||
comeGetMe.Count.Should().Be(iterations);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IterateAndAddSimultaneous()
|
||||
{
|
||||
int iterations = 500000;
|
||||
|
||||
ThreadSafeList<int> comeGetMe = new(iterations);
|
||||
long addCount = 0;
|
||||
long iterationsReadMany = 0;
|
||||
|
||||
Random r = new Random();
|
||||
DoReaderWriter(() =>
|
||||
{
|
||||
foreach (int unused in comeGetMe)
|
||||
{
|
||||
Interlocked.Increment(ref iterationsReadMany);
|
||||
}
|
||||
},
|
||||
i =>
|
||||
{
|
||||
comeGetMe.Add(r.Next());
|
||||
Interlocked.Increment(ref addCount);
|
||||
},
|
||||
iterations);
|
||||
|
||||
addCount.Should().Be(iterations);
|
||||
iterationsReadMany.Should().BePositive();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IterateAndAdd()
|
||||
{
|
||||
ThreadSafeList<int> nums = new()
|
||||
{
|
||||
1,2,3,4,5
|
||||
};
|
||||
|
||||
foreach (int num in nums)
|
||||
{
|
||||
if (num == 3)
|
||||
{
|
||||
nums.Add(10);
|
||||
}
|
||||
}
|
||||
|
||||
nums.Count.Should().Be(6);
|
||||
nums.Last().Should().Be(10);
|
||||
}
|
||||
|
||||
private void DoReaderWriter(Action reader, Action<int> writer, int iterators)
|
||||
{
|
||||
ManualResetEventSlim barrier = new(false);
|
||||
Thread readerThread = new(() =>
|
||||
{
|
||||
while (!barrier.IsSet)
|
||||
{
|
||||
reader();
|
||||
Thread.Yield();
|
||||
}
|
||||
|
||||
// Read one last time after writer finishes
|
||||
reader();
|
||||
});
|
||||
Thread writerThread = new(() =>
|
||||
{
|
||||
for (int i = 0; i < iterators; i++)
|
||||
{
|
||||
writer(i);
|
||||
}
|
||||
barrier.Set();
|
||||
});
|
||||
|
||||
readerThread.Start();
|
||||
writerThread.Start();
|
||||
barrier.Wait();
|
||||
}
|
||||
}
|
||||
}
|
56
Nitrox.Test/Model/DataStructures/ThreadSafeQueueTest.cs
Normal file
56
Nitrox.Test/Model/DataStructures/ThreadSafeQueueTest.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures
|
||||
{
|
||||
[TestClass]
|
||||
public class ThreadSafeQueueTest
|
||||
{
|
||||
private ThreadSafeQueue<string> queue;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
queue = new ThreadSafeQueue<string>();
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
queue.Enqueue($"test {i}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Peek()
|
||||
{
|
||||
queue.Peek().Should().Be("test 0");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Enqueue()
|
||||
{
|
||||
queue.Enqueue("derp");
|
||||
queue.Count.Should().Be(11);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Dequeue()
|
||||
{
|
||||
queue.Dequeue().Should().Be("test 0");
|
||||
queue.Count.Should().Be(9);
|
||||
queue.Should().NotContain("test 0");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Clear()
|
||||
{
|
||||
queue.Clear();
|
||||
queue.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Contains()
|
||||
{
|
||||
queue.Contains("test 5").Should().BeTrue();
|
||||
queue.Contains("test 11").Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
151
Nitrox.Test/Model/DataStructures/ThreadSafeSetTest.cs
Normal file
151
Nitrox.Test/Model/DataStructures/ThreadSafeSetTest.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures
|
||||
{
|
||||
[TestClass]
|
||||
public class ThreadSafeSetTest
|
||||
{
|
||||
private ThreadSafeSet<string> set;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
set = new ThreadSafeSet<string>();
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
set.Add($"test {i}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Remove()
|
||||
{
|
||||
set.Should().Contain("test 0");
|
||||
set.Remove("test 0");
|
||||
set.Should().NotContain("test 0");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Contains()
|
||||
{
|
||||
set.Contains("test 1").Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Except()
|
||||
{
|
||||
string[] exclude = { "test 0", "test 5", "test 9" };
|
||||
set.Should().Contain(exclude);
|
||||
set.ExceptWith(exclude);
|
||||
set.Should().NotContain(exclude);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAndWriteSimultaneous()
|
||||
{
|
||||
int iterations = 500000;
|
||||
|
||||
ThreadSafeSet<string> comeGetMe = new();
|
||||
List<long> countsRead = new();
|
||||
long addCount = 0;
|
||||
|
||||
Random r = new Random();
|
||||
DoReaderWriter(() =>
|
||||
{
|
||||
countsRead.Add(Interlocked.Read(ref addCount));
|
||||
},
|
||||
i =>
|
||||
{
|
||||
comeGetMe.Add(new string(Enumerable.Repeat(' ', 10).Select(c => (char)r.Next('A', 'Z')).ToArray()));
|
||||
Interlocked.Increment(ref addCount);
|
||||
},
|
||||
iterations);
|
||||
|
||||
addCount.Should().Be(iterations);
|
||||
countsRead.Count.Should().BeGreaterThan(0);
|
||||
countsRead.Last().Should().Be(iterations);
|
||||
comeGetMe.Count.Should().Be(iterations);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IterateAndAddSimultaneous()
|
||||
{
|
||||
int iterations = 500000;
|
||||
|
||||
ThreadSafeSet<int> comeGetMe = new();
|
||||
long addCount = 0;
|
||||
long iterationsReadMany = 0;
|
||||
|
||||
Random r = new();
|
||||
DoReaderWriter(() =>
|
||||
{
|
||||
foreach (int item in comeGetMe)
|
||||
{
|
||||
Interlocked.Increment(ref iterationsReadMany);
|
||||
}
|
||||
},
|
||||
i =>
|
||||
{
|
||||
comeGetMe.Add(r.Next());
|
||||
Interlocked.Increment(ref addCount);
|
||||
},
|
||||
iterations);
|
||||
|
||||
addCount.Should().Be(iterations);
|
||||
iterationsReadMany.Should().BePositive();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IterateAndAdd()
|
||||
{
|
||||
ThreadSafeSet<int> nums = new()
|
||||
{
|
||||
1,2,3,4,5
|
||||
};
|
||||
|
||||
foreach (int num in nums)
|
||||
{
|
||||
if (num == 3)
|
||||
{
|
||||
nums.Add(10);
|
||||
}
|
||||
}
|
||||
|
||||
nums.Count.Should().Be(6);
|
||||
nums.Last().Should().Be(10);
|
||||
}
|
||||
|
||||
private void DoReaderWriter(Action reader, Action<int> writer, int iterators)
|
||||
{
|
||||
ManualResetEventSlim barrier = new(false);
|
||||
Thread readerThread = new(() =>
|
||||
{
|
||||
while (!barrier.IsSet)
|
||||
{
|
||||
reader();
|
||||
Thread.Yield();
|
||||
}
|
||||
|
||||
// Read one last time after writer finishes
|
||||
reader();
|
||||
});
|
||||
Thread writerThread = new(() =>
|
||||
{
|
||||
for (int i = 0; i < iterators; i++)
|
||||
{
|
||||
writer(i);
|
||||
}
|
||||
barrier.Set();
|
||||
});
|
||||
|
||||
readerThread.Start();
|
||||
writerThread.Start();
|
||||
barrier.Wait();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures.Unity
|
||||
{
|
||||
[TestClass]
|
||||
public class NitroxQuaternionTest
|
||||
{
|
||||
private NitroxQuaternion defaultVal;
|
||||
private const float TOLERANCE = 0.0001f;
|
||||
|
||||
[TestInitialize]
|
||||
public void Init()
|
||||
{
|
||||
defaultVal = new NitroxQuaternion(0.5682333f, -0.01828304f, -0.5182831f, 0.6388735f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEquality()
|
||||
{
|
||||
NitroxQuaternion other1 = new NitroxQuaternion(0.5682333f, -0.01828304f, -0.5182831f, 0.6388735f);
|
||||
NitroxQuaternion other2 = new NitroxQuaternion(-0.5682333f, 0.01828304f, 0.5182831f, -0.6388735f);
|
||||
NitroxQuaternion other3 = new NitroxQuaternion(0.5682343f, -0.01828314f, -0.5182841f, 0.6388745f);
|
||||
|
||||
defaultVal.Equals(other1, TOLERANCE).Should().BeTrue();
|
||||
defaultVal.Equals(other2, TOLERANCE).Should().BeTrue();
|
||||
defaultVal.Equals(other3, TOLERANCE).Should().BeTrue(); //Tolerance to low to detect the difference
|
||||
|
||||
(defaultVal == other1).Should().BeTrue();
|
||||
(defaultVal == other2).Should().BeTrue();
|
||||
(defaultVal != other3).Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestMultiplication()
|
||||
{
|
||||
NitroxQuaternion result1 = defaultVal * new NitroxQuaternion(-10f, 0.5f, 0.004f, 256.1111f);
|
||||
NitroxQuaternion result2 = new NitroxQuaternion(-10f, 0.5f, 0.004f, 256.1111f) * defaultVal;
|
||||
NitroxQuaternion expectedResult1 = new NitroxQuaternion(139.4012f, 0.8175055f, -132.6342f, 169.3162f);
|
||||
NitroxQuaternion expectedResult2 = new NitroxQuaternion(138.8831f, -9.543612f, -132.8368f, 169.3162f);
|
||||
|
||||
result1.Equals(expectedResult1, TOLERANCE).Should().BeTrue($"Expected: {expectedResult1} - Found: {result1}");
|
||||
result2.Equals(expectedResult2, TOLERANCE).Should().BeTrue($"Expected: {expectedResult2} - Found: {result2}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestToEuler()
|
||||
{
|
||||
NitroxVector3 euler1 = defaultVal.ToEuler();
|
||||
NitroxVector3 euler2 = new NitroxQuaternion(0.5f, 0.5f, -0.5f, 0.5f).ToEuler();
|
||||
NitroxVector3 euler3 = new NitroxQuaternion(-0.5f, 0.5f, -0.5f, -0.5f).ToEuler();
|
||||
NitroxVector3 expectedResult1 = new NitroxVector3(45f, 300f, 255f);
|
||||
NitroxVector3 expectedResult1Other = new NitroxVector3(104.5108f, 50.75358f, 316.9205f); //defaultVal can be interpreted as both euler :shrug:
|
||||
NitroxVector3 expectedResult2 = new NitroxVector3(90f, 90f, 0f);
|
||||
NitroxVector3 expectedResult3 = new NitroxVector3(90f, 270f, 0f);
|
||||
|
||||
(euler1.Equals(expectedResult1, TOLERANCE) || euler1.Equals(expectedResult1Other, TOLERANCE)).Should().BeTrue($"Expected: {expectedResult1} or {expectedResult1Other}- Found: {euler1}");
|
||||
euler2.Equals(expectedResult2, TOLERANCE).Should().BeTrue($"Expected: {expectedResult2} - Found: {euler2}");
|
||||
euler3.Equals(expectedResult3, TOLERANCE).Should().BeTrue($"Expected: {expectedResult3} - Found: {euler3}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFromEuler()
|
||||
{
|
||||
NitroxQuaternion result1 = NitroxQuaternion.FromEuler(new NitroxVector3(45f, 300f, 255f));
|
||||
NitroxQuaternion result2 = NitroxQuaternion.FromEuler(new NitroxVector3(45f, -60f, 615f));
|
||||
NitroxQuaternion result3 = NitroxQuaternion.FromEuler(new NitroxVector3(400f, 10f, -0.07f));
|
||||
NitroxQuaternion result4 = NitroxQuaternion.FromEuler(new NitroxVector3(360f, 0f, -720));
|
||||
NitroxQuaternion expectedResult3 = new NitroxQuaternion(-0.3406684f, -0.08210776f, 0.03038081f, -0.9360985f);
|
||||
|
||||
result1.Equals(defaultVal, TOLERANCE).Should().BeTrue($"Expected: {defaultVal} - Found: {result1}");
|
||||
result2.Equals(defaultVal, TOLERANCE).Should().BeTrue($"Expected: {defaultVal} - Found: {result2}");
|
||||
result3.Equals(expectedResult3, TOLERANCE).Should().BeTrue($"Expected: {expectedResult3} - Found: {result3}");
|
||||
result4.Equals(NitroxQuaternion.Identity, TOLERANCE).Should().BeTrue($"Expected: {NitroxQuaternion.Identity} - Found: {result4}");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.DataStructures.Unity
|
||||
{
|
||||
[TestClass]
|
||||
public class NitroxTransformTest
|
||||
{
|
||||
private const float TOLERANCE = 0.005f;
|
||||
|
||||
private static readonly NitroxTransform root = new NitroxTransform(new NitroxVector3(1, 1, 1), NitroxQuaternion.FromEuler(0, 0, 0), new NitroxVector3(1, 1, 1));
|
||||
private static readonly NitroxTransform child1 = new NitroxTransform(new NitroxVector3(5, 3, -6), NitroxQuaternion.FromEuler(30, 0, 10), new NitroxVector3(2, 2, 2));
|
||||
private static readonly NitroxTransform child2 = new NitroxTransform(new NitroxVector3(11, 0, -0.5f), NitroxQuaternion.FromEuler(180, 30, 80), new NitroxVector3(0.5f, 0.5f, 0.5f));
|
||||
private static readonly NitroxTransform grandchild1 = new NitroxTransform(new NitroxVector3(13, 8, 15), NitroxQuaternion.FromEuler(-50, 5, 10), new NitroxVector3(1, 1, 1));
|
||||
private static readonly NitroxTransform grandchild2 = new NitroxTransform(new NitroxVector3(3, 18, -5), NitroxQuaternion.FromEuler(-5, 15, 1), new NitroxVector3(10, 10, 10));
|
||||
|
||||
[ClassInitialize]
|
||||
public static void Setup(TestContext testContext)
|
||||
{
|
||||
child1.SetParent(root, false);
|
||||
child2.SetParent(root, false);
|
||||
grandchild1.SetParent(child1, false);
|
||||
grandchild2.SetParent(child2, false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PositionTest()
|
||||
{
|
||||
NitroxVector3 rootResult = new NitroxVector3(1, 1, 1);
|
||||
NitroxVector3 child1Result = new NitroxVector3(6, 4, -5);
|
||||
NitroxVector3 child2Result = new NitroxVector3(12, 1, 0.5f);
|
||||
NitroxVector3 grandchild1Result = new NitroxVector3(28.82663f, 6.55587f, 31.11665f);
|
||||
NitroxVector3 grandchild2Result = new NitroxVector3(5.79976f, -2.040047f, 6.966463f);
|
||||
|
||||
root.Position.Equals(rootResult, TOLERANCE).Should().BeTrue($"Expected: {rootResult} Found: {root.Position}");
|
||||
child1.Position.Equals(child1Result, TOLERANCE).Should().BeTrue($"Expected: {child1Result} Found: {child1.Position}");
|
||||
child2.Position.Equals(child2Result, TOLERANCE).Should().BeTrue($"Expected: {child2Result} Found: {child2.Position}");
|
||||
grandchild1.Position.Equals(grandchild1Result, TOLERANCE).Should().BeTrue($"Expected: {grandchild1Result} Found: {grandchild1.Position}");
|
||||
grandchild2.Position.Equals(grandchild2Result, TOLERANCE).Should().BeTrue($"Expected: {grandchild2Result} Found: {grandchild2.Position}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RotationTest()
|
||||
{
|
||||
NitroxQuaternion rootResult = NitroxQuaternion.FromEuler(0, 0, 0);
|
||||
NitroxQuaternion child1Result = NitroxQuaternion.FromEuler(30, 0, 10);
|
||||
NitroxQuaternion child2Result = NitroxQuaternion.FromEuler(8.537737e-07f, 210, 260);
|
||||
NitroxQuaternion grandchild1Result = NitroxQuaternion.FromEuler(340.0263f, 355.2486f, 21.87437f);
|
||||
NitroxQuaternion grandchild2Result = NitroxQuaternion.FromEuler(15.60783f, 212.4433f, 261.9936f);
|
||||
|
||||
root.Rotation.Equals(rootResult, TOLERANCE).Should().BeTrue($"Expected: {rootResult} Found: {root.Rotation}");
|
||||
child1.Rotation.Equals(child1Result, TOLERANCE).Should().BeTrue($"Expected: {child1Result} Found: {child1.Rotation}");
|
||||
child2.Rotation.Equals(child2Result, TOLERANCE).Should().BeTrue($"Expected: {child2Result} Found: {child2.Rotation}");
|
||||
grandchild1.Rotation.Equals(grandchild1Result, TOLERANCE).Should().BeTrue($"Expected: {grandchild1Result} Found: {grandchild1.Rotation}");
|
||||
grandchild2.Rotation.Equals(grandchild2Result, TOLERANCE).Should().BeTrue($"Expected: {grandchild2Result} Found: {grandchild2.Rotation}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ChangingTransformTest()
|
||||
{
|
||||
NitroxVector3 beforeLocalPosition = new NitroxVector3(15, 2, -7);
|
||||
NitroxQuaternion beforeRotation = NitroxQuaternion.FromEuler(45, 15, 1);
|
||||
|
||||
NitroxVector3 beforeChildLocalPosition = new NitroxVector3(75, -10, 13.333f);
|
||||
NitroxQuaternion beforeChildRotation = NitroxQuaternion.FromEuler(75, 11, 5);
|
||||
|
||||
NitroxVector3 setGlobalPosition = new NitroxVector3(34.62131f, 45.99337f, -10.77733f);
|
||||
NitroxQuaternion setGlobalRotation = new NitroxQuaternion(0.6743798f, 0.2302919f, 0.02638498f, 0.7010573f);
|
||||
|
||||
NitroxVector3 afterLocalPosition = new NitroxVector3(17, 14, -13);
|
||||
NitroxQuaternion afterLocalRotation = NitroxQuaternion.FromEuler(60, 25, 0);
|
||||
|
||||
NitroxVector3 afterChildGlobalPosition = new NitroxVector3(379.541f, 109.6675f, -167.4466f);
|
||||
NitroxQuaternion afterChildGlobalRotation = NitroxQuaternion.FromEuler(17.81689f, 187.7957f, 151.9425f);
|
||||
|
||||
NitroxTransform grandchild3 = new NitroxTransform(beforeLocalPosition, beforeRotation, new NitroxVector3(2.5f, 2.5f, 2.5f));
|
||||
NitroxTransform grandgrandchild1 = new NitroxTransform(beforeChildLocalPosition, beforeChildRotation, new NitroxVector3(1, 1, 1));
|
||||
|
||||
grandgrandchild1.SetParent(grandchild3, false);
|
||||
grandchild3.SetParent(child1, true);
|
||||
|
||||
grandchild3.Position.Equals(beforeLocalPosition, TOLERANCE).Should().BeTrue($"Expected: {beforeLocalPosition} Found: {grandchild3.Position}");
|
||||
grandchild3.Rotation.Equals(beforeRotation, TOLERANCE).Should().BeTrue($"Expected: {beforeRotation} Found: {grandchild3.Rotation}");
|
||||
|
||||
grandchild3.Position = setGlobalPosition;
|
||||
grandchild3.Rotation = setGlobalRotation;
|
||||
|
||||
grandchild3.LocalPosition.Equals(afterLocalPosition, TOLERANCE).Should().BeTrue($"Expected: {afterLocalPosition} Found: {grandchild3.LocalPosition}");
|
||||
grandchild3.LocalRotation.Equals(afterLocalRotation, TOLERANCE).Should().BeTrue($"Expected: {afterLocalRotation} Found: {grandchild3.LocalRotation}");
|
||||
|
||||
grandgrandchild1.Position.Equals(afterChildGlobalPosition, TOLERANCE).Should().BeTrue($"Expected: {afterChildGlobalPosition} Found: {grandgrandchild1.Position}");
|
||||
grandgrandchild1.Rotation.Equals(afterChildGlobalRotation, TOLERANCE).Should().BeTrue($"Expected: {afterChildGlobalRotation} Found: {grandgrandchild1.Rotation}");
|
||||
}
|
||||
}
|
||||
}
|
140
Nitrox.Test/Model/DataStructures/Util/OptionalTest.cs
Normal file
140
Nitrox.Test/Model/DataStructures/Util/OptionalTest.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
namespace NitroxModel.DataStructures.Util;
|
||||
|
||||
[TestClass]
|
||||
public class OptionalTest
|
||||
{
|
||||
/// <summary>
|
||||
/// These optional additions should be in <see cref="OptionalHasValueDynamicChecks"/> test method but MSTest
|
||||
/// reuses instances which causes <see cref="Optional{T}.HasValue">Optional{T}.HasValue</see> to be called before the new conditions are added.
|
||||
/// </summary>
|
||||
[ClassInitialize]
|
||||
public static void Init(TestContext _)
|
||||
{
|
||||
Optional.ApplyHasValueCondition<Base>(v => v.GetType() == typeof(A) || v.Threshold > 200); // Cheat: allow check if type A to do more complex tests on Optional<T>.HasValue
|
||||
Optional.ApplyHasValueCondition<A>(v => v.Threshold <= 200);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OptionalGet()
|
||||
{
|
||||
Optional<string> op = Optional.Of("test");
|
||||
op.Value.Should().Be("test");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OptionalIsPresent()
|
||||
{
|
||||
Optional<string> op = Optional.Of("test");
|
||||
op.HasValue.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OptionalIsNotPresent()
|
||||
{
|
||||
Optional<string> op = Optional.Empty;
|
||||
op.HasValue.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OptionalOrElseValidValue()
|
||||
{
|
||||
Optional<string> op = Optional.Of("test");
|
||||
op.OrElse("test2").Should().Be("test");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OptionalOrElseNoValue()
|
||||
{
|
||||
Optional<string> op = Optional.Empty;
|
||||
op.OrElse("test").Should().Be("test");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OptionalEmpty()
|
||||
{
|
||||
Optional<string> op = Optional.Empty;
|
||||
op.HasValue.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OptionalSetValueNull()
|
||||
{
|
||||
Optional<Random> op = Optional.Of(new Random());
|
||||
Assert.IsTrue(op.HasValue);
|
||||
Assert.ThrowsException<ArgumentNullException>(() => { op = null; }, "Setting optional to null should not be allowed.");
|
||||
op = Optional.Empty;
|
||||
Assert.IsFalse(op.HasValue);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OptionalHasValueDynamicChecks()
|
||||
{
|
||||
Optional<Base> opBase = Optional.Of(new Base());
|
||||
opBase.HasValue.Should().BeTrue();
|
||||
opBase.Value.Threshold.Should().Be(202);
|
||||
|
||||
Optional<A> a = Optional.Of(new A());
|
||||
a.HasValue.Should().BeTrue();
|
||||
|
||||
Optional<A> actuallyB = Optional.Of<A>(new B());
|
||||
actuallyB.HasValue.Should().BeFalse();
|
||||
|
||||
Optional<B> b = Optional.Of(new B());
|
||||
b.HasValue.Should().BeFalse();
|
||||
|
||||
// A check should still happen on Base because Optional<Base> includes more-specific-than-itself checks.
|
||||
Optional<Base> aAsBase = Optional<Base>.Of((Base)a);
|
||||
aAsBase.HasValue.Should().BeTrue();
|
||||
aAsBase.Value.Threshold.Should().Be(200);
|
||||
|
||||
// Optional<object> should always do all checks because anything can be in it.
|
||||
// Note: This test can fail if Optional.ApplyHasValueCondition isn't called early enough. Run this test method directly and it should work.
|
||||
Optional<object> bAsObj = Optional<object>.Of(new B());
|
||||
bAsObj.HasValue.Should().BeFalse();
|
||||
|
||||
// Type C inheritance doesn't allow for type A. But Optional<object> has the check on A. It should skip the A check on C because inheritance doesn't match up.
|
||||
Optional<object> cAsObj = Optional<object>.Of(new C());
|
||||
cAsObj.HasValue.Should().BeTrue();
|
||||
((C)cAsObj.Value).Threshold.Should().Be(203);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OptionalEqualsCheck()
|
||||
{
|
||||
Optional<string> op = Optional.OfNullable<string>(null);
|
||||
Optional<string> op1 = Optional.OfNullable<string>(null);
|
||||
Optional<string> op2 = Optional.Of("Test");
|
||||
Optional<string> op3 = Optional.Of("Test2");
|
||||
Optional<string> op4 = Optional.Of("Test");
|
||||
|
||||
Assert.IsFalse(op.Equals(op2));
|
||||
Assert.IsFalse(op.Equals(op3));
|
||||
Assert.IsFalse(op2.Equals(op3));
|
||||
|
||||
Assert.IsTrue(op.Equals(op1));
|
||||
Assert.IsTrue(op2.Equals(op4));
|
||||
|
||||
Assert.IsTrue(op != op2);
|
||||
Assert.IsTrue(op2 == op4);
|
||||
}
|
||||
|
||||
private class Base
|
||||
{
|
||||
public virtual int Threshold => 202;
|
||||
}
|
||||
|
||||
private class A : Base
|
||||
{
|
||||
public override int Threshold => 200;
|
||||
}
|
||||
|
||||
private class B : A
|
||||
{
|
||||
public override int Threshold => 201;
|
||||
}
|
||||
|
||||
private class C : Base
|
||||
{
|
||||
public override int Threshold => 203;
|
||||
}
|
||||
}
|
113
Nitrox.Test/Model/ExtensionsTests.cs
Normal file
113
Nitrox.Test/Model/ExtensionsTests.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
namespace NitroxModel;
|
||||
|
||||
[TestClass]
|
||||
public class ExtensionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void RemoveAllFast_ShouldDoNothingWhenEmptyList()
|
||||
{
|
||||
object[] list = [];
|
||||
list.Should().BeEmpty();
|
||||
list.RemoveAllFast((object)null, static (_, _) => true);
|
||||
list.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveAllFast_ShouldDoNothingWhenAlwaysFalsePredicate()
|
||||
{
|
||||
List<string> list = ["one", "two", "three", "four"];
|
||||
list.RemoveAllFast((object)null, static (_, _) => false);
|
||||
list.Should().BeEquivalentTo("one", "two", "three", "four");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveAllFast_ThrowsErrorIfFixedSizeList()
|
||||
{
|
||||
string[] list = ["one", "two", "three"];
|
||||
Assert.ThrowsException<NotSupportedException>(() => list.RemoveAllFast((object)null, static (item, _) => item == "one"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveAllFast_CanRemoveFirstItem()
|
||||
{
|
||||
List<string> list = ["one", "two", "three"];
|
||||
list.RemoveAllFast((object)null, static (item, _) => item == "one");
|
||||
list.Should().BeEquivalentTo("two", "three");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveAllFast_CanRemoveMidItems()
|
||||
{
|
||||
List<string> list = ["one", "two", "three", "four"];
|
||||
list.RemoveAllFast((object)null, static (item, _) => item == "two" || item == "three");
|
||||
list.Should().BeEquivalentTo("one", "four");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveAllFast_CanRemoveEndItem()
|
||||
{
|
||||
List<string> list = ["one", "two", "three", "four"];
|
||||
list.RemoveAllFast((object)null, static (item, _) => item == "four");
|
||||
list.Should().BeEquivalentTo("one", "two", "three");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveAllFast_CanRemoveAllItems()
|
||||
{
|
||||
List<string> list = ["one", "two", "three", "four"];
|
||||
list.RemoveAllFast((object)null, static (_, _) => true);
|
||||
list.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveAllFast_CanRemoveItemsWithExtraParameterInPredicate()
|
||||
{
|
||||
List<string> list = ["one", "two", "three", "four"];
|
||||
list.RemoveAllFast(3, static (item, length) => item.Length == length);
|
||||
list.Should().BeEquivalentTo("three", "four");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetUniqueNonCombinatoryFlags_ShouldReturnUniqueNonCombinatoryFlags()
|
||||
{
|
||||
TestEnumFlags.ALL.GetUniqueNonCombinatoryFlags().Should().BeEquivalentTo([TestEnumFlags.A, TestEnumFlags.B, TestEnumFlags.C, TestEnumFlags.D, TestEnumFlags.E, TestEnumFlags.F]);
|
||||
TestEnumFlags.CDEF.GetUniqueNonCombinatoryFlags().Should().BeEquivalentTo([TestEnumFlags.C, TestEnumFlags.D, TestEnumFlags.E, TestEnumFlags.F]);
|
||||
TestEnumFlags.E.GetUniqueNonCombinatoryFlags().Should().BeEquivalentTo([TestEnumFlags.E]);
|
||||
TestEnumFlags.NONE.GetUniqueNonCombinatoryFlags().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetUniqueNonCombinatorFlags_ShouldReturnAllUniquesWhenAllBitsSet()
|
||||
{
|
||||
((TestEnumFlags)int.MaxValue).GetUniqueNonCombinatoryFlags().Should().BeEquivalentTo([TestEnumFlags.A, TestEnumFlags.B, TestEnumFlags.C, TestEnumFlags.D, TestEnumFlags.E, TestEnumFlags.F]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetCommandArgs()
|
||||
{
|
||||
Array.Empty<string>().GetCommandArgs("").Should().BeEmpty();
|
||||
Array.Empty<string>().GetCommandArgs("--something").Should().BeEmpty();
|
||||
new[] { "/bin/nitrox.dll", "--save", "My World" }.GetCommandArgs("--save").Should().BeEquivalentTo("My World");
|
||||
new[] { "--nitrox", @"C:\a\path" }.GetCommandArgs("--nitrox").Should().BeEquivalentTo(@"C:\a\path");
|
||||
new[] { "blabla", "--other=test", "--nitrox", @"C:\a\path" }.GetCommandArgs("--nitrox").Should().BeEquivalentTo(@"C:\a\path");
|
||||
new[] { "blabla", "--other=test", "--nitrox", @"C:\a\path" }.GetCommandArgs("--other").Should().BeEquivalentTo("test");
|
||||
new[] { "blabla", "--other=test", "other2", "--nitrox", @"C:\a\path" }.GetCommandArgs("--other").Should().BeEquivalentTo("test");
|
||||
new[] { "blabla", "--other", "test", "other2", "--nitrox", @"C:\a\path" }.GetCommandArgs("--other").Should().BeEquivalentTo("test", "other2");
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum TestEnumFlags
|
||||
{
|
||||
NONE = 0,
|
||||
F = 1 << 5,
|
||||
A = 1 << 0,
|
||||
B = 1 << 1,
|
||||
C = 1 << 2,
|
||||
D = 1 << 3,
|
||||
E = 1 << 4,
|
||||
AB = A | B,
|
||||
CD = C | D,
|
||||
CDEF = CD | E | F,
|
||||
ALL = AB | CDEF
|
||||
}
|
||||
}
|
24
Nitrox.Test/Model/Helper/KeyValueStoreTest.cs
Normal file
24
Nitrox.Test/Model/Helper/KeyValueStoreTest.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
[TestClass]
|
||||
public class KeyValueStoreTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void SetAndReadValue()
|
||||
{
|
||||
const string TEST_KEY = "test";
|
||||
|
||||
KeyValueStore.Instance.SetValue(TEST_KEY, -50);
|
||||
Assert.AreEqual(-50, KeyValueStore.Instance.GetValue<int>(TEST_KEY));
|
||||
|
||||
KeyValueStore.Instance.SetValue(TEST_KEY, 1337);
|
||||
Assert.AreEqual(1337, KeyValueStore.Instance.GetValue<int>(TEST_KEY));
|
||||
|
||||
// Cleanup
|
||||
KeyValueStore.Instance.DeleteKey(TEST_KEY);
|
||||
Assert.IsNull(KeyValueStore.Instance.GetValue<int?>(TEST_KEY));
|
||||
Assert.IsFalse(KeyValueStore.Instance.KeyExists(TEST_KEY));
|
||||
}
|
||||
}
|
68
Nitrox.Test/Model/Helper/NetHelperTest.cs
Normal file
68
Nitrox.Test/Model/Helper/NetHelperTest.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
[TestClass]
|
||||
public class NetHelperTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ShouldMatchPrivateIps()
|
||||
{
|
||||
// Tested subnet ranges that are reserved for private networks:
|
||||
// 10.0.0.0/8
|
||||
// 127.0.0.0/8
|
||||
// 172.16.0.0/12
|
||||
// 192.0.0.0/24
|
||||
// 192.168.0.0/16
|
||||
// 198.18.0.0/15
|
||||
|
||||
IPAddress.Parse("10.0.0.0").IsPrivate().Should().BeTrue();
|
||||
IPAddress.Parse("10.0.0.255").IsPrivate().Should().BeTrue();
|
||||
IPAddress.Parse("172.31.255.255").IsPrivate().Should().BeTrue();
|
||||
IPAddress.Parse("172.31.255.255").IsPrivate().Should().BeTrue();
|
||||
IPAddress.Parse("192.0.0.255").IsPrivate().Should().BeTrue();
|
||||
IPAddress.Parse("192.168.2.1").IsPrivate().Should().BeTrue();
|
||||
IPAddress.Parse("192.168.2.254").IsPrivate().Should().BeTrue();
|
||||
IPAddress.Parse("192.168.2.255").IsPrivate().Should().BeTrue();
|
||||
IPAddress.Parse("198.18.0.1").IsPrivate().Should().BeTrue();
|
||||
IPAddress.Parse("198.19.255.255").IsPrivate().Should().BeTrue();
|
||||
|
||||
IPAddress.Parse("9.255.255.255").IsPrivate().Should().BeFalse();
|
||||
IPAddress.Parse("91.63.176.12").IsPrivate().Should().BeFalse();
|
||||
IPAddress.Parse("172.32.0.1").IsPrivate().Should().BeFalse();
|
||||
IPAddress.Parse("192.0.1.0").IsPrivate().Should().BeFalse();
|
||||
IPAddress.Parse("198.17.255.255").IsPrivate().Should().BeFalse();
|
||||
IPAddress.Parse("198.20.0.0").IsPrivate().Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldMatchLocalhostIps()
|
||||
{
|
||||
IPAddress GetSlightlyDifferentIp(IPAddress address)
|
||||
{
|
||||
if (address.AddressFamily != AddressFamily.InterNetwork)
|
||||
{
|
||||
throw new Exception("Only supports IPv4");
|
||||
}
|
||||
byte[] bytes = address.GetAddressBytes();
|
||||
unchecked
|
||||
{
|
||||
while (bytes[3] is < 1 or > 253)
|
||||
{
|
||||
bytes[3]++;
|
||||
}
|
||||
bytes[3]++;
|
||||
}
|
||||
return new IPAddress(bytes);
|
||||
}
|
||||
|
||||
IPAddress.Parse("127.0.0.1").IsLocalhost().Should().BeTrue();
|
||||
IPAddress.Parse("127.0.0.2").IsLocalhost().Should().BeTrue();
|
||||
IPAddress.Parse("192.168.0.255").IsLocalhost().Should().BeFalse();
|
||||
NetHelper.GetLanIp().IsLocalhost().Should().BeTrue();
|
||||
IPAddress differentIp = GetSlightlyDifferentIp(NetHelper.GetLanIp());
|
||||
differentIp.Should().NotBeEquivalentTo(NetHelper.GetLanIp());
|
||||
differentIp.IsLocalhost().Should().BeFalse();
|
||||
}
|
||||
}
|
97
Nitrox.Test/Model/Helper/ReflectTest.cs
Normal file
97
Nitrox.Test/Model/Helper/ReflectTest.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input;
|
||||
|
||||
namespace NitroxModel.Helper
|
||||
{
|
||||
[TestClass]
|
||||
public class ReflectTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void Method()
|
||||
{
|
||||
// Get static method.
|
||||
MethodInfo staticMethod = Reflect.Method(() => AbusedClass.StaticMethodReturnsInt());
|
||||
staticMethod.Should().NotBeNull();
|
||||
staticMethod.ReturnType.Should().Be<int>();
|
||||
staticMethod.Name.Should().BeEquivalentTo(nameof(AbusedClass.StaticMethodReturnsInt));
|
||||
staticMethod.Invoke(null, Array.Empty<object>());
|
||||
// Extra check for method with parameters, just to be safe.
|
||||
staticMethod = Reflect.Method(() => AbusedClass.StaticMethodHasParams("", null));
|
||||
staticMethod.Should().NotBeNull();
|
||||
staticMethod.ReturnType.Should().Be<string>();
|
||||
staticMethod.Name.Should().BeEquivalentTo(nameof(AbusedClass.StaticMethodHasParams));
|
||||
staticMethod.GetParameters().Should().OnlyHaveUniqueItems();
|
||||
staticMethod.GetParameters()[0].Name.Should().BeEquivalentTo("myValue");
|
||||
staticMethod.GetParameters()[0].ParameterType.Should().Be<string>();
|
||||
staticMethod.GetParameters()[1].ParameterType.Should().Be<Process>();
|
||||
staticMethod.Invoke(null, new[] { "hello, reflection", (object)null }).Should().BeEquivalentTo("hello, reflection");
|
||||
|
||||
// Get instance method.
|
||||
MethodInfo instanceMethod = Reflect.Method((AbusedClass t) => t.Method());
|
||||
instanceMethod.Should().NotBeNull();
|
||||
instanceMethod.ReturnType.Should().Be<int>();
|
||||
instanceMethod.Name.Should().BeEquivalentTo(nameof(AbusedClass.Method));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Field()
|
||||
{
|
||||
// Get static field.
|
||||
FieldInfo staticField = Reflect.Field(() => AbusedClass.StaticField);
|
||||
staticField.Name.Should().BeEquivalentTo(nameof(AbusedClass.StaticField));
|
||||
staticField.FieldType.Should().Be<int>();
|
||||
// Get instance field.
|
||||
FieldInfo instanceField = Reflect.Field((AbusedClass t) => t.InstanceField);
|
||||
instanceField.Name.Should().BeEquivalentTo(nameof(AbusedClass.InstanceField));
|
||||
instanceField.FieldType.Should().Be<int>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Property()
|
||||
{
|
||||
// Get static property.
|
||||
PropertyInfo staticProperty = Reflect.Property(() => AbusedClass.StaticProperty);
|
||||
staticProperty.Name.Should().BeEquivalentTo(nameof(AbusedClass.StaticProperty));
|
||||
staticProperty.PropertyType.Should().Be<int>();
|
||||
// Get instance property.
|
||||
PropertyInfo instanceProperty = Reflect.Property((AbusedClass t) => t.InstanceProperty);
|
||||
instanceProperty.Name.Should().BeEquivalentTo(nameof(AbusedClass.InstanceProperty));
|
||||
instanceProperty.PropertyType.Should().Be<int>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Constructor()
|
||||
{
|
||||
ConstructorInfo method = Reflect.Constructor(() => new KeyBindingManager());
|
||||
method.DeclaringType.Should().Be<KeyBindingManager>();
|
||||
}
|
||||
|
||||
private class AbusedClass
|
||||
{
|
||||
public static readonly int StaticReadOnlyField = 1;
|
||||
public static int StaticField = 2;
|
||||
public int InstanceField = 3;
|
||||
public static int StaticProperty { get; set; } = 4;
|
||||
public int InstanceProperty { get; set; } = 5;
|
||||
|
||||
public static int StaticMethodReturnsInt()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static string StaticMethodHasParams(string myValue, Process process)
|
||||
{
|
||||
return myValue;
|
||||
}
|
||||
|
||||
public int Method()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
79
Nitrox.Test/Model/Packets/PacketsSerializableTest.cs
Normal file
79
Nitrox.Test/Model/Packets/PacketsSerializableTest.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BinaryPack.Attributes;
|
||||
using KellermanSoftware.CompareNetObjects;
|
||||
using KellermanSoftware.CompareNetObjects.TypeComparers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Nitrox.Test.Helper.Faker;
|
||||
using NitroxModel_Subnautica.Logger;
|
||||
using NitroxModel.DataStructures;
|
||||
|
||||
namespace NitroxModel.Packets;
|
||||
|
||||
[TestClass]
|
||||
public class PacketsSerializableTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void InitSerializerTest()
|
||||
{
|
||||
Packet.InitSerializer();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PacketSerializationTest()
|
||||
{
|
||||
ComparisonConfig config = new();
|
||||
config.SkipInvalidIndexers = true;
|
||||
config.AttributesToIgnore.Add(typeof(IgnoredMemberAttribute));
|
||||
config.CustomComparers.Add(new CustomComparer<NitroxId, NitroxId>((id1, id2) => id1.Equals(id2)));
|
||||
CompareLogic comparer = new(config);
|
||||
|
||||
IEnumerable<Type> types = typeof(Packet).Assembly.GetTypes().Concat(typeof(SubnauticaInGameLogger).Assembly.GetTypes());
|
||||
Type[] packetTypes = types.Where(p => typeof(Packet).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract).ToArray();
|
||||
|
||||
// We want to ignore packets with no members when using ShouldNotCompare
|
||||
Type[] emptyPackets = packetTypes.Where(t => !t.GetMembers(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Any(member => member.MemberType is MemberTypes.Field or MemberTypes.Property &&
|
||||
!member.GetCustomAttributes<IgnoredMemberAttribute>().Any()))
|
||||
.ToArray();
|
||||
|
||||
// We generate two different versions of each packet to verify comparison is actually working
|
||||
List<(Packet, Packet)> generatedPackets = new();
|
||||
|
||||
foreach (Type type in packetTypes)
|
||||
{
|
||||
dynamic faker = NitroxFaker.GetOrCreateFaker(type);
|
||||
|
||||
Packet packet = faker.Generate();
|
||||
Packet packet2 = null;
|
||||
|
||||
if (!emptyPackets.Contains(type))
|
||||
{
|
||||
ComparisonResult result;
|
||||
do
|
||||
{
|
||||
packet2 = faker.Generate();
|
||||
result = comparer.Compare(packet, packet2);
|
||||
} while (result == null || result.AreEqual);
|
||||
}
|
||||
|
||||
generatedPackets.Add(new ValueTuple<Packet, Packet>(packet, packet2));
|
||||
}
|
||||
|
||||
Packet.InitSerializer();
|
||||
|
||||
foreach (ValueTuple<Packet, Packet> packet in generatedPackets)
|
||||
{
|
||||
Packet deserialized = Packet.Deserialize(packet.Item1.Serialize());
|
||||
|
||||
packet.Item1.ShouldCompare(deserialized, $"with {packet.Item1.GetType()}", config);
|
||||
|
||||
if (!emptyPackets.Contains(packet.Item1.GetType()))
|
||||
{
|
||||
packet.Item2.ShouldNotCompare(deserialized, $"with {packet.Item1.GetType()}", config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
118
Nitrox.Test/Model/Packets/Processors/PacketProcessorTest.cs
Normal file
118
Nitrox.Test/Model/Packets/Processors/PacketProcessorTest.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Nitrox.Test;
|
||||
using NitroxClient;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.Packets.Processors.Abstract;
|
||||
using NitroxServer;
|
||||
using NitroxServer.Communication.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer_Subnautica;
|
||||
|
||||
namespace NitroxModel.Packets.Processors
|
||||
{
|
||||
[TestClass]
|
||||
public class PacketProcessorTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ClientPacketProcessorSanity()
|
||||
{
|
||||
typeof(ClientPacketProcessor<>).Assembly.GetTypes()
|
||||
.Where(p => typeof(PacketProcessor).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract)
|
||||
.ToList()
|
||||
.ForEach(processor =>
|
||||
{
|
||||
// Make sure that each packet-processor is derived from the ClientPacketProcessor class,
|
||||
// so that it's packet-type can be determined.
|
||||
Assert.IsNotNull(processor.BaseType, $"{processor} does not derive from any type!");
|
||||
Assert.IsTrue(processor.BaseType.IsGenericType, $"{processor} does not derive from a generic type!");
|
||||
Assert.IsTrue(processor.BaseType.IsAssignableToGenericType(typeof(ClientPacketProcessor<>)), $"{processor} does not derive from ClientPacketProcessor!");
|
||||
|
||||
// Check constructor availability:
|
||||
int numCtors = processor.GetConstructors().Length;
|
||||
Assert.IsTrue(numCtors == 1, $"{processor} should have exactly 1 constructor! (has {numCtors})");
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ServerPacketProcessorSanity()
|
||||
{
|
||||
typeof(PacketHandler).Assembly.GetTypes()
|
||||
.Where(p => typeof(PacketProcessor).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract)
|
||||
.ToList()
|
||||
.ForEach(processor =>
|
||||
{
|
||||
// Make sure that each packet-processor is derived from the ClientPacketProcessor class,
|
||||
// so that it's packet-type can be determined.
|
||||
Assert.IsNotNull(processor.BaseType, $"{processor} does not derive from any type!");
|
||||
Assert.IsTrue(processor.BaseType.IsGenericType, $"{processor} does not derive from a generic type!");
|
||||
Assert.IsTrue(processor.BaseType.IsAssignableToGenericType(typeof(AuthenticatedPacketProcessor<>)) ||
|
||||
processor.BaseType.IsAssignableToGenericType(typeof(UnauthenticatedPacketProcessor<>)), $"{processor} does not derive from (Un)AuthenticatedPacketProcessor!");
|
||||
|
||||
// Check constructor availability:
|
||||
int numCtors = processor.GetConstructors().Length;
|
||||
Assert.IsTrue(numCtors == 1, $"{processor} should have exactly 1 constructor! (has {numCtors})");
|
||||
|
||||
// Unable to check parameters, these are defined in PacketHandler.ctor
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SameAmountOfServerPacketProcessors()
|
||||
{
|
||||
IEnumerable<Type> processors = typeof(PacketHandler).Assembly.GetTypes()
|
||||
.Where(p => typeof(PacketProcessor).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract);
|
||||
ServerAutoFacRegistrar serverDependencyRegistrar = new ServerAutoFacRegistrar();
|
||||
NitroxServiceLocator.InitializeDependencyContainer(serverDependencyRegistrar);
|
||||
NitroxServiceLocator.BeginNewLifetimeScope();
|
||||
|
||||
List<Type> packetTypes = typeof(DefaultServerPacketProcessor).Assembly.GetTypes()
|
||||
.Where(p => typeof(PacketProcessor).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract)
|
||||
.ToList();
|
||||
|
||||
int both = packetTypes.Count;
|
||||
Assert.AreEqual(processors.Count(), both,
|
||||
$"Not all(Un) AuthenticatedPacketProcessors have been discovered by the runtime code (auth + unauth: {both} out of {processors.Count()}). Perhaps the runtime matching code is too strict, or a processor does not derive from ClientPacketProcessor (and will hence not be detected).");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AllPacketsAreHandled()
|
||||
{
|
||||
List<Type> packetTypes = typeof(DefaultServerPacketProcessor).Assembly.GetTypes()
|
||||
.Where(p => typeof(PacketProcessor).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract)
|
||||
.ToList();
|
||||
|
||||
List<Type> abstractProcessorTypes = new();
|
||||
|
||||
abstractProcessorTypes.AddRange(typeof(ClientPacketProcessor<>)
|
||||
.Assembly.GetTypes()
|
||||
.Where(p => p.IsClass && p.IsAbstract && p.IsAssignableToGenericType(typeof(ClientPacketProcessor<>))));
|
||||
|
||||
abstractProcessorTypes.AddRange(typeof(AuthenticatedPacketProcessor<>)
|
||||
.Assembly.GetTypes()
|
||||
.Where(p => p.IsClass && p.IsAbstract && (p.IsAssignableToGenericType(typeof(AuthenticatedPacketProcessor<>)) || p.IsAssignableToGenericType(typeof(UnauthenticatedPacketProcessor<>)))));
|
||||
|
||||
NitroxServiceLocator.InitializeDependencyContainer(new ClientAutoFacRegistrar(), new SubnauticaServerAutoFacRegistrar(), new TestAutoFacRegistrar());
|
||||
NitroxServiceLocator.BeginNewLifetimeScope();
|
||||
|
||||
foreach (Type packet in typeof(Packet).Assembly.GetTypes().Where(p => typeof(Packet).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract).ToList())
|
||||
{
|
||||
Assert.IsTrue(packetTypes.Contains(packet) || abstractProcessorTypes.Any(genericProcessor =>
|
||||
{
|
||||
Type processorType = genericProcessor.MakeGenericType(packet);
|
||||
return NitroxServiceLocator.LocateOptionalService(processorType).HasValue;
|
||||
}), $"Packet of type '{packet}' should have at least one processor.");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
NitroxServiceLocator.EndCurrentLifetimeScope();
|
||||
}
|
||||
}
|
||||
}
|
39
Nitrox.Test/Model/Platforms/OS/Windows/RegistryTest.cs
Normal file
39
Nitrox.Test/Model/Platforms/OS/Windows/RegistryTest.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using Nitrox.Test.Model.Platforms;
|
||||
|
||||
namespace NitroxModel.Platforms.OS.Windows;
|
||||
|
||||
[TestClass]
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class RegistryTest
|
||||
{
|
||||
[OSTestMethod("windows")]
|
||||
public async Task WaitsForRegistryKeyToExist()
|
||||
{
|
||||
const string PATH_TO_KEY = @"SOFTWARE\Nitrox\test";
|
||||
|
||||
RegistryEx.Write(PATH_TO_KEY, 0);
|
||||
Task<bool> readTask = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await RegistryEx.CompareWaitAsync<int>(PATH_TO_KEY,
|
||||
v => v == 1337,
|
||||
TimeSpan.FromSeconds(5));
|
||||
return true;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
RegistryEx.Write(PATH_TO_KEY, 1337);
|
||||
Assert.IsTrue(await readTask);
|
||||
|
||||
// Cleanup (we can keep "Nitrox" key intact).
|
||||
RegistryEx.Delete(PATH_TO_KEY);
|
||||
Assert.IsNull(RegistryEx.Read<string>(PATH_TO_KEY));
|
||||
}
|
||||
}
|
32
Nitrox.Test/Model/Platforms/OSTestMethodAttribute.cs
Normal file
32
Nitrox.Test/Model/Platforms/OSTestMethodAttribute.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Nitrox.Test.Model.Platforms;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class OSTestMethodAttribute : TestMethodAttribute
|
||||
{
|
||||
public string Platform { get;}
|
||||
|
||||
/// <summary>
|
||||
/// Test method attribute, that will only run the test on the specified platform.
|
||||
/// </summary>
|
||||
/// <param name="platform">case insensitive platform, i.e: linux, windows, osx</param>
|
||||
public OSTestMethodAttribute(string platform)
|
||||
{
|
||||
Platform = platform;
|
||||
}
|
||||
|
||||
public override TestResult[] Execute(ITestMethod testMethod)
|
||||
{
|
||||
if (!OperatingSystem.IsOSPlatform(Platform))
|
||||
{
|
||||
return [
|
||||
new TestResult()
|
||||
{
|
||||
Outcome = UnitTestOutcome.Inconclusive,
|
||||
TestContextMessages = $"This test can only be run on {Platform}"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return base.Execute(testMethod);
|
||||
}
|
||||
}
|
48
Nitrox.Test/Nitrox.Test.csproj
Normal file
48
Nitrox.Test/Nitrox.Test.csproj
Normal file
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<IsPublishable>false</IsPublishable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<!-- TODO: Do not use DI in tests, create objects in-line or use mocks for externalities -->
|
||||
<NoWarn>$(NoWarn);DIMA001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bogus" Version="35.6.3" />
|
||||
<PackageReference Include="CompareNETObjects" Version="4.83.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="[7.2.0]" />
|
||||
<PackageReference Include="FluentAssertions.Analyzers" Version="[0.34.1]">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MSTest" Version="3.8.3" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NitroxClient\NitroxClient.csproj" />
|
||||
<ProjectReference Include="..\NitroxModel\NitroxModel.csproj" />
|
||||
<ProjectReference Include="..\NitroxPatcher\NitroxPatcher.csproj" />
|
||||
<ProjectReference Include="..\NitroxServer-Subnautica\NitroxServer-Subnautica.csproj" />
|
||||
<ProjectReference Include="..\NitroxServer\NitroxServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="MoveNitroxAssetsToTestOutput" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<NitroxSubnauticaAssets Include="..\Nitrox.Assets.Subnautica\**\*." />
|
||||
<NitroxSubnauticaStaticDlls Include="..\Nitrox.Assets.Subnautica\**\*.dll" />
|
||||
<NitroxSubnauticaResources Include="..\Nitrox.Assets.Subnautica\Resources\*.*" />
|
||||
<NitroxSubnauticaLanguageFiles Include="..\Nitrox.Assets.Subnautica\LanguageFiles\*.json" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(NitroxSubnauticaAssets)" DestinationFolder="$(TargetDir)\%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(NitroxSubnauticaStaticDlls)" DestinationFolder="$(TargetDir)\lib\%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(NitroxSubnauticaLanguageFiles)" DestinationFolder="$(TargetDir)\LanguageFiles\%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(NitroxSubnauticaResources)" DestinationFolder="$(TargetDir)\Resources\%(RecursiveDir)" />
|
||||
</Target>
|
||||
</Project>
|
87
Nitrox.Test/Patcher/PatchTestHelper.cs
Normal file
87
Nitrox.Test/Patcher/PatchTestHelper.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using FluentAssertions;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
|
||||
namespace NitroxTest.Patcher
|
||||
{
|
||||
public static class PatchTestHelper
|
||||
{
|
||||
public static List<CodeInstruction> GenerateDummyInstructions(int count)
|
||||
{
|
||||
List<CodeInstruction> instructions = new List<CodeInstruction>();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
instructions.Add(new CodeInstruction(OpCodes.Nop));
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
public static ReadOnlyCollection<CodeInstruction> GetInstructionsFromMethod(DynamicMethod targetMethod)
|
||||
{
|
||||
Validate.NotNull(targetMethod);
|
||||
return GetInstructionsFromIL(GetILInstructions(targetMethod));
|
||||
}
|
||||
|
||||
public static ReadOnlyCollection<CodeInstruction> GetInstructionsFromMethod(MethodInfo targetMethod)
|
||||
{
|
||||
Validate.NotNull(targetMethod);
|
||||
return GetInstructionsFromIL(GetILInstructions(targetMethod));
|
||||
}
|
||||
|
||||
public static IEnumerable<KeyValuePair<OpCode, object>> GetILInstructions(MethodInfo method)
|
||||
{
|
||||
return PatchProcessor.ReadMethodBody(method, method.GetILGenerator());
|
||||
}
|
||||
|
||||
public static IEnumerable<KeyValuePair<OpCode, object>> GetILInstructions(DynamicMethod method)
|
||||
{
|
||||
return PatchProcessor.ReadMethodBody(method, method.GetILGenerator());
|
||||
}
|
||||
|
||||
public static ILGenerator GetILGenerator(this MethodInfo method)
|
||||
{
|
||||
return new DynamicMethod(method.Name, method.ReturnType, method.GetParameters().Types()).GetILGenerator();
|
||||
}
|
||||
|
||||
public static void TestPattern(MethodInfo targetMethod, InstructionsPattern pattern, out IEnumerable<CodeInstruction> originalIl, out IEnumerable<CodeInstruction> transformedIl)
|
||||
{
|
||||
bool shouldHappen = false;
|
||||
originalIl = PatchProcessor.GetCurrentInstructions(targetMethod);
|
||||
transformedIl = originalIl
|
||||
.Transform(pattern, (_, _) =>
|
||||
{
|
||||
shouldHappen = true;
|
||||
})
|
||||
.ToArray(); // Required, otherwise nothing happens.
|
||||
|
||||
shouldHappen.Should().BeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones the instructions so that the returned instructions are not the same reference.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Useful for testing code differences before and after a Harmony transpiler.
|
||||
/// </remarks>
|
||||
public static List<CodeInstruction> Clone(this IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new List<CodeInstruction>(instructions.Select(il => new CodeInstruction(il)));
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<CodeInstruction> GetInstructionsFromIL(IEnumerable<KeyValuePair<OpCode, object>> il)
|
||||
{
|
||||
List<CodeInstruction> result = new List<CodeInstruction>();
|
||||
foreach (KeyValuePair<OpCode, object> instruction in il)
|
||||
{
|
||||
result.Add(new CodeInstruction(instruction.Key, instruction.Value));
|
||||
}
|
||||
return result.AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
218
Nitrox.Test/Patcher/Patches/PatchesTranspilerTest.cs
Normal file
218
Nitrox.Test/Patcher/Patches/PatchesTranspilerTest.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxPatcher.Patches;
|
||||
using NitroxPatcher.Patches.Dynamic;
|
||||
using NitroxPatcher.Patches.Persistent;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
using NitroxTest.Patcher;
|
||||
|
||||
namespace Nitrox.Test.Patcher.Patches;
|
||||
|
||||
[TestClass]
|
||||
public class PatchesTranspilerTest
|
||||
{
|
||||
// Add "true" to any of those elements to have its transformed IL printed.
|
||||
public static IEnumerable<object[]> TranspilerPatchClasses =>
|
||||
[
|
||||
[typeof(AggressiveWhenSeeTarget_ScanForAggressionTarget_Patch), 3],
|
||||
[typeof(AttackCyclops_OnCollisionEnter_Patch), -17],
|
||||
[typeof(AttackCyclops_UpdateAggression_Patch), -23],
|
||||
[typeof(Bullet_Update_Patch), 3],
|
||||
[typeof(BaseDeconstructable_Deconstruct_Patch), BaseDeconstructable_Deconstruct_Patch.InstructionsToAdd(true).Count() * 2],
|
||||
[typeof(BaseHullStrength_CrushDamageUpdate_Patch), 3],
|
||||
[typeof(BreakableResource_SpawnResourceFromPrefab_Patch), 2],
|
||||
[typeof(Builder_TryPlace_Patch), Builder_TryPlace_Patch.InstructionsToAdd1.Count + Builder_TryPlace_Patch.InstructionsToAdd2.Count],
|
||||
[typeof(CellManager_TryLoadCacheBatchCells_Patch), 4],
|
||||
[typeof(Constructable_Construct_Patch), Constructable_Construct_Patch.InstructionsToAdd.Count],
|
||||
[typeof(Constructable_DeconstructAsync_Patch), Constructable_DeconstructAsync_Patch.InstructionsToAdd.Count],
|
||||
[typeof(ConstructableBase_SetState_Patch), ConstructableBase_SetState_Patch.InstructionsToAdd.Count],
|
||||
[typeof(ConstructorInput_OnCraftingBegin_Patch), 7],
|
||||
[typeof(CrafterLogic_TryPickupSingleAsync_Patch), 4],
|
||||
[typeof(CrashHome_Spawn_Patch), 2],
|
||||
[typeof(CrashHome_Update_Patch), -5],
|
||||
[typeof(CreatureDeath_OnKillAsync_Patch), 9],
|
||||
[typeof(CreatureDeath_SpawnRespawner_Patch), 2],
|
||||
[typeof(CyclopsDestructionEvent_DestroyCyclops_Patch), 3],
|
||||
[typeof(CyclopsDestructionEvent_SpawnLootAsync_Patch), 7],
|
||||
[typeof(CyclopsShieldButton_OnClick_Patch), -6],
|
||||
[typeof(CyclopsSonarButton_Update_Patch), 3],
|
||||
[typeof(CyclopsSonarDisplay_NewEntityOnSonar_Patch), 3],
|
||||
[typeof(DevConsole_Update_Patch), 0],
|
||||
[typeof(Eatable_IterateDespawn_Patch), 2],
|
||||
[typeof(EnergyMixin_SpawnDefaultAsync_Patch), -64],
|
||||
[typeof(EntityCell_AwakeAsync_Patch), 2],
|
||||
[typeof(EntityCell_SleepAsync_Patch), 2],
|
||||
[typeof(Equipment_RemoveItem_Patch), 7],
|
||||
[typeof(EscapePod_Start_Patch), 43],
|
||||
[typeof(FireExtinguisherHolder_TakeTankAsync_Patch), 2],
|
||||
[typeof(FireExtinguisherHolder_TryStoreTank_Patch), 3],
|
||||
[typeof(Flare_Update_Patch), 0],
|
||||
[typeof(FootstepSounds_OnStep_Patch), 6],
|
||||
[typeof(GameInput_Initialize_Patch), 5],
|
||||
[typeof(GrowingPlant_SpawnGrownModelAsync_Patch), -1],
|
||||
[typeof(Player_TriggerInfectionRevealAsync_Patch), 1],
|
||||
[typeof(IngameMenu_OnSelect_Patch), -2],
|
||||
[typeof(IngameMenu_QuitGameAsync_Patch), 2],
|
||||
[typeof(IngameMenu_QuitSubscreen_Patch), -24],
|
||||
[typeof(ItemsContainer_DestroyItem_Patch), 2],
|
||||
[typeof(Knife_OnToolUseAnim_Patch), 0],
|
||||
[typeof(LargeRoomWaterPark_OnDeconstructionStart_Patch), 3],
|
||||
[typeof(LargeWorldEntity_UpdateCell_Patch), 1],
|
||||
[typeof(LaunchRocket_OnHandClick_Patch), -9],
|
||||
[typeof(LeakingRadiation_Update_Patch), 0],
|
||||
[typeof(MainGameController_StartGame_Patch), 1],
|
||||
[typeof(MeleeAttack_CanDealDamageTo_Patch), 4],
|
||||
[typeof(PDAScanner_Scan_Patch), 3],
|
||||
[typeof(PickPrefab_AddToContainerAsync_Patch), 4],
|
||||
[typeof(Player_OnKill_Patch), 0],
|
||||
[typeof(Respawn_Start_Patch), 3],
|
||||
[typeof(RocketConstructor_StartRocketConstruction_Patch), 3],
|
||||
[typeof(SpawnConsoleCommand_SpawnAsync_Patch), 2],
|
||||
[typeof(SpawnOnKill_OnKill_Patch), 3],
|
||||
[typeof(SubConsoleCommand_OnConsoleCommand_sub_Patch), 0],
|
||||
[typeof(SubRoot_OnPlayerEntered_Patch), 5],
|
||||
[typeof(Trashcan_Update_Patch), 4],
|
||||
[typeof(uGUI_OptionsPanel_AddAccessibilityTab_Patch), -10],
|
||||
[typeof(uGUI_PDA_Initialize_Patch), 2],
|
||||
[typeof(uGUI_PDA_SetTabs_Patch), 3],
|
||||
[typeof(uGUI_Pings_IsVisibleNow_Patch), 0],
|
||||
[typeof(uGUI_SceneIntro_HandleInput_Patch), -2],
|
||||
[typeof(uGUI_SceneIntro_IntroSequence_Patch), 8],
|
||||
[typeof(uSkyManager_SetVaryingMaterialProperties_Patch), 0],
|
||||
[typeof(Welder_Weld_Patch), 1],
|
||||
[typeof(Poop_Perform_Patch), 1],
|
||||
[typeof(SeaDragonMeleeAttack_OnTouchFront_Patch), 9],
|
||||
[typeof(SeaDragonMeleeAttack_SwatAttack_Patch), 4],
|
||||
[typeof(SeaTreaderSounds_SpawnChunks_Patch), 3],
|
||||
[typeof(Vehicle_TorpedoShot_Patch), 3],
|
||||
[typeof(SeamothTorpedo_Update_Patch), 0],
|
||||
[typeof(SeaTreader_UpdatePath_Patch), 0],
|
||||
[typeof(SeaTreader_UpdateTurning_Patch), 0],
|
||||
[typeof(SeaTreader_Update_Patch), 0],
|
||||
[typeof(StasisSphere_LateUpdate_Patch), 0],
|
||||
[typeof(WaterParkCreature_BornAsync_Patch), 6],
|
||||
[typeof(WaterParkCreature_ManagedUpdate_Patch), 2],
|
||||
];
|
||||
|
||||
[TestMethod]
|
||||
public void AllTranspilerPatchesHaveSanityTest()
|
||||
{
|
||||
Type[] allPatchesWithTranspiler = typeof(NitroxPatcher.Main).Assembly.GetTypes().Where(p => typeof(NitroxPatch).IsAssignableFrom(p) && p.IsClass).Where(x => x.GetMethod("Transpiler") != null).ToArray();
|
||||
|
||||
foreach (Type patch in allPatchesWithTranspiler)
|
||||
{
|
||||
if (TranspilerPatchClasses.All(x => (Type)x[0] != patch))
|
||||
{
|
||||
Assert.Fail($"{patch.Name} has an Transpiler but is not included in the test-suit and {nameof(TranspilerPatchClasses)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DynamicData(nameof(TranspilerPatchClasses))]
|
||||
public void AllPatchesTranspilerSanity(Type patchClassType, int ilDifference, bool logInstructions = false)
|
||||
{
|
||||
FieldInfo targetMethodInfo = patchClassType.GetRuntimeFields().FirstOrDefault(x => string.Equals(x.Name.Replace("_", ""), "targetMethod", StringComparison.OrdinalIgnoreCase));
|
||||
if (targetMethodInfo == null)
|
||||
{
|
||||
Assert.Fail($"Could not find either \"TARGET_METHOD\" nor \"targetMethod\" inside {patchClassType.Name}");
|
||||
}
|
||||
|
||||
MethodInfo targetMethod = targetMethodInfo.GetValue(null) as MethodInfo;
|
||||
List<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(targetMethod).ToList();
|
||||
List<CodeInstruction> originalIlCopy = PatchTestHelper.GetInstructionsFromMethod(targetMethod).ToList(); // Our custom pattern matching replaces OpCode/Operand in place, therefor we need a copy to compare if changes are present
|
||||
|
||||
MethodInfo transpilerMethod = patchClassType.GetMethod("Transpiler");
|
||||
if (transpilerMethod == null)
|
||||
{
|
||||
Assert.Fail($"Could not find \"Transpiler\" inside {patchClassType.Name}");
|
||||
}
|
||||
|
||||
List<object> injectionParameters = [];
|
||||
foreach (ParameterInfo parameterInfo in transpilerMethod.GetParameters())
|
||||
{
|
||||
if (parameterInfo.ParameterType == typeof(MethodBase))
|
||||
{
|
||||
injectionParameters.Add(targetMethod);
|
||||
}
|
||||
else if (parameterInfo.ParameterType == typeof(IEnumerable<CodeInstruction>))
|
||||
{
|
||||
injectionParameters.Add(originalIl);
|
||||
}
|
||||
else if (parameterInfo.ParameterType == typeof(ILGenerator))
|
||||
{
|
||||
injectionParameters.Add(GetILGenerator(targetMethod, patchClassType));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail($"Unexpected parameter type: {parameterInfo.ParameterType} inside Transpiler method of {patchClassType.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
List<CodeInstruction> transformedIl = (transpilerMethod.Invoke(null, injectionParameters.ToArray()) as IEnumerable<CodeInstruction>)?.ToList();
|
||||
|
||||
if (logInstructions)
|
||||
{
|
||||
Console.WriteLine(transformedIl.ToPrettyString());
|
||||
}
|
||||
|
||||
if (transformedIl == null || transformedIl.Count == 0)
|
||||
{
|
||||
Assert.Fail($"Calling {patchClassType.Name}.Transpiler() through reflection returned null or an empty list.");
|
||||
}
|
||||
|
||||
originalIlCopy.Count.Should().Be(transformedIl.Count - ilDifference);
|
||||
Assert.IsFalse(originalIlCopy.SequenceEqual(transformedIl, new CodeInstructionComparer()), $"The transpiler patch of {patchClassType.Name} did not change the IL");
|
||||
}
|
||||
|
||||
private static readonly ModuleBuilder patchTestModule;
|
||||
|
||||
static PatchesTranspilerTest()
|
||||
{
|
||||
AssemblyName asmName = new();
|
||||
asmName.Name = "PatchTestAssembly";
|
||||
|
||||
PersistedAssemblyBuilder myAsmBuilder = new(asmName, typeof(object).Assembly);
|
||||
patchTestModule = myAsmBuilder.DefineDynamicModule(asmName.Name);
|
||||
}
|
||||
|
||||
/// This complicated generation is required for ILGenerator.DeclareLocal to work
|
||||
private static ILGenerator GetILGenerator(MethodInfo method, Type generatingType)
|
||||
{
|
||||
TypeBuilder myTypeBld = patchTestModule.DefineType($"{generatingType}_PatchTestType", TypeAttributes.Public);
|
||||
|
||||
return myTypeBld.DefineMethod(method.Name, MethodAttributes.Public, method.ReturnType, method.GetParameters().Types()).GetILGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
public class CodeInstructionComparer : IEqualityComparer<CodeInstruction>
|
||||
{
|
||||
public bool Equals(CodeInstruction x, CodeInstruction y)
|
||||
{
|
||||
if (ReferenceEquals(x, y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (x is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (y is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (x.GetType() != y.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return x.opcode.Equals(y.opcode) && Equals(x.operand, y.operand);
|
||||
}
|
||||
|
||||
public int GetHashCode(CodeInstruction obj)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (obj.opcode.GetHashCode() * 397) ^ (obj.operand != null ? obj.operand.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using LitJson;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace Nitrox.Test.Patcher.Patches.Persistent;
|
||||
|
||||
[TestClass]
|
||||
public class Language_LoadLanguageFile_PatchTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void DefaultLanguageSanity()
|
||||
{
|
||||
string languageFolder = Path.Combine(NitroxUser.AssetsPath, "LanguageFiles");
|
||||
Assert.IsTrue(Directory.Exists(languageFolder), $"The language files folder does not exist at {languageFolder}.");
|
||||
|
||||
string defaultLanguageFilePath = Path.Combine(languageFolder, "en.json");
|
||||
Assert.IsTrue(File.Exists(defaultLanguageFilePath), $"The english language file does not exist at {defaultLanguageFilePath}.");
|
||||
|
||||
using StreamReader streamReader = new(defaultLanguageFilePath);
|
||||
try
|
||||
{
|
||||
JsonData defaultLanguage = JsonMapper.ToObject(streamReader);
|
||||
Assert.IsTrue(defaultLanguage.Keys.Count > 0, "The english language file has no entries.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Unable to map default json file : {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
24
Nitrox.Test/Server/Helper/XORRandomTest.cs
Normal file
24
Nitrox.Test/Server/Helper/XORRandomTest.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NitroxServer.Helper;
|
||||
|
||||
namespace Nitrox.Test.Server.Helper;
|
||||
|
||||
[TestClass]
|
||||
public class XORRandomTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestMeanGeneration()
|
||||
{
|
||||
// arbitrary values under there but we can't compare the generated values with UnityEngine.Random because it's unaccessible
|
||||
XORRandom.InitSeed("cheescake".GetHashCode());
|
||||
float mean = 0;
|
||||
int count = 1000000;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
mean += XORRandom.NextFloat();
|
||||
}
|
||||
mean /= count;
|
||||
Assert.IsTrue(Math.Abs(0.5f - mean) < 0.001f, $"Float number generation isn't uniform enough: {mean}");
|
||||
}
|
||||
}
|
508
Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs
Normal file
508
Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs
Normal file
@@ -0,0 +1,508 @@
|
||||
using Nitrox.Test;
|
||||
using Nitrox.Test.Helper.Faker;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata.Bases;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Unlockables;
|
||||
using NitroxServer.Serialization.World;
|
||||
using NitroxServer_Subnautica;
|
||||
|
||||
namespace NitroxServer.Serialization;
|
||||
|
||||
[TestClass]
|
||||
public class WorldPersistenceTest
|
||||
{
|
||||
private static readonly string tempSaveFilePath = Path.Combine(Path.GetTempPath(), "NitroxTestTempDir");
|
||||
private static PersistedWorldData worldData;
|
||||
public static PersistedWorldData[] WorldsDataAfter { get; private set; }
|
||||
public static IServerSerializer[] ServerSerializers { get; private set; }
|
||||
|
||||
[ClassInitialize]
|
||||
public static void ClassInitialize(TestContext context)
|
||||
{
|
||||
NitroxServiceLocator.InitializeDependencyContainer(new SubnauticaServerAutoFacRegistrar(), new TestAutoFacRegistrar());
|
||||
NitroxServiceLocator.BeginNewLifetimeScope();
|
||||
|
||||
WorldPersistence worldPersistence = NitroxServiceLocator.LocateService<WorldPersistence>();
|
||||
ServerSerializers = NitroxServiceLocator.LocateService<IServerSerializer[]>();
|
||||
WorldsDataAfter = new PersistedWorldData[ServerSerializers.Length];
|
||||
|
||||
worldData = new NitroxAutoFaker<PersistedWorldData>().Generate();
|
||||
|
||||
for (int index = 0; index < ServerSerializers.Length; index++)
|
||||
{
|
||||
//Checking saving
|
||||
worldPersistence.UpdateSerializer(ServerSerializers[index]);
|
||||
Assert.IsTrue(worldPersistence.Save(worldData, tempSaveFilePath), $"Saving normal world failed while using {ServerSerializers[index]}.");
|
||||
|
||||
//Checking loading
|
||||
WorldsDataAfter[index] = worldPersistence.LoadDataFromPath(tempSaveFilePath);
|
||||
Assert.IsNotNull(WorldsDataAfter[index], $"Loading saved world failed while using {ServerSerializers[index]}.");
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod, DynamicWorldDataAfter]
|
||||
public void WorldDataTest(PersistedWorldData worldDataAfter, string serializerName)
|
||||
{
|
||||
Assert.IsTrue(worldData.WorldData.ParsedBatchCells.SequenceEqual(worldDataAfter.WorldData.ParsedBatchCells));
|
||||
Assert.AreEqual(worldData.WorldData.Seed, worldDataAfter.WorldData.Seed);
|
||||
|
||||
PDAStateTest(worldData.WorldData.GameData.PDAState, worldDataAfter.WorldData.GameData.PDAState);
|
||||
StoryGoalTest(worldData.WorldData.GameData.StoryGoals, worldDataAfter.WorldData.GameData.StoryGoals);
|
||||
StoryTimingTest(worldData.WorldData.GameData.StoryTiming, worldDataAfter.WorldData.GameData.StoryTiming);
|
||||
}
|
||||
|
||||
private static void PDAStateTest(PDAStateData pdaState, PDAStateData pdaStateAfter)
|
||||
{
|
||||
Assert.IsTrue(pdaState.KnownTechTypes.SequenceEqual(pdaStateAfter.KnownTechTypes));
|
||||
Assert.IsTrue(pdaState.AnalyzedTechTypes.SequenceEqual(pdaStateAfter.AnalyzedTechTypes));
|
||||
AssertHelper.IsListEqual(pdaState.PdaLog.OrderBy(x => x.Key), pdaStateAfter.PdaLog.OrderBy(x => x.Key), (entry, entryAfter) =>
|
||||
{
|
||||
Assert.AreEqual(entry.Key, entryAfter.Key);
|
||||
Assert.AreEqual(entry.Timestamp, entryAfter.Timestamp);
|
||||
});
|
||||
Assert.IsTrue(pdaState.EncyclopediaEntries.SequenceEqual(pdaStateAfter.EncyclopediaEntries));
|
||||
Assert.IsTrue(pdaState.ScannerFragments.SequenceEqual(pdaStateAfter.ScannerFragments));
|
||||
AssertHelper.IsListEqual(pdaState.ScannerPartial.OrderBy(x => x.TechType.Name), pdaStateAfter.ScannerPartial.OrderBy(x => x.TechType.Name), (entry, entryAfter) =>
|
||||
{
|
||||
Assert.AreEqual(entry.TechType, entryAfter.TechType);
|
||||
Assert.AreEqual(entry.Unlocked, entryAfter.Unlocked);
|
||||
});
|
||||
|
||||
Assert.IsTrue(pdaState.ScannerComplete.SequenceEqual(pdaStateAfter.ScannerComplete));
|
||||
}
|
||||
|
||||
private static void StoryGoalTest(StoryGoalData storyGoal, StoryGoalData storyGoalAfter)
|
||||
{
|
||||
Assert.IsTrue(storyGoal.CompletedGoals.SequenceEqual(storyGoalAfter.CompletedGoals));
|
||||
Assert.IsTrue(storyGoal.RadioQueue.SequenceEqual(storyGoalAfter.RadioQueue));
|
||||
AssertHelper.IsListEqual(storyGoal.ScheduledGoals.OrderBy(x => x.GoalKey), storyGoalAfter.ScheduledGoals.OrderBy(x => x.GoalKey), (scheduledGoal, scheduledGoalAfter) =>
|
||||
{
|
||||
Assert.AreEqual(scheduledGoal.TimeExecute, scheduledGoalAfter.TimeExecute);
|
||||
Assert.AreEqual(scheduledGoal.GoalKey, scheduledGoalAfter.GoalKey);
|
||||
Assert.AreEqual(scheduledGoal.GoalType, scheduledGoalAfter.GoalType);
|
||||
});
|
||||
}
|
||||
|
||||
private static void StoryTimingTest(StoryTimingData storyTiming, StoryTimingData storyTimingAfter)
|
||||
{
|
||||
Assert.AreEqual(storyTiming.ElapsedSeconds, storyTimingAfter.ElapsedSeconds);
|
||||
Assert.AreEqual(storyTiming.AuroraCountdownTime, storyTimingAfter.AuroraCountdownTime);
|
||||
Assert.AreEqual(storyTiming.AuroraWarningTime, storyTimingAfter.AuroraWarningTime);
|
||||
}
|
||||
|
||||
[DataTestMethod, DynamicWorldDataAfter]
|
||||
public void PlayerDataTest(PersistedWorldData worldDataAfter, string serializerName)
|
||||
{
|
||||
AssertHelper.IsListEqual(worldData.PlayerData.Players.OrderBy(x => x.Id), worldDataAfter.PlayerData.Players.OrderBy(x => x.Id), (playerData, playerDataAfter) =>
|
||||
{
|
||||
Assert.AreEqual(playerData.Name, playerDataAfter.Name);
|
||||
|
||||
Assert.IsTrue(playerData.UsedItems.SequenceEqual(playerDataAfter.UsedItems));
|
||||
Assert.IsTrue(playerData.QuickSlotsBindingIds.SequenceEqual(playerDataAfter.QuickSlotsBindingIds));
|
||||
AssertHelper.IsDictionaryEqual(playerData.EquippedItems, playerDataAfter.EquippedItems, (keyValuePair, keyValuePairAfter) =>
|
||||
{
|
||||
Assert.AreEqual(keyValuePair.Key, keyValuePairAfter.Key);
|
||||
Assert.AreEqual(keyValuePair.Value, keyValuePairAfter.Value);
|
||||
});
|
||||
|
||||
Assert.AreEqual(playerData.Id, playerDataAfter.Id);
|
||||
Assert.AreEqual(playerData.SpawnPosition, playerDataAfter.SpawnPosition);
|
||||
Assert.AreEqual(playerData.SpawnRotation, playerDataAfter.SpawnRotation);
|
||||
|
||||
Assert.AreEqual(playerData.CurrentStats.Oxygen, playerDataAfter.CurrentStats.Oxygen);
|
||||
Assert.AreEqual(playerData.CurrentStats.MaxOxygen, playerDataAfter.CurrentStats.MaxOxygen);
|
||||
Assert.AreEqual(playerData.CurrentStats.Health, playerDataAfter.CurrentStats.Health);
|
||||
Assert.AreEqual(playerData.CurrentStats.Food, playerDataAfter.CurrentStats.Food);
|
||||
Assert.AreEqual(playerData.CurrentStats.Water, playerDataAfter.CurrentStats.Water);
|
||||
Assert.AreEqual(playerData.CurrentStats.InfectionAmount, playerDataAfter.CurrentStats.InfectionAmount);
|
||||
|
||||
Assert.AreEqual(playerData.SubRootId, playerDataAfter.SubRootId);
|
||||
Assert.AreEqual(playerData.Permissions, playerDataAfter.Permissions);
|
||||
Assert.AreEqual(playerData.NitroxId, playerDataAfter.NitroxId);
|
||||
Assert.AreEqual(playerData.IsPermaDeath, playerDataAfter.IsPermaDeath);
|
||||
|
||||
Assert.IsTrue(playerData.PersonalCompletedGoalsWithTimestamp.SequenceEqual(playerDataAfter.PersonalCompletedGoalsWithTimestamp));
|
||||
|
||||
AssertHelper.IsDictionaryEqual(playerData.PlayerPreferences.PingPreferences, playerDataAfter.PlayerPreferences.PingPreferences, (keyValuePair, keyValuePairAfter) =>
|
||||
{
|
||||
Assert.AreEqual(keyValuePair.Key, keyValuePairAfter.Key);
|
||||
Assert.AreEqual(keyValuePair.Value.Color, keyValuePairAfter.Value.Color);
|
||||
Assert.AreEqual(keyValuePair.Value.Visible, keyValuePairAfter.Value.Visible);
|
||||
});
|
||||
Assert.IsTrue(playerData.PlayerPreferences.PinnedTechTypes.SequenceEqual(playerDataAfter.PlayerPreferences.PinnedTechTypes));
|
||||
});
|
||||
}
|
||||
|
||||
[DataTestMethod, DynamicWorldDataAfter]
|
||||
public void EntityDataTest(PersistedWorldData worldDataAfter, string serializerName)
|
||||
{
|
||||
AssertHelper.IsListEqual(worldData.EntityData.Entities.OrderBy(x => x.Id), worldDataAfter.EntityData.Entities.OrderBy(x => x.Id), EntityTest);
|
||||
}
|
||||
|
||||
[DataTestMethod, DynamicWorldDataAfter]
|
||||
public void GlobalRootDataTest(PersistedWorldData worldDataAfter, string serializerName)
|
||||
{
|
||||
AssertHelper.IsListEqual(worldData.GlobalRootData.Entities.OrderBy(x => x.Id), worldDataAfter.GlobalRootData.Entities.OrderBy(x => x.Id), EntityTest);
|
||||
}
|
||||
|
||||
private static void EntityTest(Entity entity, Entity entityAfter)
|
||||
{
|
||||
Assert.AreEqual(entity.Id, entityAfter.Id);
|
||||
Assert.AreEqual(entity.TechType, entityAfter.TechType);
|
||||
Assert.AreEqual(entity.ParentId, entityAfter.ParentId);
|
||||
|
||||
switch (entity.Metadata)
|
||||
{
|
||||
case KeypadMetadata metadata when entityAfter.Metadata is KeypadMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Unlocked, metadataAfter.Unlocked);
|
||||
break;
|
||||
case SealedDoorMetadata metadata when entityAfter.Metadata is SealedDoorMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Sealed, metadataAfter.Sealed);
|
||||
Assert.AreEqual(metadata.OpenedAmount, metadataAfter.OpenedAmount);
|
||||
break;
|
||||
case PrecursorDoorwayMetadata metadata when entityAfter.Metadata is PrecursorDoorwayMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.IsOpen, metadataAfter.IsOpen);
|
||||
break;
|
||||
case PrecursorTeleporterMetadata metadata when entityAfter.Metadata is PrecursorTeleporterMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.IsOpen, metadataAfter.IsOpen);
|
||||
break;
|
||||
case PrecursorKeyTerminalMetadata metadata when entityAfter.Metadata is PrecursorKeyTerminalMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Slotted, metadataAfter.Slotted);
|
||||
break;
|
||||
case PrecursorTeleporterActivationTerminalMetadata metadata when entityAfter.Metadata is PrecursorTeleporterActivationTerminalMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Unlocked, metadataAfter.Unlocked);
|
||||
break;
|
||||
case StarshipDoorMetadata metadata when entityAfter.Metadata is StarshipDoorMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.DoorLocked, metadataAfter.DoorLocked);
|
||||
Assert.AreEqual(metadata.DoorOpen, metadataAfter.DoorOpen);
|
||||
break;
|
||||
case WeldableWallPanelGenericMetadata metadata when entityAfter.Metadata is WeldableWallPanelGenericMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.LiveMixInHealth, metadataAfter.LiveMixInHealth);
|
||||
break;
|
||||
case IncubatorMetadata metadata when entityAfter.Metadata is IncubatorMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Powered, metadataAfter.Powered);
|
||||
Assert.AreEqual(metadata.Hatched, metadataAfter.Hatched);
|
||||
break;
|
||||
case EntitySignMetadata metadata when entityAfter.Metadata is EntitySignMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Text, metadataAfter.Text);
|
||||
Assert.AreEqual(metadata.ColorIndex, metadataAfter.ColorIndex);
|
||||
Assert.AreEqual(metadata.ScaleIndex, metadataAfter.ScaleIndex);
|
||||
Assert.IsTrue(metadata.Elements.SequenceEqual(metadataAfter.Elements));
|
||||
Assert.AreEqual(metadata.Background, metadataAfter.Background);
|
||||
break;
|
||||
case ConstructorMetadata metadata when entityAfter.Metadata is ConstructorMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Deployed, metadataAfter.Deployed);
|
||||
break;
|
||||
case FlashlightMetadata metadata when entityAfter.Metadata is FlashlightMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.On, metadataAfter.On);
|
||||
break;
|
||||
case BatteryMetadata metadata when entityAfter.Metadata is BatteryMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Charge, metadataAfter.Charge);
|
||||
break;
|
||||
case EscapePodMetadata metadata when entityAfter.Metadata is EscapePodMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.PodRepaired, metadataAfter.PodRepaired);
|
||||
Assert.AreEqual(metadata.RadioRepaired, metadataAfter.RadioRepaired);
|
||||
break;
|
||||
case CrafterMetadata metadata when entityAfter.Metadata is CrafterMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.TechType, metadataAfter.TechType);
|
||||
Assert.AreEqual(metadata.StartTime, metadataAfter.StartTime);
|
||||
Assert.AreEqual(metadata.Duration, metadataAfter.Duration);
|
||||
break;
|
||||
case PlantableMetadata metadata when entityAfter.Metadata is PlantableMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.TimeStartGrowth, metadataAfter.TimeStartGrowth);
|
||||
Assert.AreEqual(metadata.SlotID, metadataAfter.SlotID);
|
||||
// FruitPlantMetadata field is not checked before it's only temporary
|
||||
break;
|
||||
case FruitPlantMetadata metadata when entityAfter.Metadata is FruitPlantMetadata metadataAfter:
|
||||
Assert.IsTrue(metadata.PickedStates.SequenceEqual(metadataAfter.PickedStates));
|
||||
Assert.AreEqual(metadata.TimeNextFruit, metadataAfter.TimeNextFruit);
|
||||
break;
|
||||
case CyclopsMetadata metadata when entityAfter.Metadata is CyclopsMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.SilentRunningOn, metadataAfter.SilentRunningOn);
|
||||
Assert.AreEqual(metadata.ShieldOn, metadataAfter.ShieldOn);
|
||||
Assert.AreEqual(metadata.SonarOn, metadataAfter.SonarOn);
|
||||
Assert.AreEqual(metadata.EngineOn, metadataAfter.EngineOn);
|
||||
Assert.AreEqual(metadata.EngineMode, metadataAfter.EngineMode);
|
||||
Assert.AreEqual(metadata.Health, metadataAfter.Health);
|
||||
break;
|
||||
case SeamothMetadata metadata when entityAfter.Metadata is SeamothMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.LightsOn, metadataAfter.LightsOn);
|
||||
Assert.AreEqual(metadata.Health, metadataAfter.Health);
|
||||
Assert.AreEqual(metadata.Name, metadataAfter.Name);
|
||||
Assert.IsTrue(metadata.Colors.SequenceEqual(metadataAfter.Colors));
|
||||
break;
|
||||
case ExosuitMetadata metadata when entityAfter.Metadata is ExosuitMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Health, metadataAfter.Health);
|
||||
Assert.AreEqual(metadata.Name, metadataAfter.Name);
|
||||
Assert.IsTrue(metadata.Colors.SequenceEqual(metadataAfter.Colors));
|
||||
break;
|
||||
case SubNameInputMetadata metadata when entityAfter.Metadata is SubNameInputMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Name, metadataAfter.Name);
|
||||
Assert.IsTrue(metadata.Colors.SequenceEqual(metadataAfter.Colors));
|
||||
break;
|
||||
case RocketMetadata metadata when entityAfter.Metadata is RocketMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.CurrentStage, metadataAfter.CurrentStage);
|
||||
Assert.AreEqual(metadata.LastStageTransitionTime, metadataAfter.LastStageTransitionTime);
|
||||
Assert.AreEqual(metadata.ElevatorState, metadataAfter.ElevatorState);
|
||||
Assert.AreEqual(metadata.ElevatorPosition, metadataAfter.ElevatorPosition);
|
||||
Assert.IsTrue(metadata.PreflightChecks.SequenceEqual(metadataAfter.PreflightChecks));
|
||||
break;
|
||||
case CyclopsLightingMetadata metadata when entityAfter.Metadata is CyclopsLightingMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.FloodLightsOn, metadataAfter.FloodLightsOn);
|
||||
Assert.AreEqual(metadata.InternalLightsOn, metadataAfter.InternalLightsOn);
|
||||
break;
|
||||
case FireExtinguisherHolderMetadata metadata when entityAfter.Metadata is FireExtinguisherHolderMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.HasExtinguisher, metadataAfter.HasExtinguisher);
|
||||
Assert.AreEqual(metadata.Fuel, metadataAfter.Fuel);
|
||||
break;
|
||||
case PlayerMetadata metadata when entityAfter.Metadata is PlayerMetadata metadataAfter:
|
||||
AssertHelper.IsListEqual(metadata.EquippedItems.OrderBy(x => x.Id), metadataAfter.EquippedItems.OrderBy(x => x.Id), (equippedItem, equippedItemAfter) =>
|
||||
{
|
||||
Assert.AreEqual(equippedItem.Id, equippedItemAfter.Id);
|
||||
Assert.AreEqual(equippedItem.Slot, equippedItemAfter.Slot);
|
||||
Assert.AreEqual(equippedItem.TechType, equippedItemAfter.TechType);
|
||||
});
|
||||
break;
|
||||
case GhostMetadata ghostMetadata when entityAfter.Metadata is GhostMetadata ghostMetadataAfter:
|
||||
Assert.AreEqual(ghostMetadata.TargetOffset, ghostMetadataAfter.TargetOffset);
|
||||
|
||||
if (ghostMetadata.GetType() != ghostMetadataAfter.GetType())
|
||||
{
|
||||
Assert.Fail($"Runtime type of {nameof(GhostMetadata)} in {nameof(Entity)}.{nameof(Entity.Metadata)} is not equal: {ghostMetadata.GetType().Name} - {ghostMetadataAfter.GetType().Name}");
|
||||
}
|
||||
|
||||
switch (ghostMetadata)
|
||||
{
|
||||
case BaseAnchoredCellGhostMetadata metadata when ghostMetadataAfter is BaseAnchoredCellGhostMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.AnchoredCell, metadataAfter.AnchoredCell);
|
||||
break;
|
||||
case BaseAnchoredFaceGhostMetadata metadata when ghostMetadataAfter is BaseAnchoredFaceGhostMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.AnchoredFace, metadataAfter.AnchoredFace);
|
||||
break;
|
||||
case BaseDeconstructableGhostMetadata metadata when ghostMetadataAfter is BaseDeconstructableGhostMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.ModuleFace, metadataAfter.ModuleFace);
|
||||
Assert.AreEqual(metadata.ClassId, metadataAfter.ClassId);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case WaterParkCreatureMetadata metadata when entityAfter.Metadata is WaterParkCreatureMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Age, metadataAfter.Age);
|
||||
Assert.AreEqual(metadata.MatureTime, metadataAfter.MatureTime);
|
||||
Assert.AreEqual(metadata.TimeNextBreed, metadataAfter.TimeNextBreed);
|
||||
Assert.AreEqual(metadata.BornInside, metadataAfter.BornInside);
|
||||
break;
|
||||
case FlareMetadata metadata when entityAfter.Metadata is FlareMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.EnergyLeft, metadataAfter.EnergyLeft);
|
||||
Assert.AreEqual(metadata.HasBeenThrown, metadataAfter.HasBeenThrown);
|
||||
Assert.AreEqual(metadata.FlareActivateTime, metadataAfter.FlareActivateTime);
|
||||
break;
|
||||
case BeaconMetadata metadata when entityAfter.Metadata is BeaconMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Label, metadataAfter.Label);
|
||||
break;
|
||||
case RadiationMetadata metadata when entityAfter.Metadata is RadiationMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.Health, metadataAfter.Health);
|
||||
Assert.AreEqual(metadata.FixRealTime, metadataAfter.FixRealTime);
|
||||
break;
|
||||
case CrashHomeMetadata metadata when entityAfter.Metadata is CrashHomeMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.SpawnTime, metadataAfter.SpawnTime);
|
||||
break;
|
||||
case EatableMetadata metadata when entityAfter.Metadata is EatableMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.TimeDecayStart, metadataAfter.TimeDecayStart);
|
||||
break;
|
||||
case SeaTreaderMetadata metadata when entityAfter.Metadata is SeaTreaderMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.ReverseDirection, metadataAfter.ReverseDirection);
|
||||
Assert.AreEqual(metadata.GrazingEndTime, metadataAfter.GrazingEndTime);
|
||||
Assert.AreEqual(metadata.LeashPosition, metadataAfter.LeashPosition);
|
||||
break;
|
||||
case StayAtLeashPositionMetadata metadata when entityAfter.Metadata is StayAtLeashPositionMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.LeashPosition, metadataAfter.LeashPosition);
|
||||
break;
|
||||
case EggMetadata metadata when entityAfter.Metadata is EggMetadata metadataAfter:
|
||||
Assert.AreEqual(metadata.TimeStartHatching, metadataAfter.TimeStartHatching);
|
||||
Assert.AreEqual(metadata.Progress, metadataAfter.Progress);
|
||||
break;
|
||||
default:
|
||||
Assert.Fail($"Runtime type of {nameof(Entity)}.{nameof(Entity.Metadata)} is not equal: {entity.Metadata?.GetType().Name} - {entityAfter.Metadata?.GetType().Name}");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (entity)
|
||||
{
|
||||
case WorldEntity worldEntity when entityAfter is WorldEntity worldEntityAfter:
|
||||
Assert.AreEqual(worldEntity.Transform.LocalPosition, worldEntityAfter.Transform.LocalPosition);
|
||||
Assert.AreEqual(worldEntity.Transform.LocalRotation, worldEntityAfter.Transform.LocalRotation);
|
||||
Assert.AreEqual(worldEntity.Transform.LocalScale, worldEntityAfter.Transform.LocalScale);
|
||||
Assert.AreEqual(worldEntity.Level, worldEntityAfter.Level);
|
||||
Assert.AreEqual(worldEntity.ClassId, worldEntityAfter.ClassId);
|
||||
Assert.AreEqual(worldEntity.SpawnedByServer, worldEntityAfter.SpawnedByServer);
|
||||
|
||||
if (worldEntity.GetType() != worldEntityAfter.GetType())
|
||||
{
|
||||
Assert.Fail($"Runtime type of {nameof(WorldEntity)} is not equal: {worldEntity.GetType().Name} - {worldEntityAfter.GetType().Name}");
|
||||
}
|
||||
else if (worldEntity.GetType() != typeof(WorldEntity))
|
||||
{
|
||||
switch (worldEntity)
|
||||
{
|
||||
case PlaceholderGroupWorldEntity placeholderGroupWorldEntity when worldEntityAfter is PlaceholderGroupWorldEntity placeholderGroupWorldEntityAfter:
|
||||
Assert.AreEqual(placeholderGroupWorldEntity.ComponentIndex, placeholderGroupWorldEntityAfter.ComponentIndex);
|
||||
break;
|
||||
case CellRootEntity when worldEntityAfter is CellRootEntity:
|
||||
break;
|
||||
case PlacedWorldEntity when worldEntityAfter is PlacedWorldEntity:
|
||||
break;
|
||||
case OxygenPipeEntity oxygenPipeEntity when worldEntityAfter is OxygenPipeEntity oxygenPipeEntityAfter:
|
||||
Assert.AreEqual(oxygenPipeEntity.ParentPipeId, oxygenPipeEntityAfter.ParentPipeId);
|
||||
Assert.AreEqual(oxygenPipeEntity.RootPipeId, oxygenPipeEntityAfter.RootPipeId);
|
||||
Assert.AreEqual(oxygenPipeEntity.ParentPosition, oxygenPipeEntityAfter.ParentPosition);
|
||||
break;
|
||||
case PrefabPlaceholderEntity prefabPlaceholderEntity when entityAfter is PrefabPlaceholderEntity prefabPlaceholderEntityAfter:
|
||||
Assert.AreEqual(prefabPlaceholderEntity.ComponentIndex, prefabPlaceholderEntityAfter.ComponentIndex);
|
||||
break;
|
||||
case SerializedWorldEntity serializedWorldEntity when entityAfter is SerializedWorldEntity serializedWorldEntityAfter:
|
||||
Assert.AreEqual(serializedWorldEntity.AbsoluteEntityCell, serializedWorldEntityAfter.AbsoluteEntityCell);
|
||||
AssertHelper.IsListEqual(serializedWorldEntity.Components.OrderBy(c => c.GetHashCode()), serializedWorldEntityAfter.Components.OrderBy(c => c.GetHashCode()), (c1, c2) => c1.Equals(c2));
|
||||
Assert.AreEqual(serializedWorldEntity.Layer, serializedWorldEntityAfter.Layer);
|
||||
Assert.AreEqual(serializedWorldEntity.BatchId, serializedWorldEntityAfter.BatchId);
|
||||
Assert.AreEqual(serializedWorldEntity.CellId, serializedWorldEntityAfter.CellId);
|
||||
break;
|
||||
case GeyserWorldEntity geyserEntity when entityAfter is GeyserWorldEntity geyserEntityAfter:
|
||||
Assert.AreEqual(geyserEntity.RandomIntervalVarianceMultiplier, geyserEntityAfter.RandomIntervalVarianceMultiplier);
|
||||
Assert.AreEqual(geyserEntity.StartEruptTime, geyserEntityAfter.StartEruptTime);
|
||||
break;
|
||||
case ReefbackEntity reefbackEntity when entityAfter is ReefbackEntity reefbackEntityAfter:
|
||||
Assert.AreEqual(reefbackEntity.GrassIndex, reefbackEntityAfter.GrassIndex);
|
||||
Assert.AreEqual(reefbackEntity.OriginalPosition, reefbackEntityAfter.OriginalPosition);
|
||||
break;
|
||||
case ReefbackChildEntity reefbackChildEntity when entityAfter is ReefbackChildEntity reefbackChildEntityAfter:
|
||||
Assert.AreEqual(reefbackChildEntity.Type, reefbackChildEntityAfter.Type);
|
||||
break;
|
||||
case CreatureRespawnEntity creatureRespawnEntity when entityAfter is CreatureRespawnEntity creatureRespawnEntityAfter:
|
||||
Assert.AreEqual(creatureRespawnEntity.SpawnTime, creatureRespawnEntityAfter.SpawnTime);
|
||||
Assert.AreEqual(creatureRespawnEntity.RespawnTechType, creatureRespawnEntityAfter.RespawnTechType);
|
||||
Assert.IsTrue(creatureRespawnEntity.AddComponents.SequenceEqual(creatureRespawnEntityAfter.AddComponents));
|
||||
break;
|
||||
case GlobalRootEntity globalRootEntity when worldEntityAfter is GlobalRootEntity globalRootEntityAfter:
|
||||
if (globalRootEntity.GetType() != typeof(GlobalRootEntity))
|
||||
{
|
||||
switch (globalRootEntity)
|
||||
{
|
||||
case BuildEntity buildEntity when globalRootEntityAfter is BuildEntity buildEntityAfter:
|
||||
Assert.AreEqual(buildEntity.BaseData, buildEntityAfter.BaseData);
|
||||
break;
|
||||
case EscapePodWorldEntity escapePodWorldEntity when globalRootEntityAfter is EscapePodWorldEntity escapePodWorldEntityAfter:
|
||||
Assert.IsTrue(escapePodWorldEntity.Players.SequenceEqual(escapePodWorldEntityAfter.Players));
|
||||
break;
|
||||
case InteriorPieceEntity interiorPieceEntity when globalRootEntityAfter is InteriorPieceEntity interiorPieceEntityAfter:
|
||||
Assert.AreEqual(interiorPieceEntity.BaseFace, interiorPieceEntityAfter.BaseFace);
|
||||
break;
|
||||
case MapRoomEntity mapRoomEntity when globalRootEntityAfter is MapRoomEntity mapRoomEntityAfter:
|
||||
Assert.AreEqual(mapRoomEntity.Cell, mapRoomEntityAfter.Cell);
|
||||
break;
|
||||
case ModuleEntity moduleEntity when globalRootEntityAfter is ModuleEntity moduleEntityAfter:
|
||||
Assert.AreEqual(moduleEntity.ConstructedAmount, moduleEntityAfter.ConstructedAmount);
|
||||
Assert.AreEqual(moduleEntity.IsInside, moduleEntityAfter.IsInside);
|
||||
|
||||
if (moduleEntity.GetType() != moduleEntityAfter.GetType())
|
||||
{
|
||||
Assert.Fail($"Runtime type of {nameof(ModuleEntity)} is not equal: {moduleEntity.GetType().Name} - {moduleEntityAfter.GetType().Name}");
|
||||
}
|
||||
|
||||
switch (moduleEntity)
|
||||
{
|
||||
case GhostEntity ghostEntity when moduleEntityAfter is GhostEntity ghostEntityAfter:
|
||||
Assert.AreEqual(ghostEntity.BaseFace, ghostEntityAfter.BaseFace);
|
||||
Assert.AreEqual(ghostEntity.BaseData, ghostEntityAfter.BaseData);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case MoonpoolEntity moonpoolEntity when globalRootEntityAfter is MoonpoolEntity moonpoolEntityAfter:
|
||||
Assert.AreEqual(moonpoolEntity.Cell, moonpoolEntityAfter.Cell);
|
||||
break;
|
||||
case PlanterEntity when globalRootEntityAfter is PlanterEntity:
|
||||
break;
|
||||
case PlayerWorldEntity when globalRootEntityAfter is PlayerWorldEntity:
|
||||
break;
|
||||
case VehicleWorldEntity vehicleWorldEntity when globalRootEntityAfter is VehicleWorldEntity vehicleWorldEntityAfter:
|
||||
Assert.AreEqual(vehicleWorldEntity.SpawnerId, vehicleWorldEntityAfter.SpawnerId);
|
||||
Assert.AreEqual(vehicleWorldEntity.ConstructionTime, vehicleWorldEntityAfter.ConstructionTime);
|
||||
break;
|
||||
case RadiationLeakEntity radiationLeakEntity when globalRootEntityAfter is RadiationLeakEntity radiationLeakEntityAfter:
|
||||
Assert.AreEqual(radiationLeakEntity.ObjectIndex, radiationLeakEntityAfter.ObjectIndex);
|
||||
break;
|
||||
default:
|
||||
Assert.Fail($"Runtime type of {nameof(GlobalRootEntity)} is not equal even after the check: {worldEntity.GetType().Name} - {globalRootEntityAfter.GetType().Name}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.Fail($"Runtime type of {nameof(WorldEntity)} is not equal even after the check: {worldEntity.GetType().Name} - {worldEntityAfter.GetType().Name}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case PrefabChildEntity prefabChildEntity when entityAfter is PrefabChildEntity prefabChildEntityAfter:
|
||||
Assert.AreEqual(prefabChildEntity.ComponentIndex, prefabChildEntityAfter.ComponentIndex);
|
||||
Assert.AreEqual(prefabChildEntity.ClassId, prefabChildEntityAfter.ClassId);
|
||||
break;
|
||||
case InventoryEntity inventoryEntity when entityAfter is InventoryEntity inventoryEntityAfter:
|
||||
Assert.AreEqual(inventoryEntity.ComponentIndex, inventoryEntityAfter.ComponentIndex);
|
||||
break;
|
||||
case InventoryItemEntity inventoryItemEntity when entityAfter is InventoryItemEntity inventoryItemEntityAfter:
|
||||
Assert.AreEqual(inventoryItemEntity.ClassId, inventoryItemEntityAfter.ClassId);
|
||||
break;
|
||||
case PathBasedChildEntity pathBasedChildEntity when entityAfter is PathBasedChildEntity pathBasedChildEntityAfter:
|
||||
Assert.AreEqual(pathBasedChildEntity.Path, pathBasedChildEntityAfter.Path);
|
||||
break;
|
||||
case InstalledBatteryEntity when entityAfter is InstalledBatteryEntity:
|
||||
break;
|
||||
case InstalledModuleEntity installedModuleEntity when entityAfter is InstalledModuleEntity installedModuleEntityAfter:
|
||||
Assert.AreEqual(installedModuleEntity.Slot, installedModuleEntityAfter.Slot);
|
||||
Assert.AreEqual(installedModuleEntity.ClassId, installedModuleEntityAfter.ClassId);
|
||||
break;
|
||||
case BaseLeakEntity baseLeakEntity when entityAfter is BaseLeakEntity baseLeakEntityAfter:
|
||||
Assert.AreEqual(baseLeakEntity.Health, baseLeakEntityAfter.Health);
|
||||
Assert.AreEqual(baseLeakEntity.RelativeCell, baseLeakEntityAfter.RelativeCell);
|
||||
break;
|
||||
default:
|
||||
Assert.Fail($"Runtime type of {nameof(Entity)} is not equal: {entity.GetType().Name} - {entityAfter.GetType().Name}");
|
||||
break;
|
||||
}
|
||||
|
||||
AssertHelper.IsListEqual(entity.ChildEntities.OrderBy(x => x.Id), entityAfter.ChildEntities.OrderBy(x => x.Id), EntityTest);
|
||||
}
|
||||
|
||||
[ClassCleanup]
|
||||
public static void ClassCleanup()
|
||||
{
|
||||
NitroxServiceLocator.EndCurrentLifetimeScope();
|
||||
if (Directory.Exists(tempSaveFilePath))
|
||||
{
|
||||
Directory.Delete(tempSaveFilePath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class DynamicWorldDataAfterAttribute : Attribute, ITestDataSource
|
||||
{
|
||||
public IEnumerable<object[]> GetData(MethodInfo methodInfo)
|
||||
{
|
||||
return WorldPersistenceTest.WorldsDataAfter.Select((t, i) => new object[] { t, WorldPersistenceTest.ServerSerializers[i].GetType().Name });
|
||||
}
|
||||
|
||||
public string GetDisplayName(MethodInfo methodInfo, object[] data)
|
||||
{
|
||||
return data != null ? $"{methodInfo.Name} ({data[1]})" : $"{methodInfo.Name} (no-data)";
|
||||
}
|
||||
}
|
17
Nitrox.Test/SetupAssemblyInitializer.cs
Normal file
17
Nitrox.Test/SetupAssemblyInitializer.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
global using Nitrox.Test.Helper;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Logger;
|
||||
|
||||
namespace Nitrox.Test;
|
||||
|
||||
[TestClass]
|
||||
public class SetupAssemblyInitializer
|
||||
{
|
||||
[AssemblyInitialize]
|
||||
public static void AssemblyInit(TestContext context)
|
||||
{
|
||||
NitroxEnvironment.Set(NitroxEnvironment.Types.TESTING);
|
||||
Log.Setup();
|
||||
}
|
||||
}
|
15
Nitrox.Test/TestAutoFacRegistrar.cs
Normal file
15
Nitrox.Test/TestAutoFacRegistrar.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Autofac;
|
||||
using NitroxClient.Debuggers;
|
||||
using NitroxModel.Core;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Nitrox.Test
|
||||
{
|
||||
public class TestAutoFacRegistrar : IAutoFacRegistrar
|
||||
{
|
||||
public void RegisterDependencies(ContainerBuilder containerBuilder)
|
||||
{
|
||||
containerBuilder.RegisterInstance(Substitute.For<INetworkDebugger>()).As<INetworkDebugger>().SingleInstance();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user