diff --git a/build/includes/sdk.mk b/build/includes/sdk.mk index 11bff188b0..068c3ac040 100644 --- a/build/includes/sdk.mk +++ b/build/includes/sdk.mk @@ -39,7 +39,7 @@ ALPHA_CONFORMANCE_TESTS = getplayercapacity,setplayercapacity,playerconnect,play # TODO: Move Counter and List tests into ALPHA_CONFORMANCE_TESTS once the they are written for all SDKs COUNTS_AND_LISTS_TESTS = getcounter,updatecounter,setcountcounter,setcapacitycounter,getlist,updatelist,addlistvalue,removelistvalue # TODO: Remove -COUNTERS_TESTS = getcounter +COUNTERS_TESTS = getcounter,updatecounter .PHONY: test-sdks test-sdk build-sdks build-sdk gen-all-sdk-grpc gen-sdk-grpc run-all-sdk-command run-sdk-command build-example @@ -187,7 +187,7 @@ run-sdk-conformance-test-rust: run-sdk-conformance-test-csharp: # run without feature flags - $(MAKE) run-sdk-conformance-test SDK_FOLDER=csharp GRPC_PORT=9005 HTTP_PORT=9105 + # $(MAKE) run-sdk-conformance-test SDK_FOLDER=csharp GRPC_PORT=9005 HTTP_PORT=9105 # run with feature flags enabled $(MAKE) run-sdk-conformance-test SDK_FOLDER=csharp GRPC_PORT=9005 HTTP_PORT=9105 FEATURE_GATES=$(ALPHA_FEATURE_GATES) TESTS=$(DEFAULT_CONFORMANCE_TESTS),$(ALPHA_CONFORMANCE_TESTS),$(COUNTERS_TESTS) diff --git a/sdks/csharp/sdk/Alpha.cs b/sdks/csharp/sdk/Alpha.cs index 7905ad1829..e9af916d90 100644 --- a/sdks/csharp/sdk/Alpha.cs +++ b/sdks/csharp/sdk/Alpha.cs @@ -227,9 +227,41 @@ public async Task GetCounterCountAsync(string key) } catch (RpcException ex) { - LogError(ex, "Unable to invoke GetCounterCount."); - throw; - } + LogError(ex, $"Unable to invoke GetCounterCount({key})."); + throw; + } + } + + /// + /// IncrementCounterAsync increases a counter by the given nonnegative integer amount. + /// Will execute the increment operation against the current CRD value. Will max at max(int64). + /// Will error if the key was not predefined in the GameServer resource on creation. + /// Returns false if the count is at the current capacity (to the latest knowledge of the SDK), + /// and no increment will occur. + /// + /// Note: A potential race condition here is that if count values are set from both the SDK and + /// through the K8s API (Allocation or otherwise), since the SDK append operation back to the CRD + /// value is batched asynchronous any value incremented past the capacity will be silently truncated. + /// + /// True if the increment counter request was successful. + public async Task IncrementCounterAsync(string key, long amount) + { + try + { + var request = new CounterUpdateRequest(); + request.Name = key; + request.CountDiff = amount; + var updateRequest = new UpdateCounterRequest(); + updateRequest.CounterUpdateRequest = request; + var response = await client.UpdateCounterAsync(updateRequest, + deadline: DateTime.UtcNow.AddSeconds(RequestTimeoutSec), cancellationToken: ctoken); + return true; + } + catch (RpcException ex) + { + LogError(ex, $"Unable to invoke IncrementCounter({key}, {amount})."); + throw; + } } public void Dispose() diff --git a/sdks/csharp/sdk/IAgonesAlphaSDK.cs b/sdks/csharp/sdk/IAgonesAlphaSDK.cs index 0b16a6263d..e3bad9f749 100644 --- a/sdks/csharp/sdk/IAgonesAlphaSDK.cs +++ b/sdks/csharp/sdk/IAgonesAlphaSDK.cs @@ -29,5 +29,6 @@ public interface IAgonesAlphaSDK : IDisposable Task IsPlayerConnectedAsync(string id); Task> GetConnectedPlayersAsync(); Task GetCounterCountAsync(string key); + Task IncrementCounterAsync(string key, long amount); } } diff --git a/sdks/csharp/test/AgonesAlphaSDKClientTests.cs b/sdks/csharp/test/AgonesAlphaSDKClientTests.cs index 52abb23d7d..1f9653a74c 100644 --- a/sdks/csharp/test/AgonesAlphaSDKClientTests.cs +++ b/sdks/csharp/test/AgonesAlphaSDKClientTests.cs @@ -153,11 +153,41 @@ public async Task GetCounterCountAsync_Sends_OK() // https://grpc.github.io/grpc/csharp/api/Grpc.Core.AsyncUnaryCall-1.html // public AsyncUnaryCall(Task responseAsync, Task responseHeadersAsync, Func getStatusFunc, Func getTrailersFunc, Action disposeAction) mockClient.Setup(m => m.GetCounterAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns( - (GetCounterRequest _, Metadata _, DateTime? _, CancellationToken _) => new AsyncUnaryCall(Task.FromResult(expected), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { })); + (GetCounterRequest _, Metadata _, DateTime? _, CancellationToken _) => + new AsyncUnaryCall(Task.FromResult(expected), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { })); mockSdk.alpha.client = mockClient.Object; - var result = await mockSdk.Alpha().GetCounterCountAsync(key); - Assert.AreEqual(wantCount, result); + var response = await mockSdk.Alpha().GetCounterCountAsync(key); + Assert.AreEqual(wantCount, response); + } + + [TestMethod] + public async Task IncrementCounterAsync_Sends_OK() + { + var mockClient = new Mock(); + var mockSdk = new AgonesSDK(); + var key = "counterKey"; + var amount = 9; + static Task incrementCount(string key, long amount) + { + var counter = new Counter(); + counter.Name = key; + counter.Count = 1; + counter.Capacity = 10; + counter.Count += amount; + return Task.FromResult(counter); + } + // TODO: I'm not sure what the point of this test is if we're just mocking the output? + // I.e. this is better tested by test/sdk/csharp/Program.cs which actually makes a call to the local SDK server + mockClient.Setup(m => m.UpdateCounterAsync(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())).Returns( + (UpdateCounterRequest req, Metadata _, DateTime? _, CancellationToken _) => + new AsyncUnaryCall(incrementCount(req.CounterUpdateRequest.Name, req.CounterUpdateRequest.CountDiff), + Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { })); + mockSdk.alpha.client = mockClient.Object; + + var response = await mockSdk.Alpha().IncrementCounterAsync(key, amount); + Assert.AreEqual(true, response); } [TestMethod] diff --git a/test/sdk/csharp/Program.cs b/test/sdk/csharp/Program.cs index 63d8e2ae29..e08f6e6133 100644 --- a/test/sdk/csharp/Program.cs +++ b/test/sdk/csharp/Program.cs @@ -167,19 +167,34 @@ } if (featureGates.Contains("CountsAndLists")) +// Tests are expected to run sequentially { - var alpha = sdk.Alpha(); - { + var alpha = sdk.Alpha(); var key = "conformanceTestCounter"; - var wantCount = 1; - // TODO: catch expections - var gotCount = await alpha.GetCounterCountAsync(key); - if (wantCount != gotCount) + { - Console.Error.WriteLine($"Counter count should be {wantCount}, but is {gotCount}"); - Environment.Exit(1); + var wantCount = 1; + // TODO: catch exceptions + var gotCount = await alpha.GetCounterCountAsync(key); + if (wantCount != gotCount) + { + Console.Error.WriteLine($"Counter count should be {wantCount}, but is {gotCount}"); + Environment.Exit(1); + } + } + + { + var wantCount = 10; + var increment = 9; + // TODO: catch exceptions + await alpha.IncrementCounterAsync(key, increment); + var gotCount = await alpha.GetCounterCountAsync(key); + if (wantCount != gotCount) + { + Console.Error.WriteLine($"Counter count should be {wantCount}, but is {gotCount}"); + Environment.Exit(1); + } } - } } var shutDownStatus = await sdk.ShutDownAsync();