Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

Commit b75637d

Browse files
authored
UTY-2508: enable command tests (#1437)
* Created mock command sender * Inject mock command sender for tests * lint * Change last test to use monobehaviour, add method to retrieve outbound requests * use sorted set * remove linq * extract subscription test inot new file * remove unused * disable warning * implement pr suggestions * Use command req id struct * remove unused :( * Adjust access modifiers * lint * comment * stray 'Z' * Update CHANGELOG.md * Use test command * Fix command test * Next frame response * throw not impl exc * multiline tests * Update CHANGELOG.md
1 parent 3852674 commit b75637d

File tree

14 files changed

+366
-14
lines changed

14 files changed

+366
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
- Fixed an `IndexOutOfRangeException` that could be thrown when editing your 'Build Configuration' asset. [#1441](https://github.com/spatialos/gdk-for-unity/pull/1441)
88
- The 'Build Configuration' Inspector window will no longer report your Android SDK installation as missing if you have a completely fresh Unity installation with the bundled Android SDK. [#1441](https://github.com/spatialos/gdk-for-unity/pull/1441)
99

10+
### Added
11+
12+
- Added capability to test commands through the `MockConnectionHandler`. [#1437](https://github.com/spatialos/gdk-for-unity/pull/1437)
13+
1014
### Internal
1115

1216
- Added C# bindings for C Event Tracing API. [#1440](https://github.com/spatialos/gdk-for-unity/pull/1440)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using Improbable.Gdk.Core;
2+
using Improbable.Gdk.Core.Commands;
3+
using Improbable.Gdk.TestUtils;
4+
using Improbable.Worker.CInterop;
5+
using NUnit.Framework;
6+
using Playground;
7+
8+
namespace Improbable.Gdk.EditmodeTests.Core
9+
{
10+
public class CommandReceivedResponseTests : MockBase
11+
{
12+
private const long EntityId = 101;
13+
private const long LaunchedId = 102;
14+
15+
[Test]
16+
public void ReceivedResponse_isNull_when_Response_is_not_received()
17+
{
18+
World.Step(world =>
19+
{
20+
world.Connection.CreateEntity(EntityId, GetTemplate());
21+
})
22+
.Step(world =>
23+
{
24+
return world.GetSystem<CommandSystem>().SendCommand(GetRequest());
25+
})
26+
.Step((world, id) =>
27+
{
28+
Assert.IsFalse(world.GetSystem<CommandSystem>()
29+
.GetResponse<Launcher.LaunchEntity.ReceivedResponse>(id).HasValue);
30+
});
31+
}
32+
33+
[Test]
34+
public void ReceivedResponse_isNotNull_when_Response_is_received()
35+
{
36+
World.Step(world =>
37+
{
38+
world.Connection.CreateEntity(EntityId, GetTemplate());
39+
})
40+
.Step(world =>
41+
{
42+
return world.GetSystem<CommandSystem>().SendCommand(GetRequest());
43+
})
44+
.Step((world, id) =>
45+
{
46+
world.Connection
47+
.GenerateResponse<Launcher.LaunchEntity.Request, Launcher.LaunchEntity.ReceivedResponse>(id,
48+
ResponseGenerator);
49+
return id;
50+
})
51+
.Step((world, id) =>
52+
{
53+
var response = world.GetSystem<CommandSystem>()
54+
.GetResponse<Launcher.LaunchEntity.ReceivedResponse>(id);
55+
Assert.IsTrue(response.HasValue);
56+
Assert.AreEqual(response.Value.EntityId, GetRequest().TargetEntityId);
57+
Assert.AreEqual(response.Value.RequestPayload, GetRequest().Payload);
58+
});
59+
}
60+
61+
private static Launcher.LaunchEntity.ReceivedResponse ResponseGenerator(CommandRequestId id, Launcher.LaunchEntity.Request request)
62+
{
63+
return new Launcher.LaunchEntity.ReceivedResponse(
64+
default,
65+
request.TargetEntityId,
66+
null,
67+
StatusCode.Success,
68+
default,
69+
request.Payload,
70+
null,
71+
id
72+
);
73+
}
74+
75+
private static EntityTemplate GetTemplate()
76+
{
77+
var template = new EntityTemplate();
78+
template.AddComponent(new Position.Snapshot(), "worker");
79+
template.AddComponent(new Launcher.Snapshot(), "worker");
80+
return template;
81+
}
82+
83+
private static Launcher.LaunchEntity.Request GetRequest()
84+
{
85+
return new Launcher.LaunchEntity.Request(new EntityId(EntityId), new LaunchCommandRequest(new EntityId(LaunchedId),
86+
new Vector3f(1, 0, 1),
87+
new Vector3f(0, 1, 0), 5, new EntityId(EntityId)));
88+
}
89+
}
90+
}

workers/unity/Assets/Tests/EditmodeTests/Correctness/Core/CommandReceivedResponseTests.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using Improbable.Gdk.Core.Commands;
2+
using Improbable.Gdk.TestUtils;
3+
using Improbable.Gdk.Subscriptions;
4+
using Improbable.Gdk.Test;
5+
using Improbable.Worker.CInterop;
6+
using NUnit.Framework;
7+
using UnityEngine;
8+
9+
namespace Improbable.Gdk.Core.EditmodeTests.Subscriptions
10+
{
11+
[TestFixture]
12+
public class CommandTests : MockBase
13+
{
14+
private const long EntityId = 101;
15+
16+
[Test]
17+
public void SubscriptionSystem_invokes_callback_on_receiving_response()
18+
{
19+
var pass = false;
20+
World.Step(world =>
21+
{
22+
world.Connection.CreateEntity(EntityId, GetTemplate());
23+
})
24+
.Step(world =>
25+
{
26+
return world.CreateGameObject<CommandStub>(EntityId).Item2.Sender;
27+
})
28+
.Step((world, sender) =>
29+
{
30+
sender.SendTestCommand(GetRequest(), response => pass = true);
31+
})
32+
.Step(world =>
33+
{
34+
world.Connection.GenerateResponses<TestCommands.Test.Request, TestCommands.Test.ReceivedResponse>(
35+
ResponseGenerator);
36+
})
37+
.Step(world =>
38+
{
39+
Assert.IsTrue(pass);
40+
});
41+
}
42+
43+
private static TestCommands.Test.ReceivedResponse ResponseGenerator(CommandRequestId id, TestCommands.Test.Request request)
44+
{
45+
return new TestCommands.Test.ReceivedResponse(
46+
default,
47+
request.TargetEntityId,
48+
null,
49+
StatusCode.Success,
50+
default,
51+
request.Payload,
52+
null,
53+
id
54+
);
55+
}
56+
57+
private static EntityTemplate GetTemplate()
58+
{
59+
var template = new EntityTemplate();
60+
template.AddComponent(new Position.Snapshot(), "worker");
61+
template.AddComponent(new TestCommands.Snapshot(), "worker");
62+
return template;
63+
}
64+
65+
private static TestCommands.Test.Request GetRequest()
66+
{
67+
return new TestCommands.Test.Request(new EntityId(EntityId), new Empty());
68+
}
69+
#pragma warning disable 649
70+
private class CommandStub : MonoBehaviour
71+
{
72+
[Require] public TestCommandsCommandSender Sender;
73+
}
74+
#pragma warning restore 649
75+
}
76+
}

workers/unity/Assets/Tests/EditmodeTests/Correctness/Subscriptions/CommandTests.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

workers/unity/Packages/io.improbable.gdk.core/.codegen/Source/Generators/Core/MetaclassGenerator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ private static TypeBlock GenerateCommandMetaclass(string qualifiedNamespace, str
7676
commandMetaclass =>
7777
{
7878
commandMetaclass.Line($@"
79+
public uint ComponentId => {rootNamespace}.ComponentId;
7980
public uint CommandIndex => {command.CommandIndex};
8081
public string Name => ""{command.PascalCaseName}"";
8182
@@ -85,6 +86,11 @@ private static TypeBlock GenerateCommandMetaclass(string qualifiedNamespace, str
8586
public Type MetaDataStorage {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}CommandMetaDataStorage);
8687
public Type SendStorage {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}CommandsToSendStorage);
8788
public Type DiffStorage {{ get; }} = typeof({rootNamespace}.Diff{command.PascalCaseName}CommandStorage);
89+
90+
public Type Response {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}.Response);
91+
public Type ReceivedResponse {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}.ReceivedResponse);
92+
public Type Request {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}.Request);
93+
public Type ReceivedRequest {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}.ReceivedRequest);
8894
");
8995
});
9096
}

