Skip to content

Commit

Permalink
Merge pull request #83 from openzipkin/junit-failures
Browse files Browse the repository at this point in the history
Adds HttpFailure and httpRequestCount to ZipkinRule
  • Loading branch information
adriancole committed Feb 24, 2016
2 parents e25bcb2 + 3ce5a17 commit 91a6f36
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 32 deletions.
21 changes: 21 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<junit.version>4.12</junit.version>
<assertj.version>3.3.0</assertj.version>

<animal-sniffer-maven-plugin.version>1.14</animal-sniffer-maven-plugin.version>
<maven-plugin.version>0.3.3</maven-plugin.version>
<maven-install-plugin.version>2.5.2</maven-install-plugin.version>
<maven-source-plugin.version>2.4</maven-source-plugin.version>
Expand Down Expand Up @@ -237,6 +238,26 @@
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>${animal-sniffer-maven-plugin.version}</version>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java17</artifactId>
<version>1.0</version>
</signature>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>

Expand Down
20 changes: 20 additions & 0 deletions zipkin-junit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

This contains `ZipkinRule`, a JUnit rule to spin-up a Zipkin server during tests.

Usage
------

For example, you can write micro-integration tests like so:

```java
Expand All @@ -24,3 +27,20 @@ public void skipsReportingWhenNotSampled() throws IOException {
assertThat(zipkin.getTraces()).containsOnly(asList(rootSpan));
}
```

You can also simulate failures.

For example, if you want to ensure your instrumentation doesn't retry on http 400.

```java
@Test
public void doesntAttemptToRetryOn400() throws IOException {
zipkin.enqueueFailure(sendErrorResponse(400, "Invalid Format"));

reporter.record(span);
reporter.flush();

// check that we didn't retry on 400
assertThat(zipkin.httpRequestCount()).isEqualTo(1);
}
```
14 changes: 13 additions & 1 deletion zipkin-junit/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@
</parent>

<artifactId>zipkin-junit</artifactId>
<name>JUnit rule to spin-up a Zipkin server during tests</name>
<name>Zipkin JUnit</name>
<description>JUnit rule to spin-up a Zipkin server during tests</description>

<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<mockwebserver.version>3.1.2</mockwebserver.version>
</properties>

Expand Down Expand Up @@ -55,4 +58,13 @@
</dependency>
</dependencies>

<build>
<plugins>
<!-- Make sure Java 8 types and methods aren't used -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
43 changes: 43 additions & 0 deletions zipkin-junit/src/main/java/zipkin/junit/HttpFailure.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright 2015-2016 The OpenZipkin Authors
*
* 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 zipkin.junit;

import okhttp3.mockwebserver.MockResponse;

import static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_DURING_REQUEST_BODY;

/**
* Instrumentation that uses the {@code POST /api/v1/spans} endpoint needs to survive failures.
* Besides simply not starting the zipkin server, you can enqueue failures like this to test edge
* cases. For example, that you log a failure when a 400 code is returned.
*/
public final class HttpFailure {

/** Ex a network partition occurs in the middle of the POST request */
public static HttpFailure disconnectDuringBody() {
return new HttpFailure(new MockResponse().setSocketPolicy(DISCONNECT_DURING_REQUEST_BODY));
}

/** Ex code 400 when the server cannot read the spans */
public static HttpFailure sendErrorResponse(int code, String body) {
return new HttpFailure(new MockResponse().setResponseCode(code).setBody(body));
}

/** Not exposed publicly in order to not leak okhttp3 types. */
final MockResponse response;

private HttpFailure(MockResponse response) {
this.response = response;
}
}
49 changes: 48 additions & 1 deletion zipkin-junit/src/main/java/zipkin/junit/ZipkinRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@

import java.io.IOException;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import zipkin.InMemorySpanStore;
import zipkin.Span;

import static okhttp3.mockwebserver.SocketPolicy.KEEP_OPEN;

/**
* Starts up a local Zipkin server, listening for http requests on {@link #httpUrl}.
*
Expand All @@ -31,18 +38,42 @@
* See http://openzipkin.github.io/zipkin-api/#/
*/
public final class ZipkinRule implements TestRule {

private final InMemorySpanStore store = new InMemorySpanStore();
private final MockWebServer server = new MockWebServer();
private final BlockingQueue<MockResponse> failureQueue = new LinkedBlockingQueue<>();

public ZipkinRule() {
server.setDispatcher(new ZipkinDispatcher(store, server));
Dispatcher dispatcher = new Dispatcher() {
final ZipkinDispatcher successDispatch = new ZipkinDispatcher(store, server);

@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
MockResponse maybeFailure = failureQueue.poll();
if (maybeFailure != null) return maybeFailure;
return successDispatch.dispatch(request);
}

@Override
public MockResponse peek() {
MockResponse maybeFailure = failureQueue.peek();
if (maybeFailure != null) return maybeFailure;
return new MockResponse().setSocketPolicy(KEEP_OPEN);
}
};
server.setDispatcher(dispatcher);
}

/** Use this to connect. The zipkin v1 interface will be under "/api/v1" */
public String httpUrl() {
return String.format("http://%s:%s", server.getHostName(), server.getPort());
}

/** Use this to see how many requests you've sent to any zipkin http endpoint. */
public int httpRequestCount() {
return server.getRequestCount();
}

