package com.yammer.telemetry.agent.handlers; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; import com.yammer.telemetry.test.TransformedTest; import com.yammer.telemetry.tracing.*; import javassist.ClassPool; import javassist.CtClass; import org.apache.http.*; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.impl.conn.BasicClientConnectionManager; import org.apache.http.message.BasicHttpResponse; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HttpContext; import org.junit.Test; import java.io.IOException; import java.net.URI; import java.net.URL; import java.util.Collection; import java.util.List; import static com.yammer.telemetry.test.TelemetryTestHelpers.runTransformed; import static org.junit.Assert.*; public class ApacheHttpClientClassHandlerTest { private ApacheHttpClientClassHandler handler = new ApacheHttpClientClassHandler(); @Test public void testHttpClientDoesNotGetTransformedBecauseItIsAbstract() throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(HttpClient.class.getName()); assertFalse(handler.transform(ctClass, cp)); } @Test public void testHttpClientSubClassDoesGetTransformedIfNonAbstractExecuteMethodExists() throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(HttpClientSubClass.class.getName()); assertTrue(handler.transform(ctClass, cp)); } @Test public void testHttpClientAbstractSubClassDoesNotGetTransformed() throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(HttpClientAbstractSubClass.class.getName()); assertFalse(handler.transform(ctClass, cp)); } @Test public void testHttpClientSubClassGetsTransformed() throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(AbstractHttpClient.class.getName()); assertTrue(handler.transform(ctClass, cp)); } @Test public void testNonHttpClientClassDoesNotGetTransformed() throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(String.class.getName()); assertFalse(handler.transform(ctClass, cp)); } @Test public void runTransformedTests() throws Exception { runTransformed(TransformedTests.class, handler); } @Test public void testUsingHttpClientRecordsSpan() throws Exception { BasicHttpResponse expectedResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK"); HttpClient client = new TestableHttpClient(expectedResponse); URL localResourceUrl = TransformedTest.class.getResource("/telemetry.yml"); HttpResponse httpResponse = client.execute(new HttpGet(localResourceUrl.toURI())); assertEquals(expectedResponse, httpResponse); } @SuppressWarnings("UnusedDeclaration") public static class TransformedTests { @TransformedTest public void testUsingHttpClientRecordsSpan() throws Exception { InMemorySpanSinkSource sink = new InMemorySpanSinkSource(); SpanSinkRegistry.register(sink); HttpGet request = new HttpGet("http://anything"); try (Span trace = SpanHelper.startTrace("Test")) { BasicHttpResponse expectedResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK"); HttpClient client = new TestableHttpClient(expectedResponse); HttpResponse httpResponse = client.execute(request); assertEquals(expectedResponse, httpResponse); } Collection<Trace> traces = sink.getTraces(); assertEquals(1, traces.size()); Trace trace = traces.iterator().next(); SpanData root = trace.getRoot(); assertEquals("Test", root.getName()); List<SpanData> spans = trace.getChildren(root.getSpanId()); assertEquals(1, spans.size()); SpanData httpClientSpan = spans.get(0); assertEquals("GET http://anything", httpClientSpan.getName()); Multimap<String, String> annotationsMap = LinkedListMultimap.create(); for (AnnotationData annotation : trace.getAnnotations(httpClientSpan.getSpanId())) { annotationsMap.put(annotation.getName(), annotation.getMessage()); } assertTrue(annotationsMap.containsKey(AnnotationNames.CLIENT_SENT)); assertTrue(annotationsMap.containsKey(AnnotationNames.CLIENT_RECEIVED)); assertFalse(annotationsMap.containsKey(AnnotationNames.CLIENT_EXCEPTION)); } @TransformedTest public void testUsingHttpClientPropagatesTraceAndSpanIds() throws Exception { InMemorySpanSinkSource sink = new InMemorySpanSinkSource(); SpanSinkRegistry.register(sink); HttpGet request = new HttpGet("http://anything"); try (Span trace = SpanHelper.startTrace("Test")) { BasicHttpResponse expectedResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK"); HttpClient client = new TestableHttpClient(expectedResponse); assertEquals(expectedResponse, client.execute(request)); } Trace trace = sink.getTraces().iterator().next(); SpanData httpClientSpan = trace.getChildren(trace.getRoot().getSpanId()).get(0); Header traceHeader = request.getFirstHeader(HttpHeaderNames.TRACE_ID); Header spanHeader = request.getFirstHeader(HttpHeaderNames.SPAN_ID); assertNotNull(traceHeader); assertNotNull(spanHeader); assertEquals(trace.getTraceId().toString(), traceHeader.getValue()); assertEquals(httpClientSpan.getSpanId().toString(), spanHeader.getValue()); } @TransformedTest public void testSpanNameIncludesHttpRequestMethod() throws Exception { InMemorySpanSinkSource sink = new InMemorySpanSinkSource(); SpanSinkRegistry.register(sink); HttpRequestBase request = new HttpRequestBase() { @Override public String getMethod() { return "FOOF"; } }; request.setURI(URI.create("http://anything")); try (Span trace = SpanHelper.startTrace("Test")) { BasicHttpResponse expectedResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK"); HttpClient client = new TestableHttpClient(expectedResponse); assertEquals(expectedResponse, client.execute(request)); } Trace trace = sink.getTraces().iterator().next(); SpanData httpClientSpan = trace.getChildren(trace.getRoot().getSpanId()).get(0); assertEquals("FOOF http://anything", httpClientSpan.getName()); } @TransformedTest public void testNoTraceAndSpanHeadersSentIfCurrentSpanIsDisabled() throws Exception { InMemorySpanSinkSource sink = new InMemorySpanSinkSource(); SpanSinkRegistry.register(sink); HttpRequestBase request = new HttpRequestBase() { @Override public String getMethod() { return "GET"; } }; request.setURI(URI.create("http://anything")); SpanHelper.setSampler(Sampling.OFF); try (Span trace = SpanHelper.startTrace("Trace")) { // assertTrue(trace instanceof DisabledSpan); BasicHttpResponse expectedResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK"); HttpClient client = new TestableHttpClient(expectedResponse); assertEquals(expectedResponse, client.execute(request)); } assertTrue(sink.getTraces().isEmpty()); } } public static class TestableHttpClient implements HttpClient { private final HttpResponse result; public TestableHttpClient(HttpResponse result) { this.result = result; } @Override public HttpParams getParams() { return new BasicHttpParams(); } @Override public ClientConnectionManager getConnectionManager() { return new BasicClientConnectionManager(); } @Override public HttpResponse execute(HttpUriRequest request) { return result; } @Override public HttpResponse execute(HttpUriRequest request, HttpContext context) { return result; } @Override public HttpResponse execute(HttpHost target, HttpRequest request) { return result; } @Override public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) { return result; } @Override public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException { return responseHandler.handleResponse(result); } @Override public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException { return responseHandler.handleResponse(result); } @Override public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler) throws IOException { return responseHandler.handleResponse(result); } @Override public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException { return responseHandler.handleResponse(result); } } public static abstract class HttpClientSubClass implements HttpClient { @Override public HttpResponse execute(HttpHost target, HttpRequest request) { return null; } } public static abstract class HttpClientAbstractSubClass implements HttpClient { @Override public abstract HttpResponse execute(HttpHost target, HttpRequest request); } }