From 2aa315c03c085fa5d4d7afbcdc0830b1caca9a6e Mon Sep 17 00:00:00 2001
From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Date: Tue, 14 Jul 2026 14:05:02 +1000
Subject: [PATCH] Add repo tests and fix build (#12)
---
.github/workflows/build-test.yml | 44 ++++++++++++++
Content.Server/LocalHostConGroup.cs | 4 +-
Content.Tests/Content.Tests.csproj | 25 ++++++++
Content.Tests/StartupTest.cs | 94 +++++++++++++++++++++++++++++
README.md | 2 +-
RobustTemplate.sln | 21 +++----
RobustToolbox | 2 +-
7 files changed, 177 insertions(+), 15 deletions(-)
create mode 100644 .github/workflows/build-test.yml
create mode 100644 Content.Tests/Content.Tests.csproj
create mode 100644 Content.Tests/StartupTest.cs
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
new file mode 100644
index 0000000..c0ccf41
--- /dev/null
+++ b/.github/workflows/build-test.yml
@@ -0,0 +1,44 @@
+name: Build & Test
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 30
+
+ steps:
+ - uses: actions/checkout@v4.2.2
+ with:
+ submodules: recursive
+
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v4.1.0
+ with:
+ dotnet-version: 10.0.x
+
+ - name: Install dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --no-restore /p:WarningsAsErrors=nullable
+
+ - name: Run tests
+ shell: pwsh
+ run: dotnet test --no-build Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0 NUnit.TestOutputXml="logs" NUnit.WorkDirectory="$(pwd)/test_results"
+
+ - name: Archive NUnit3 test results.
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: nunit3-results-${{ matrix.os }}
+ path: test_results/*
+ retention-days: 7
diff --git a/Content.Server/LocalHostConGroup.cs b/Content.Server/LocalHostConGroup.cs
index bc85b00..0e5c6f4 100644
--- a/Content.Server/LocalHostConGroup.cs
+++ b/Content.Server/LocalHostConGroup.cs
@@ -13,9 +13,9 @@ namespace Content.Server;
/// Debug/example ConGroup controller implementation that gives any client connected through localhost every permission.
///
[UsedImplicitly]
-public sealed class LocalHostConGroup : IConGroupControllerImplementation, IPostInjectInit
+public sealed partial class LocalHostConGroup : IConGroupControllerImplementation, IPostInjectInit
{
- [Dependency] private readonly IConGroupController _controller = null!;
+ [Dependency] private IConGroupController _controller = null!;
public bool CanCommand(ICommonSession session, string cmdName) {
return IsLocal(session);
diff --git a/Content.Tests/Content.Tests.csproj b/Content.Tests/Content.Tests.csproj
new file mode 100644
index 0000000..111af38
--- /dev/null
+++ b/Content.Tests/Content.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+ false
+ enable
+ enable
+ nullable
+ false
+ ..\bin\Content.Tests\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Tests/StartupTest.cs b/Content.Tests/StartupTest.cs
new file mode 100644
index 0000000..1a359de
--- /dev/null
+++ b/Content.Tests/StartupTest.cs
@@ -0,0 +1,94 @@
+using Content.Client;
+using NUnit.Framework;
+using Robust.Server.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.IoC;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
+using Robust.UnitTesting;
+
+namespace Content.Tests;
+
+[TestFixture]
+public sealed class StartupTest : RobustIntegrationTest
+{
+ [Test]
+ public async Task ServerStarts()
+ {
+ var server = StartServer(ServerOptions());
+
+ await server.WaitIdleAsync();
+
+ Assert.That(server.IsAlive, Is.True);
+ Assert.That(server.UnhandledException, Is.Null);
+ }
+
+ [Test]
+ public async Task ClientStarts()
+ {
+ var client = StartClient(ClientOptions());
+
+ await client.WaitIdleAsync();
+
+ Assert.That(client.IsAlive, Is.True);
+ Assert.That(client.UnhandledException, Is.Null);
+ }
+
+ [Test]
+ public async Task ClientConnectsToServer()
+ {
+ await using var pair = await StartConnectedPair(ServerOptions(), ClientOptions(), "TemplateTester");
+ var server = pair.Server;
+ var client = pair.Client;
+
+ await RunTicksSync(server, client, 10);
+
+ await server.WaitAssertion(() =>
+ {
+ var playerManager = IoCManager.Resolve();
+
+ Assert.That(playerManager.PlayerCount, Is.EqualTo(1));
+ Assert.That(playerManager.Sessions, Has.One.Matches(
+ session => session.Status == SessionStatus.Connected && session.Channel.IsConnected));
+ });
+
+ await client.WaitAssertion(() =>
+ {
+ var netManager = IoCManager.Resolve();
+
+ Assert.That(netManager.IsConnected, Is.True);
+ Assert.That(netManager.ServerChannel, Is.Not.Null);
+ Assert.That(netManager.ServerChannel!.IsConnected, Is.True);
+ });
+ }
+
+ private static ServerIntegrationOptions ServerOptions()
+ {
+ return new ServerIntegrationOptions
+ {
+ ContentStart = true,
+ ContentAssemblies = [typeof(Content.Server.EntryPoint).Assembly],
+ Options = new Robust.Server.ServerOptions
+ {
+ ContentModulePrefix = "Content.",
+ LoadConfigAndUserData = false,
+ LoadContentResources = false,
+ },
+ };
+ }
+
+ private static ClientIntegrationOptions ClientOptions()
+ {
+ return new ClientIntegrationOptions
+ {
+ ContentStart = true,
+ ContentAssemblies = [typeof(EntryPoint).Assembly],
+ Options = new Robust.Client.GameControllerOptions
+ {
+ ContentModulePrefix = "Content.",
+ LoadConfigAndUserData = false,
+ LoadContentResources = false,
+ },
+ };
+ }
+}
diff --git a/README.md b/README.md
index ba3a686..b257ded 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ This template additionally includes some helpful IDE settings if you're using Vi
An example file structure for Resources is also provided.
### Wait, why would I use RobustToolbox?
-RobustToolbox is a fairly modern ECS game engine with strong multiplayer support. Out of the box, it's netcode is capable of handling hundreds of players (as demonstrated by the game it was originally written for, [Space Station 14](https://spacestation14.io/)) and is relatively easy to use with less risk of common mistakes like giving clients authority over game state compared to other engines.
+RobustToolbox is a fairly modern ECS game engine with strong multiplayer support. Out of the box, its netcode is capable of handling hundreds of players (as demonstrated by the game it was originally written for, [Space Station 14](https://spacestation14.io/)) and is relatively easy to use with less risk of common mistakes like giving clients authority over game state compared to other engines.
It provides prediction-based server authoritative netcode by default, with options to both autogenerate simple state synchronization for components and to manually implement more complex state application algorithms as necessary. Additionally, basic RPC is provided through networked entity events (which can be sent both ways, and optionally targeted at specific entities) alongside direct access to the underlying transit layer for more direct control when necessary.
diff --git a/RobustTemplate.sln b/RobustTemplate.sln
index fb1dc2e..d06feae 100644
--- a/RobustTemplate.sln
+++ b/RobustTemplate.sln
@@ -37,8 +37,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlX.IL.Cecil", "RobustToo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlX.Runtime", "RobustToolbox\XamlX\src\XamlX.Runtime\XamlX.Runtime.csproj", "{19BC1F51-45E8-4BA6-854C-FDB7670AB4E3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenToolkit.GraphicsLibraryFramework", "RobustToolbox\OpenToolkit.GraphicsLibraryFramework\OpenToolkit.GraphicsLibraryFramework.csproj", "{6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Robust.LoaderApi", "Robust.LoaderApi", "{EDB842C6-67AE-4F0E-A9F2-09C2DF1024C9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.LoaderApi", "RobustToolbox\Robust.LoaderApi\Robust.LoaderApi\Robust.LoaderApi.csproj", "{EC94C69F-F0E2-44B0-BE5E-C385BECB6AD3}"
@@ -68,6 +66,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project Files", "Project Fi
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.Tests", "Content.Tests\Content.Tests.csproj", "{72C482DE-2310-4CDD-BE0A-0BFF29E25987}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -183,14 +183,6 @@ Global
{19BC1F51-45E8-4BA6-854C-FDB7670AB4E3}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU
{19BC1F51-45E8-4BA6-854C-FDB7670AB4E3}.Tools|Any CPU.ActiveCfg = Release|Any CPU
{19BC1F51-45E8-4BA6-854C-FDB7670AB4E3}.Tools|Any CPU.Build.0 = Release|Any CPU
- {6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F}.Release|Any CPU.Build.0 = Release|Any CPU
- {6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU
- {6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU
- {6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F}.Tools|Any CPU.ActiveCfg = Tools|Any CPU
- {6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F}.Tools|Any CPU.Build.0 = Tools|Any CPU
{EC94C69F-F0E2-44B0-BE5E-C385BECB6AD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC94C69F-F0E2-44B0-BE5E-C385BECB6AD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC94C69F-F0E2-44B0-BE5E-C385BECB6AD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -279,6 +271,14 @@ Global
{DBF1DF9E-6359-4179-9AB9-F38E5A348ABB}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU
{DBF1DF9E-6359-4179-9AB9-F38E5A348ABB}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{DBF1DF9E-6359-4179-9AB9-F38E5A348ABB}.Tools|Any CPU.Build.0 = Debug|Any CPU
+ {72C482DE-2310-4CDD-BE0A-0BFF29E25987}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72C482DE-2310-4CDD-BE0A-0BFF29E25987}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72C482DE-2310-4CDD-BE0A-0BFF29E25987}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72C482DE-2310-4CDD-BE0A-0BFF29E25987}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72C482DE-2310-4CDD-BE0A-0BFF29E25987}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU
+ {72C482DE-2310-4CDD-BE0A-0BFF29E25987}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU
+ {72C482DE-2310-4CDD-BE0A-0BFF29E25987}.Tools|Any CPU.ActiveCfg = Tools|Any CPU
+ {72C482DE-2310-4CDD-BE0A-0BFF29E25987}.Tools|Any CPU.Build.0 = Tools|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2A829DE3-FCB2-4FEA-A6F3-B85122C8D11E} = {F5F87A9D-C304-4833-B107-D666317F6931}
@@ -294,7 +294,6 @@ Global
{7380FFC8-AFEF-4B66-8426-2D8E34221CF4} = {91AF0388-356D-4929-9A86-1B1F5196E921}
{A406137A-DEC7-4CC5-99A8-6595ACC70B92} = {91AF0388-356D-4929-9A86-1B1F5196E921}
{19BC1F51-45E8-4BA6-854C-FDB7670AB4E3} = {91AF0388-356D-4929-9A86-1B1F5196E921}
- {6E9EFBDA-DC7F-4AFE-8BEF-D75C71FFB11F} = {F5F87A9D-C304-4833-B107-D666317F6931}
{EDB842C6-67AE-4F0E-A9F2-09C2DF1024C9} = {F5F87A9D-C304-4833-B107-D666317F6931}
{EC94C69F-F0E2-44B0-BE5E-C385BECB6AD3} = {EDB842C6-67AE-4F0E-A9F2-09C2DF1024C9}
{AF551ED1-6F07-4035-8ADE-E647A9E533AB} = {F5F87A9D-C304-4833-B107-D666317F6931}
diff --git a/RobustToolbox b/RobustToolbox
index dbde802..b366e29 160000
--- a/RobustToolbox
+++ b/RobustToolbox
@@ -1 +1 @@
-Subproject commit dbde8023ed256ac69ae4b419422c936f78361653
+Subproject commit b366e29750a1b41352bc0ca0c7c47b618df36838