/**
* Stores the given spans directly, to setup preconditions for a test.
*
Expand All @@ -54,6 +85,22 @@ public ZipkinRule storeSpans(Iterable<Span> spans) {
return this;
}

/**
* Adds a one-time failure to the http endpoint.
*
* <p>Ex. If you want to test that you don't repeatedly send bad data, you could send a 400 back.
*
* <pre>{@code
* zipkin.enqueueFailure(sendErrorResponse(400, "bad format"));
* }</pre>
*
* @param failure type of failure the next call to the http endpoint responds with
*/
public ZipkinRule enqueueFailure(HttpFailure failure) {
failureQueue.add(failure.response);
return this;
}

/** Retrieves all traces this zipkin server has received. */
public List<List<Span>> getTraces() {
return store.getTracesByIds(store.traceIds());
Expand Down
66 changes: 56 additions & 10 deletions zipkin-junit/src/test/java/zipkin/junit/ZipkinRuleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package zipkin.junit;

import java.io.IOException;
import java.net.ConnectException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
Expand All @@ -28,6 +29,7 @@

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
import static zipkin.Constants.SERVER_RECV;

public class ZipkinRuleTest {
Expand All @@ -40,22 +42,17 @@ public class ZipkinRuleTest {
String service = "web";
Endpoint endpoint = Endpoint.create(service, 127 << 24 | 1, 80);
Annotation ann = Annotation.create(System.currentTimeMillis() * 1000, SERVER_RECV, endpoint);
Span span = new Span.Builder().id(1L).traceId(1L).name("get").addAnnotation(ann).build();
Span span = new Span.Builder().id(1L).traceId(1L).timestamp(ann.timestamp).name("get")
.addAnnotation(ann).build();

@Test
public void getTraces_storedUsingHttp() throws IOException {
public void getTraces_storedViaPost() throws IOException {
// write the span to the zipkin using http
byte[] spansInJson = Codec.JSON.writeSpans(asList(span));
Response postResponse = client.newCall(new Request.Builder()
.url(zipkin.httpUrl() + "/api/v1/spans")
.post(RequestBody.create(MediaType.parse("application/json"), spansInJson)).build()
).execute();
assertThat(postResponse.code()).isEqualTo(202);
assertThat(postSpans(span).code()).isEqualTo(202);

// read the traces directly
assertThat(zipkin.getTraces())
.extracting(t -> t.get(0).traceId) // get only the trace id
.containsOnly(span.traceId);
.containsOnly(asList(span));
}

@Test
Expand All @@ -67,6 +64,55 @@ public void storeSpans_readbackHttp() throws IOException {
Response getResponse = client.newCall(new Request.Builder()
.url(String.format("%s/api/v1/trace/%016x", zipkin.httpUrl(), span.traceId)).build()
).execute();

assertThat(getResponse.code()).isEqualTo(200);
}

@Test
public void httpRequestCountIncrements() throws IOException {
postSpans(span);
postSpans(span);

assertThat(zipkin.httpRequestCount()).isEqualTo(2);
}

@Test
public void postSpans_disconnectDuringBody() throws IOException {
zipkin.enqueueFailure(HttpFailure.disconnectDuringBody());

try {
postSpans(span);
failBecauseExceptionWasNotThrown(ConnectException.class);
} catch (ConnectException expected) {
}

// Zipkin didn't store the spans, as they shouldn't have been readable, due to disconnect
assertThat(zipkin.getTraces()).isEmpty();

// The failure shouldn't affect later requests
assertThat(postSpans(span).code()).isEqualTo(202);
}

@Test
public void postSpans_sendErrorResponse400() throws IOException {
zipkin.enqueueFailure(HttpFailure.sendErrorResponse(400, "Invalid Format"));

Response response = postSpans(span);
assertThat(response.code()).isEqualTo(400);
assertThat(response.body().string()).isEqualTo("Invalid Format");

// Zipkin didn't store the spans, as they shouldn't have been readable, due to the error
assertThat(zipkin.getTraces()).isEmpty();

// The failure shouldn't affect later requests
assertThat(postSpans(span).code()).isEqualTo(202);
}

private Response postSpans(Span ... spans) throws IOException {
byte[] spansInJson = Codec.JSON.writeSpans(asList(spans));
return client.newCall(new Request.Builder()
.url(zipkin.httpUrl() + "/api/v1/spans")
.post(RequestBody.create(MediaType.parse("application/json"), spansInJson)).build()
).execute();
}
}
20 changes: 0 additions & 20 deletions zipkin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

<properties>
<main.basedir>${project.basedir}/..</main.basedir>
<animal-sniffer-maven-plugin.version>1.14</animal-sniffer-maven-plugin.version>
</properties>

<dependencies>
Expand All @@ -47,10 +46,6 @@
<plugin>
<inherited>true</inherited>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
<executions>
<!-- Ensure main source tree compiles to Java 7 bytecode. -->
<execution>
Expand All @@ -70,21 +65,6 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>${animal-sniffer-maven-plugin.version}</version>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java17</artifactId>
<version>1.0</version>
</signature>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Use of okio, moshi, and libthrift are internal only -->
<plugin>
Expand Down

0 comments on commit 91a6f36

Please sign in to comment.