From 137ed213b54005adba995a61cd888634857c2c21 Mon Sep 17 00:00:00 2001 From: Tom Akehurst Date: Fri, 8 Dec 2023 17:56:35 +0000 Subject: [PATCH] Added support for fixed and random delays in the DSL (#31) --- .../dsl/GrpcResponseDefinitionBuilder.java | 28 +++++++++++++++- .../grpc/dsl/GrpcStubMappingBuilder.java | 22 +++++++++++++ .../org/wiremock/grpc/internal/Delays.java | 33 +++++++++++++++++++ .../grpc/internal/UnaryServerCallHandler.java | 3 ++ .../org/wiremock/grpc/GrpcAcceptanceTest.java | 32 ++++++++++++++++++ 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/wiremock/grpc/internal/Delays.java diff --git a/src/main/java/org/wiremock/grpc/dsl/GrpcResponseDefinitionBuilder.java b/src/main/java/org/wiremock/grpc/dsl/GrpcResponseDefinitionBuilder.java index f6432de..ee86432 100644 --- a/src/main/java/org/wiremock/grpc/dsl/GrpcResponseDefinitionBuilder.java +++ b/src/main/java/org/wiremock/grpc/dsl/GrpcResponseDefinitionBuilder.java @@ -16,7 +16,7 @@ package org.wiremock.grpc.dsl; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; -import com.github.tomakehurst.wiremock.http.Fault; +import com.github.tomakehurst.wiremock.http.*; import org.wiremock.annotations.Beta; @Beta(justification = "Incubating extension: https://github.com/wiremock/wiremock/issues/2383") @@ -33,6 +33,8 @@ public class GrpcResponseDefinitionBuilder { private boolean templatingEnabled; + private DelayDistribution delay; + public GrpcResponseDefinitionBuilder(WireMockGrpc.Status grpcStatus) { this(grpcStatus, null); } @@ -59,6 +61,26 @@ public GrpcResponseDefinitionBuilder withTemplatingEnabled(boolean enabled) { return this; } + public GrpcResponseDefinitionBuilder withFixedDelay(long milliseconds) { + this.delay = new FixedDelayDistribution(milliseconds); + return null; + } + + public GrpcResponseDefinitionBuilder withRandomDelay(DelayDistribution distribution) { + this.delay = distribution; + return this; + } + + public GrpcResponseDefinitionBuilder withLogNormalRandomDelay( + double medianMilliseconds, double sigma) { + return withRandomDelay(new LogNormal(medianMilliseconds, sigma)); + } + + public GrpcResponseDefinitionBuilder withUniformRandomDelay( + int lowerMilliseconds, int upperMilliseconds) { + return withRandomDelay(new UniformDistribution(lowerMilliseconds, upperMilliseconds)); + } + public ResponseDefinitionBuilder build() { if (fault != null) { return ResponseDefinitionBuilder.responseDefinition().withFault(fault); @@ -76,6 +98,10 @@ public ResponseDefinitionBuilder build() { responseDefinitionBuilder.withTransformers("response-template"); } + if (delay != null) { + responseDefinitionBuilder.withRandomDelay(delay); + } + return responseDefinitionBuilder.withBody(json); } } diff --git a/src/main/java/org/wiremock/grpc/dsl/GrpcStubMappingBuilder.java b/src/main/java/org/wiremock/grpc/dsl/GrpcStubMappingBuilder.java index 5c9cb25..d6ed4da 100644 --- a/src/main/java/org/wiremock/grpc/dsl/GrpcStubMappingBuilder.java +++ b/src/main/java/org/wiremock/grpc/dsl/GrpcStubMappingBuilder.java @@ -19,7 +19,10 @@ import com.github.tomakehurst.wiremock.client.MappingBuilder; import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.http.DelayDistribution; import com.github.tomakehurst.wiremock.http.Fault; +import com.github.tomakehurst.wiremock.http.LogNormal; +import com.github.tomakehurst.wiremock.http.UniformDistribution; import com.github.tomakehurst.wiremock.matching.StringValuePattern; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import java.util.ArrayList; @@ -58,6 +61,25 @@ public GrpcStubMappingBuilder willReturn(Fault fault) { return this; } + public GrpcStubMappingBuilder withFixedDelay(long milliseconds) { + responseBuilder.withFixedDelay(milliseconds); + return this; + } + + public GrpcStubMappingBuilder withDelay(DelayDistribution delay) { + responseBuilder.withRandomDelay(delay); + return this; + } + + public GrpcStubMappingBuilder withLogNormalRandomDelay(double medianMilliseconds, double sigma) { + return withDelay(new LogNormal(medianMilliseconds, sigma)); + } + + public GrpcStubMappingBuilder withUniformRandomDelay( + int lowerMilliseconds, int upperMilliseconds) { + return withDelay(new UniformDistribution(lowerMilliseconds, upperMilliseconds)); + } + public StubMapping build(String serviceName) { final MappingBuilder mappingBuilder = WireMock.post(grpcUrlPath(serviceName, method)); requestMessageJsonPatterns.forEach(mappingBuilder::withRequestBody); diff --git a/src/main/java/org/wiremock/grpc/internal/Delays.java b/src/main/java/org/wiremock/grpc/internal/Delays.java new file mode 100644 index 0000000..d45c92d --- /dev/null +++ b/src/main/java/org/wiremock/grpc/internal/Delays.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wiremock.grpc.internal; + +import com.github.tomakehurst.wiremock.http.Response; +import java.util.concurrent.TimeUnit; + +public class Delays { + + public static void delayIfRequired(Response response) { + final long delayMillis = response.getInitialDelay(); + if (delayMillis > 0) { + try { + TimeUnit.MILLISECONDS.sleep(delayMillis); + } catch (InterruptedException var4) { + Thread.currentThread().interrupt(); + } + } + } +} diff --git a/src/main/java/org/wiremock/grpc/internal/UnaryServerCallHandler.java b/src/main/java/org/wiremock/grpc/internal/UnaryServerCallHandler.java index edf8dff..fcaff4b 100644 --- a/src/main/java/org/wiremock/grpc/internal/UnaryServerCallHandler.java +++ b/src/main/java/org/wiremock/grpc/internal/UnaryServerCallHandler.java @@ -17,6 +17,7 @@ import static org.wiremock.grpc.dsl.GrpcResponseDefinitionBuilder.GRPC_STATUS_NAME; import static org.wiremock.grpc.dsl.GrpcResponseDefinitionBuilder.GRPC_STATUS_REASON; +import static org.wiremock.grpc.internal.Delays.delayIfRequired; import com.github.tomakehurst.wiremock.http.HttpHeader; import com.github.tomakehurst.wiremock.http.StubRequestHandler; @@ -62,6 +63,8 @@ public void invoke(DynamicMessage request, StreamObserver respon (req, resp, attributes) -> { final HttpHeader statusHeader = resp.getHeaders().getHeader(GRPC_STATUS_NAME); + delayIfRequired(resp); + if (!statusHeader.isPresent() && resp.getStatus() == 404) { responseObserver.onError( Status.NOT_FOUND diff --git a/src/test/java/org/wiremock/grpc/GrpcAcceptanceTest.java b/src/test/java/org/wiremock/grpc/GrpcAcceptanceTest.java index ee1c174..8eeed82 100644 --- a/src/test/java/org/wiremock/grpc/GrpcAcceptanceTest.java +++ b/src/test/java/org/wiremock/grpc/GrpcAcceptanceTest.java @@ -17,6 +17,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -28,6 +29,7 @@ import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.http.Fault; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.google.common.base.Stopwatch; import com.google.protobuf.Empty; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -254,4 +256,34 @@ void networkFault() { assertThrows(StatusRuntimeException.class, () -> greetingsClient.greet("Alan")); assertThat(exception.getMessage(), is("UNKNOWN")); } + + @Test + void fixedDelay() { + mockGreetingService.stubFor( + method("greeting") + .willReturn(json("{ \"greeting\": \"Delayed hello\" }")) + .withFixedDelay(1000)); + + Stopwatch stopwatch = Stopwatch.createStarted(); + String greeting = greetingsClient.greet("Tom"); + stopwatch.stop(); + + assertThat(greeting, is("Delayed hello")); + assertThat(stopwatch.elapsed(MILLISECONDS), greaterThanOrEqualTo(1000L)); + } + + @Test + void randomDelay() { + mockGreetingService.stubFor( + method("greeting") + .willReturn(json("{ \"greeting\": \"Delayed hello\" }")) + .withUniformRandomDelay(500, 1000)); + + Stopwatch stopwatch = Stopwatch.createStarted(); + String greeting = greetingsClient.greet("Tom"); + stopwatch.stop(); + + assertThat(greeting, is("Delayed hello")); + assertThat(stopwatch.elapsed(MILLISECONDS), greaterThanOrEqualTo(500L)); + } }