Added integration tests

This commit is contained in:
Spencer Gibb
2015-11-30 13:17:32 -07:00
committed by Dave Syer
parent ffa97c1f20
commit cc36d6b8ad
16 changed files with 602 additions and 8 deletions

18
pom.xml
View File

@@ -198,6 +198,24 @@
<artifactId>groovy</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.0.6-beta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<version>1.0.4</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -115,6 +115,21 @@
<version>2.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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())) {

View File

@@ -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();
}
}

View File

@@ -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 {
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
};
}
}

View File

@@ -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<String> asyncPing() {
return new Callable<String>() {
@Override
public String call() throws Exception {
return callWiremockAndReturnOk();
}
};
}
@RequestMapping(value = "/webAsyncTaskPing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE)
WebAsyncTask<String> webAsyncTaskPing() {
return new WebAsyncTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
return callWiremockAndReturnOk();
}
});
};
private String callWiremockAndReturnOk() {
this.restTemplate.getForObject("http://localhost:"+this.httpMockServer.port(), String.class);
return "OK";
}
}
}

View File

@@ -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> 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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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).
* <p>
* The method from super class should be called.
*/
protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) {
}
}

View File

@@ -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));
}
}