/**
* 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.internal;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import zipkin.BinaryAnnotation;
import zipkin.Codec;
import zipkin.CodecTest;
import zipkin.Endpoint;
import zipkin.Span;
import zipkin.TestObjects;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static zipkin.internal.Util.UTF_8;
public final class JsonCodecTest extends CodecTest {
@Override
protected JsonCodec codec() {
return Codec.JSON;
}
@Test
public void tracesRoundTrip() throws IOException {
List<List<Span>> traces = asList(TestObjects.TRACE, TestObjects.TRACE);
byte[] bytes = codec().writeTraces(traces);
assertThat(codec().readTraces(bytes))
.isEqualTo(traces);
}
@Test
public void stringsRoundTrip() throws IOException {
List<String> strings = asList("foo", "bar", "baz");
byte[] bytes = codec().writeStrings(strings);
assertThat(codec().readStrings(bytes))
.isEqualTo(strings);
}
@Test
public void writesTraceIdHighIntoTraceIdField() {
Span with128BitTraceId = Span.builder()
.traceIdHigh(Util.lowerHexToUnsignedLong("48485a3953bb6124"))
.traceId(Util.lowerHexToUnsignedLong("6b221d5bc9e6496c"))
.id(1).name("").build();
assertThat(new String(Codec.JSON.writeSpan(with128BitTraceId), Util.UTF_8))
.startsWith("{\"traceId\":\"48485a3953bb61246b221d5bc9e6496c\"");
}
@Test
public void readsTraceIdHighFromTraceIdField() {
byte[] with128BitTraceId = ("{\n"
+ " \"traceId\": \"48485a3953bb61246b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\"\n"
+ "}").getBytes(UTF_8);
byte[] withLower64bitsTraceId = ("{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\"\n"
+ "}").getBytes(UTF_8);
assertThat(Codec.JSON.readSpan(with128BitTraceId))
.isEqualTo(Codec.JSON.readSpan(withLower64bitsTraceId).toBuilder()
.traceIdHigh(Util.lowerHexToUnsignedLong("48485a3953bb6124")).build());
}
@Test
public void ignoreNull_parentId() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"parentId\": null\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void ignoreNull_timestamp() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"timestamp\": null\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void ignoreNull_duration() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"duration\": null\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void ignoreNull_debug() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"debug\": null\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void ignoreNull_annotation_endpoint() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"annotations\": [\n"
+ " {\n"
+ " \"timestamp\": 1461750491274000,\n"
+ " \"value\": \"cs\",\n"
+ " \"endpoint\": null\n"
+ " }\n"
+ " ]\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void ignoreNull_binaryAnnotation_endpoint() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"binaryAnnotations\": [\n"
+ " {\n"
+ " \"key\": \"lc\",\n"
+ " \"value\": \"JDBCSpanStore\",\n"
+ " \"endpoint\": null\n"
+ " }\n"
+ " ]\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void niceErrorOnNull_traceId() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Expected a string but was NULL");
String json = "{\n"
+ " \"traceId\": null,\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\"\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void doesntStackOverflowOnToBufferWriterBug_lessThanBytes() {
thrown.expect(AssertionError.class);
thrown.expectMessage("Bug found using FooWriter to write Foo as json. Wrote 1/2 bytes: a");
class FooWriter implements Buffer.Writer {
@Override public int sizeInBytes(Object value) {
return 2;
}
@Override public void write(Object value, Buffer buffer) {
buffer.writeByte('a');
throw new RuntimeException("buggy");
}
}
class Foo {
@Override
public String toString() {
return new String(JsonCodec.write(new FooWriter(), this), UTF_8);
}
}
new Foo().toString();
}
@Test
public void doesntStackOverflowOnToBufferWriterBug_Overflow() {
thrown.expect(AssertionError.class);
thrown.expectMessage("Bug found using FooWriter to write Foo as json. Wrote 2/2 bytes: ab");
// pretend there was a bug calculating size, ex it calculated incorrectly as to small
class FooWriter implements Buffer.Writer {
@Override public int sizeInBytes(Object value) {
return 2;
}
@Override public void write(Object value, Buffer buffer) {
buffer.writeByte('a').writeByte('b').writeByte('c'); // wrote larger than size!
}
}
class Foo {
@Override
public String toString() {
return new String(JsonCodec.write(new FooWriter(), this), UTF_8);
}
}
new Foo().toString();
}
@Test
public void niceErrorOnNull_id() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Expected a string but was NULL");
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": null\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void binaryAnnotation_long() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"binaryAnnotations\": [\n"
+ " {\n"
+ " \"key\": \"num\",\n"
+ " \"value\": 123456789,\n"
+ " \"type\": \"I64\"\n"
+ " }\n"
+ " ]\n"
+ "}";
Span span = Codec.JSON.readSpan(json.getBytes(UTF_8));
assertThat(span.binaryAnnotations)
.containsExactly(BinaryAnnotation.builder()
.key("num")
.type(BinaryAnnotation.Type.I64)
.value(toBytes(123456789))
.build());
assertThat(Codec.JSON.readSpan(Codec.JSON.writeSpan(span)))
.isEqualTo(span);
}
@Test
public void binaryAnnotation_long_max() {
String json = ("{"
+ " \"traceId\": \"6b221d5bc9e6496c\","
+ " \"id\": \"6b221d5bc9e6496c\","
+ " \"name\": \"get-traces\","
+ " \"binaryAnnotations\": ["
+ " {"
+ " \"key\": \"num\","
+ " \"value\": \"9223372036854775807\","
+ " \"type\": \"I64\""
+ " }"
+ " ]"
+ "}").replaceAll("\\s", "");
Span span = Codec.JSON.readSpan(json.getBytes(UTF_8));
assertThat(new String(Codec.JSON.writeSpan(span), UTF_8))
.isEqualTo(json);
}
@Test
public void binaryAnnotation_double() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"binaryAnnotations\": [\n"
+ " {\n"
+ " \"key\": \"num\",\n"
+ " \"value\": 1.23456789,\n"
+ " \"type\": \"DOUBLE\"\n"
+ " }\n"
+ " ]\n"
+ "}";
Span span = Codec.JSON.readSpan(json.getBytes(UTF_8));
assertThat(span.binaryAnnotations)
.containsExactly(BinaryAnnotation.builder()
.key("num")
.type(BinaryAnnotation.Type.DOUBLE)
.value(toBytes(Double.doubleToRawLongBits(1.23456789)))
.build());
assertThat(Codec.JSON.readSpan(Codec.JSON.writeSpan(span)))
.isEqualTo(span);
}
@Test
public void endpointHighPort() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"binaryAnnotations\": [\n"
+ " {\n"
+ " \"key\": \"foo\",\n"
+ " \"value\": \"bar\",\n"
+ " \"endpoint\": {\n"
+ " \"serviceName\": \"service\",\n"
+ " \"port\": 65535\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ "}";
Span span = Codec.JSON.readSpan(json.getBytes(UTF_8));
assertThat(span.binaryAnnotations)
.containsExactly(BinaryAnnotation.create("foo", "bar",
Endpoint.builder().serviceName("service").port(65535).build()));
assertThat(Codec.JSON.readSpan(Codec.JSON.writeSpan(span)))
.isEqualTo(span);
}
@Test
public void mappedIPv6toIPv4() {
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"binaryAnnotations\": [\n"
+ " {\n"
+ " \"key\": \"foo\",\n"
+ " \"value\": \"bar\",\n"
+ " \"endpoint\": {\n"
+ " \"serviceName\": \"service\",\n"
+ " \"port\": 65535,\n"
+ " \"ipv6\": \"::ffff:192.0.2.128\"\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ "}";
Span span = Codec.JSON.readSpan(json.getBytes(UTF_8));
assertThat(span.binaryAnnotations.get(0).endpoint.ipv6)
.isNull();
assertThat(span.binaryAnnotations.get(0).endpoint.ipv4)
.isEqualTo((192 << 24) | (0 << 16) | (2 << 8) | 128); // 192.0.2.128
}
@Test
public void missingKey() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("No key at $.binaryAnnotations[0]");
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"binaryAnnotations\": [\n"
+ " {\n"
+ " \"value\": \"bar\"\n"
+ " }\n"
+ " ]\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void missingValue() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("No value for key foo at $.binaryAnnotations[0]");
String json = "{\n"
+ " \"traceId\": \"6b221d5bc9e6496c\",\n"
+ " \"name\": \"get-traces\",\n"
+ " \"id\": \"6b221d5bc9e6496c\",\n"
+ " \"binaryAnnotations\": [\n"
+ " {\n"
+ " \"key\": \"foo\"\n"
+ " }\n"
+ " ]\n"
+ "}";
Codec.JSON.readSpan(json.getBytes(UTF_8));
}
@Test
public void sizeInBytes_span() throws IOException {
Span span = TestObjects.LOTS_OF_SPANS[0];
assertThat(JsonCodec.SPAN_ADAPTER.sizeInBytes(span))
.isEqualTo(codec().writeSpan(span).length);
}
@Test
public void sizeInBytes_link() throws IOException {
assertThat(JsonCodec.DEPENDENCY_LINK_ADAPTER.sizeInBytes(TestObjects.LINKS.get(0)))
.isEqualTo(codec().writeDependencyLink(TestObjects.LINKS.get(0)).length);
}
static byte[] toBytes(long v) {
okio.Buffer buffer = new okio.Buffer();
buffer.writeLong(v);
return buffer.readByteArray();
}
}