From cc36d6b8ad91afc56d98b94b48a55051933d1702 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 30 Nov 2015 13:17:32 -0700 Subject: [PATCH] Added integration tests --- pom.xml | 18 +++ spring-cloud-sleuth-core/pom.xml | 15 ++ .../scheduling/TraceSchedulingAspect.java | 21 ++- .../TraceSchedulingAutoConfiguration.java | 5 +- .../sleuth/trace/DefaultTraceManager.java | 2 +- .../BaseConfigurationForITests.java | 20 +++ .../DefaultTestAutoConfiguration.java | 18 +++ .../ScheduledTestConfiguration.java | 18 +++ .../TestBeanWithScheduledMethod.java | 19 +++ .../scheduling/TracingOnScheduledITest.java | 50 +++++++ .../web/RestTemplateTraceAspectITest.java | 136 ++++++++++++++++++ .../instrument/web/TraceAsyncITest.java | 121 ++++++++++++++++ .../instrument/web/common/HttpMockServer.java | 36 +++++ .../web/common/MockServerConfiguration.java | 30 ++++ .../instrument/web/common/MvcITest.java | 45 ++++++ .../web/common/MvcWiremockITest.java | 56 ++++++++ 16 files changed, 602 insertions(+), 8 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/BaseConfigurationForITests.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/DefaultTestAutoConfiguration.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/ScheduledTestConfiguration.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TestBeanWithScheduledMethod.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledITest.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectITest.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncITest.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/HttpMockServer.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MockServerConfiguration.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MvcITest.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MvcWiremockITest.java diff --git a/pom.xml b/pom.xml index 465050911..8c14a64e4 100644 --- a/pom.xml +++ b/pom.xml @@ -198,6 +198,24 @@ groovy test + + com.jayway.awaitility + awaitility + 1.7.0 + test + + + com.github.tomakehurst + wiremock + 2.0.6-beta + test + + + pl.pragmatists + JUnitParams + 1.0.4 + test + diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 9a434aa66..671f1f478 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -115,6 +115,21 @@ 2.1.0 test + + com.jayway.awaitility + awaitility + test + + + com.github.tomakehurst + wiremock + test + + + pl.pragmatists + JUnitParams + test + diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java index f7e6487f7..04a643825 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java @@ -19,13 +19,17 @@ package org.springframework.cloud.sleuth.instrument.scheduling; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.springframework.cloud.sleuth.IdGenerator; +import org.springframework.cloud.sleuth.MilliSpan; +import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Trace; import org.springframework.cloud.sleuth.TraceManager; import org.springframework.scheduling.annotation.Scheduled; /** - * Aspect that creates a new Span for running threads executing methods annotated with {@link Scheduled} annotation. - * For every execution of scheduled method a new trace will be started. + * Aspect that creates a new Span for running threads executing methods annotated with + * {@link Scheduled} annotation. For every execution of scheduled method a new trace will + * be started. * * @author Tomasz Nurkewicz, 4financeIT * @author Michal Chmielarz, 4financeIT @@ -38,17 +42,24 @@ import org.springframework.scheduling.annotation.Scheduled; public class TraceSchedulingAspect { private final TraceManager trace; + private final IdGenerator idGenerator; - public TraceSchedulingAspect(TraceManager trace) { + public TraceSchedulingAspect(TraceManager trace, IdGenerator idGenerator) { this.trace = trace; + this.idGenerator = idGenerator; } @Around("execution (@org.springframework.scheduling.annotation.Scheduled * *.*(..))") public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwable { - Trace scope = this.trace.startSpan(pjp.toShortString()); + final Span span = this.trace.isTracing() ? this.trace.getCurrentSpan() + : MilliSpan.builder().begin(System.currentTimeMillis()) + .traceId(this.idGenerator.create()).spanId(this.idGenerator.create()) + .build(); + Trace scope = this.trace.startSpan(pjp.toShortString(), span); try { return pjp.proceed(); - } finally { + } + finally { this.trace.close(scope); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java index caf6b964a..7904464d5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.sleuth.IdGenerator; import org.springframework.cloud.sleuth.TraceManager; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; @@ -48,8 +49,8 @@ public class TraceSchedulingAutoConfiguration { @ConditionalOnClass(ProceedingJoinPoint.class) @Bean - public TraceSchedulingAspect traceSchedulingAspect(TraceManager trace) { - return new TraceSchedulingAspect(trace); + public TraceSchedulingAspect traceSchedulingAspect(TraceManager trace, IdGenerator idGenerator) { + return new TraceSchedulingAspect(trace, idGenerator); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTraceManager.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTraceManager.java index fd3de6776..e635b856f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTraceManager.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTraceManager.java @@ -117,7 +117,7 @@ public class DefaultTraceManager implements TraceManager { + ". You have " + "probably forgotten to close or detach " + cur); } else { - if (span != NullTrace.INSTANCE) { + if (span != NullTrace.INSTANCE && span!=null) { span.stop(); if (savedTrace != null && span.getParents().contains(savedTrace.getSpan().getSpanId())) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/BaseConfigurationForITests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/BaseConfigurationForITests.java new file mode 100644 index 000000000..feb492e65 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/BaseConfigurationForITests.java @@ -0,0 +1,20 @@ +package org.springframework.cloud.sleuth.instrument; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; +import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; + +@EnableAspectJAutoProxy(proxyTargetClass = true) +@Configuration +@EnableAutoConfiguration(exclude = {LoadBalancerAutoConfiguration.class, JmxAutoConfiguration.class}) +public class BaseConfigurationForITests { + + @Bean static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/DefaultTestAutoConfiguration.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/DefaultTestAutoConfiguration.java new file mode 100644 index 000000000..a96e09cfe --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/DefaultTestAutoConfiguration.java @@ -0,0 +1,18 @@ +package org.springframework.cloud.sleuth.instrument; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.integration.TraceSpringIntegrationAutoConfiguration; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@EnableAutoConfiguration(exclude = { TraceSpringIntegrationAutoConfiguration.class, + ArchaiusAutoConfiguration.class, LoadBalancerAutoConfiguration.class }) +public @interface DefaultTestAutoConfiguration { +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/ScheduledTestConfiguration.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/ScheduledTestConfiguration.java new file mode 100644 index 000000000..5bb42243b --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/ScheduledTestConfiguration.java @@ -0,0 +1,18 @@ +package org.springframework.cloud.sleuth.instrument.scheduling; + +import org.springframework.cloud.sleuth.instrument.BaseConfigurationForITests; +import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(BaseConfigurationForITests.class) +@DefaultTestAutoConfiguration +class ScheduledTestConfiguration { + + @Bean TestBeanWithScheduledMethod testBeanWithScheduledMethod() { + return new TestBeanWithScheduledMethod(); + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TestBeanWithScheduledMethod.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TestBeanWithScheduledMethod.java new file mode 100644 index 000000000..addad9f62 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TestBeanWithScheduledMethod.java @@ -0,0 +1,19 @@ +package org.springframework.cloud.sleuth.instrument.scheduling; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.trace.TraceContextHolder; +import org.springframework.scheduling.annotation.Scheduled; + +class TestBeanWithScheduledMethod { + + Span span; + + @Scheduled(fixedDelay = 1L) + public void scheduledMethod() { + this.span = TraceContextHolder.getCurrentSpan(); + } + + public Span getSpan() { + return this.span; + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledITest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledITest.java new file mode 100644 index 000000000..7afc5eb05 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledITest.java @@ -0,0 +1,50 @@ +package org.springframework.cloud.sleuth.instrument.scheduling; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.sleuth.Span; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static com.jayway.awaitility.Awaitility.await; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = {ScheduledTestConfiguration.class}) +public class TracingOnScheduledITest { + + @Autowired TestBeanWithScheduledMethod beanWithScheduledMethod; + + @Test + public void should_have_span_set_after_scheduled_method_has_been_executed() { + await().until(spanIsSetOnAScheduledMethod()); + } + + @Test + public void should_have_a_new_span_set_each_time_a_scheduled_method_has_been_executed() { + Span firstSpan = beanWithScheduledMethod.getSpan(); + await().until(differentSpanHasBeenSetThan(firstSpan)); + } + + private Runnable spanIsSetOnAScheduledMethod() { + return new Runnable() { + @Override + public void run() { + Span storedSpan = beanWithScheduledMethod.getSpan(); + assertThat(storedSpan).isNotNull(); + assertThat(storedSpan.getTraceId()).isNotNull(); + } + }; + } + + private Runnable differentSpanHasBeenSetThan(final Span spanToCompare) { + return new Runnable() { + @Override + public void run() { + assertThat(beanWithScheduledMethod.getSpan()).isNotEqualTo(spanToCompare); + } + }; + } + +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectITest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectITest.java new file mode 100644 index 000000000..325f08182 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectITest.java @@ -0,0 +1,136 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static java.util.concurrent.TimeUnit.SECONDS; +import static junitparams.JUnitParamsRunner.$; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.concurrent.Callable; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.sleuth.Trace; +import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.web.common.HttpMockServer; +import org.springframework.cloud.sleuth.instrument.web.common.MvcWiremockITest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.context.request.async.WebAsyncTask; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; + +@SpringApplicationConfiguration(classes = {RestTemplateTraceAspectITest.CorrelationIdAspectSpecConfiguration.class}) +@Ignore("Will fail due to not setting initial values for Trace and Span IDs") +@RunWith(JUnitParamsRunner.class) +public class RestTemplateTraceAspectITest extends MvcWiremockITest { + + @ClassRule public static final SpringClassRule SCR = new SpringClassRule(); + @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); + + @Before public void setupDefaultWireMockStubbing() { + stubInteraction(get(urlMatching(".*")), aResponse().withStatus(200)); + } + + @Test + public void should_set_span_data_on_headers_via_aspect_in_synchronous_call() throws Exception { + whenARequestIsSentToASyncEndpoint(); + + thenTraceIdHasBeenSetOnARequestHeader(); + } + + @Test + @Parameters + public void should_set_span_data_on_headers_via_aspect_in_asynchronous_call(String url) throws Exception { + whenARequestIsSentToAnAsyncEndpoint(url); + + thenTraceIdHasBeenSetOnARequestHeader(); + } + + public Object[] parametersForShould_set_span_data_on_headers_via_aspect_in_asynchronous_call() { + return $("/callablePing", "/webAsyncTaskPing"); + } + + private void whenARequestIsSentToASyncEndpoint() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/syncPing").accept(MediaType.TEXT_PLAIN)).andReturn(); + } + + private void thenTraceIdHasBeenSetOnARequestHeader() { + this.wireMock.verifyThat(getRequestedFor(urlMatching(".*")).withHeader(Trace.TRACE_ID_NAME, matching("^(?!\\s*$).+/))"))); + } + + private void whenARequestIsSentToAnAsyncEndpoint(String url) throws Exception { + MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get(url).accept(MediaType.TEXT_PLAIN)) + .andExpect(request().asyncStarted()) + .andReturn(); + mvcResult.getAsyncResult(SECONDS.toMillis(2)); + this.mockMvc.perform(asyncDispatch(mvcResult)). + andDo(print()). + andExpect(status().isOk()); + } + + @EnableAsync + @DefaultTestAutoConfiguration + @Import(AspectTestingController.class) + public static class CorrelationIdAspectSpecConfiguration { + } + + @RestController + public static class AspectTestingController { + + @Autowired HttpMockServer httpMockServer; + @Autowired RestTemplate restTemplate; + + @RequestMapping(value = "/syncPing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + public String syncPing() { + return callWiremockAndReturnOk(); + } + + @RequestMapping(value = "/callablePing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + public Callable asyncPing() { + return new Callable() { + @Override + public String call() throws Exception { + return callWiremockAndReturnOk(); + } + }; + } + + @RequestMapping(value = "/webAsyncTaskPing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + WebAsyncTask webAsyncTaskPing() { + return new WebAsyncTask<>(new Callable() { + @Override + public String call() throws Exception { + return callWiremockAndReturnOk(); + } + }); + }; + + private String callWiremockAndReturnOk() { + this.restTemplate.getForObject("http://localhost:"+this.httpMockServer.port(), String.class); + return "OK"; + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncITest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncITest.java new file mode 100644 index 000000000..ddc844a23 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncITest.java @@ -0,0 +1,121 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.sleuth.IdGenerator; +import org.springframework.cloud.sleuth.MilliSpan; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TraceManager; +import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; +import org.springframework.cloud.sleuth.trace.TraceContextHolder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.jayway.awaitility.Awaitility; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = {TraceAsyncITest.CorrelationIdAsyncSpecConfiguration.class}) +public class TraceAsyncITest { + + @Autowired AsyncClass asyncClass; + @Autowired AsyncDelegation asyncDelegation; + @Autowired IdGenerator idGenerator; + @Autowired TraceManager traceManager; + + @Test + public void should_set_span_on_an_async_annotated_method() { + final Span span = givenASpanInCurrentThread(); + + whenAsyncProcessingTakesPlace(); + + thenSpanPutInTheAsyncThreadIsSameAs(span); + } + + private Span givenASpanInCurrentThread() { + Span span = MilliSpan.builder().traceId(this.idGenerator.create()).spanId(this.idGenerator.create()).build(); + this.traceManager.continueSpan(span); + return span; + } + + private void whenAsyncProcessingTakesPlace() { + this.asyncDelegation.doSthThatDelegatesToAsync(); + } + + private void thenSpanPutInTheAsyncThreadIsSameAs(final Span span) { + Awaitility.await().until(new Runnable() { + @Override + public void run() { + assertThat(span.getTraceId()).isNotNull().isEqualTo(TraceAsyncITest.this.asyncClass.getTraceId()); + assertThat(span.getName()).isNotEqualTo(TraceAsyncITest.this.asyncClass.getSpanName()); + } + }); + } + + @After + public void cleanTrace() { + TraceContextHolder.removeCurrentTrace(); + } + + @DefaultTestAutoConfiguration + @EnableAsync + @EnableAspectJAutoProxy(proxyTargetClass = true) + @Configuration + public static class CorrelationIdAsyncSpecConfiguration { + + @Bean AsyncClass asyncClass() { + return new AsyncClass(); + } + + @Bean AsyncDelegation asyncDelegation() { + return new AsyncDelegation(asyncClass()); + } + } + + public static class AsyncDelegation { + + private final AsyncClass asyncClass; + + public AsyncDelegation(AsyncClass asyncClass) { + this.asyncClass = asyncClass; + } + + public void doSthThatDelegatesToAsync() { + this.asyncClass.doSth(); + } + } + + public static class AsyncClass { + + AtomicReference span; + + @Async + public void doSth() { + this.span = new AtomicReference<>(TraceContextHolder.getCurrentSpan()); + } + + public String getTraceId() { + if (this.span == null || (this.span.get() != null && this.span.get().getTraceId() == null)) { + return null; + } + return this.span.get().getTraceId(); + } + + public String getSpanName() { + if (this.span == null || (this.span.get() != null && this.span.get().getName() == null)) { + return null; + } + return this.span.get().getName(); + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/HttpMockServer.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/HttpMockServer.java new file mode 100644 index 000000000..0f8914c74 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/HttpMockServer.java @@ -0,0 +1,36 @@ +package org.springframework.cloud.sleuth.instrument.web.common; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.core.Options; + +/** + * Custom implementation of {@link WireMockServer} that by default registers itself at port + * {@link HttpMockServer#DEFAULT_PORT}. + * + * @see WireMockServer + * + * @author 4financeIT + */ +public class HttpMockServer extends WireMockServer { + + public static final int DEFAULT_PORT = 8030; + + HttpMockServer(int port) { + super(port); + } + + HttpMockServer() { + super(DEFAULT_PORT); + } + + HttpMockServer(Options options) { + super(options); + } + + public void shutdownServer() { + if (isRunning()) { + stop(); + } + shutdown(); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MockServerConfiguration.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MockServerConfiguration.java new file mode 100644 index 000000000..4314d71c3 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MockServerConfiguration.java @@ -0,0 +1,30 @@ +package org.springframework.cloud.sleuth.instrument.web.common; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.SocketUtils; + +@Configuration +@Slf4j +public class MockServerConfiguration { + + @Bean(destroyMethod = "shutdownServer") + HttpMockServer httpMockServer() { + return tryToStartMockServer(); + } + + private HttpMockServer tryToStartMockServer() { + HttpMockServer httpMockServer = null; + while(httpMockServer == null) { + try { + httpMockServer = new HttpMockServer(SocketUtils.findAvailableTcpPort()); + httpMockServer.start(); + } catch (Exception exception) { + log.warn("Exception occurred while trying to set the port for the Wiremock server", exception); + httpMockServer = null; + } + } + return httpMockServer; + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MvcITest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MvcITest.java new file mode 100644 index 000000000..432316f71 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MvcITest.java @@ -0,0 +1,45 @@ +package org.springframework.cloud.sleuth.instrument.web.common; + +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +/** + * Base for specifications that use Spring's {@link MockMvc}. Provides also {@link WebApplicationContext}, + * {@link ApplicationContext}. The latter you can use to specify what + * kind of address should be returned for a given dependency name. + * + * @see WebApplicationContext + * @see ApplicationContext + * + * @author 4financeIT + */ +@WebAppConfiguration +@IntegrationTest +public abstract class MvcITest { + + @Autowired protected WebApplicationContext webApplicationContext; + @Autowired protected ApplicationContext applicationContext; + protected MockMvc mockMvc; + + @Before + public void setup() { + DefaultMockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(this.webApplicationContext); + configureMockMvcBuilder(mockMvcBuilder); + this.mockMvc = mockMvcBuilder.build(); + } + + /** + * Override in a subclass to modify mockMvcBuilder configuration (e.g. add filter). + *

+ * The method from super class should be called. + */ + protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) { + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MvcWiremockITest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MvcWiremockITest.java new file mode 100644 index 000000000..822ed8d6e --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/MvcWiremockITest.java @@ -0,0 +1,56 @@ +package org.springframework.cloud.sleuth.instrument.web.common; + +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.sleuth.TraceManager; +import org.springframework.cloud.sleuth.instrument.web.TraceFilter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; + +import com.github.tomakehurst.wiremock.client.MappingBuilder; +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.client.WireMock; + +/** + * Base specification for tests that use Wiremock as HTTP server stub. + * By extending this specification you gain a bean with {@link HttpMockServer} and a {@link WireMock} + * + * @author 4financeIT + * @see MockServerConfiguration + * @see WireMock + * @see HttpMockServer + * + * @author 4financeIT + */ +@ContextConfiguration(classes = {MockServerConfiguration.class}) +public abstract class MvcWiremockITest extends MvcITest { + + protected WireMock wireMock; + @Autowired protected HttpMockServer httpMockServer; + @Autowired protected TraceManager trace; + + @Override + @Before + public void setup() { + super.setup(); + this.wireMock = new WireMock("localhost", this.httpMockServer.port()); + this.wireMock.resetToDefaultMappings(); + } + + protected void stubInteraction(MappingBuilder mapping, ResponseDefinitionBuilder response) { + this.wireMock.register(mapping.willReturn(response)); + } + + public WireMock getWireMock() { + return this.wireMock; + } + + public void setWireMock(WireMock wireMock) { + this.wireMock = wireMock; + } + + @Override + protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) { + mockMvcBuilder.addFilters(new TraceFilter(this.trace)); + } +}