workers/unity/Packages/io.improbable.gdk.core/Commands/ICommandMetaclass.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Improbable.Gdk.Core.Commands
44
{
55
public interface ICommandMetaclass
66
{
7+
uint ComponentId { get; }
78
uint CommandIndex { get; }
89
string Name { get; }
910

@@ -13,5 +14,10 @@ public interface ICommandMetaclass
1314
Type MetaDataStorage { get; }
1415
Type SendStorage { get; }
1516
Type DiffStorage { get; }
17+
18+
Type Response { get; }
19+
Type ReceivedResponse { get; }
20+
Type Request { get; }
21+
Type ReceivedRequest { get; }
1622
}
1723
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Unity.Entities;
2+
3+
namespace Improbable.Gdk.Core.Commands
4+
{
5+
public interface IOutgoingCommandHandler
6+
{
7+
CommandRequestId SendCommand<T>(T request, Entity sendingEntity = default) where T : ICommandRequest;
8+
9+
void SendResponse<T>(T response) where T : ICommandResponse;
10+
}
11+
}

workers/unity/Packages/io.improbable.gdk.core/Commands/IOutgoingCommandHandler.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

workers/unity/Packages/io.improbable.gdk.core/Dynamic/ComponentDatabase.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ public static class ComponentDatabase
1313

1414
private static Dictionary<Type, uint> SnapshotsToIds { get; }
1515

16+
private static Dictionary<Type, ICommandMetaclass> RequestsToCommandMetaclass { get; }
17+
18+
private static Dictionary<Type, ICommandMetaclass> ReceivedRequestsToCommandMetaclass { get; }
19+
1620
static ComponentDatabase()
1721
{
1822
Metaclasses = ReflectionUtility.GetNonAbstractTypes(typeof(IComponentMetaclass))
@@ -21,6 +25,12 @@ static ComponentDatabase()
2125

2226
ComponentsToIds = Metaclasses.ToDictionary(pair => pair.Value.Data, pair => pair.Key);
2327
SnapshotsToIds = Metaclasses.ToDictionary(pair => pair.Value.Snapshot, pair => pair.Key);
28+
RequestsToCommandMetaclass = Metaclasses
29+
.SelectMany(pair => pair.Value.Commands.Select(cmd => (cmd.Request, cmd)))
30+
.ToDictionary(pair => pair.Request, pair => pair.cmd);
31+
ReceivedRequestsToCommandMetaclass = Metaclasses
32+
.SelectMany(pair => pair.Value.Commands.Select(cmd => (cmd.ReceivedRequest, cmd)))
33+
.ToDictionary(pair => pair.ReceivedRequest, pair => pair.cmd);
2434
}
2535

2636
public static IComponentMetaclass GetMetaclass(uint componentId)
@@ -62,5 +72,25 @@ public static uint GetSnapshotComponentId<T>() where T : ISpatialComponentSnapsh
6272

6373
return id;
6474
}
75+
76+
public static ICommandMetaclass GetCommandMetaclassFromRequest<T>() where T : ICommandRequest
77+
{
78+
if (!RequestsToCommandMetaclass.TryGetValue(typeof(T), out var metaclass))
79+
{
80+
throw new ArgumentException($"Can not find ID for unregistered SpatialOS command request {nameof(T)}.");
81+
}
82+
83+
return metaclass;
84+
}
85+
86+
public static ICommandMetaclass GetCommandMetaclassFromReceivedRequest<T>() where T : IReceivedCommandRequest
87+
{
88+
if (!ReceivedRequestsToCommandMetaclass.TryGetValue(typeof(T), out var metaclass))
89+
{
90+
throw new ArgumentException($"Can not find ID for unregistered SpatialOS received command request {nameof(T)}.");
91+
}
92+
93+
return metaclass;
94+
}
6595
}
6696
}

0 commit comments

Comments
 (0)