/**
* Copyright 2015-2017 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;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import okio.Buffer;
import okio.ByteString;
import org.junit.Test;
import zipkin.internal.Util;
import static org.assertj.core.api.Assertions.assertThat;
import static zipkin.Constants.CLIENT_RECV;
import static zipkin.Constants.CLIENT_SEND;
import static zipkin.Constants.SERVER_RECV;
import static zipkin.Constants.SERVER_SEND;
import static zipkin.TestObjects.APP_ENDPOINT;
public class SpanTest {
@Test
public void traceIdHigh() {
Span with128BitId = Span.builder()
.traceId(Util.lowerHexToUnsignedLong("48485a3953bb6124"))
.traceIdHigh(Util.lowerHexToUnsignedLong("463ac35c9f6413ad"))
.id(1)
.name("foo").build();
assertThat(with128BitId.idString())
.isEqualTo("463ac35c9f6413ad48485a3953bb6124.0000000000000001<:0000000000000001");
}
@Test
public void idString_withParent() {
Span withParent = Span.builder().name("foo").traceId(1).id(3).parentId(2L).build();
assertThat(withParent.idString())
.isEqualTo("0000000000000001.0000000000000003<:0000000000000002");
}
@Test
public void idString_noParent() {
Span noParent = Span.builder().name("foo").traceId(1).id(1).build();
assertThat(noParent.idString())
.isEqualTo("0000000000000001.0000000000000001<:0000000000000001");
}
@Test
public void spanNamesLowercase() {
assertThat(Span.builder().traceId(1L).id(1L).name("GET").build().name)
.isEqualTo("get");
}
@Test
public void mergeWhenBinaryAnnotationsSentSeparately() {
Span part1 = Span.builder()
.traceId(1L)
.name("")
.id(1L)
.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR, APP_ENDPOINT))
.build();
Span part2 = Span.builder()
.traceId(1L)
.name("get")
.id(1L)
.timestamp(1444438900939000L)
.duration(376000L)
.addAnnotation(Annotation.create(1444438900939000L, Constants.SERVER_RECV, APP_ENDPOINT))
.addAnnotation(Annotation.create(1444438901315000L, Constants.SERVER_SEND, APP_ENDPOINT))
.build();
Span expected = part2.toBuilder()
.addBinaryAnnotation(part1.binaryAnnotations.get(0))
.build();
assertThat(part1.toBuilder().merge(part2).build()).isEqualTo(expected);
assertThat(part2.toBuilder().merge(part1).build()).isEqualTo(expected);
}
/**
* Test merging of client and server spans into a single span, with a clock skew. Final timestamp
* and duration for the span should be same as client.
*/
@Test
public void timestampAndDurationMergeWithClockSkew() {
long today = Util.midnightUTC(System.currentTimeMillis());
long clientTimestamp = (today + 100) * 1000;
long clientDuration = 35 * 1000;
long serverTimestamp = (today + 200) * 1000;
long serverDuration = 30 * 1000;
Span clientPart = Span.builder()
.traceId(1L)
.name("test")
.id(1L)
.timestamp(clientTimestamp).duration(clientDuration)
.addAnnotation(Annotation.create(clientTimestamp, CLIENT_SEND, APP_ENDPOINT))
.addAnnotation(Annotation.create(clientTimestamp + clientDuration, CLIENT_RECV, APP_ENDPOINT))
.build();
Span serverPart = Span.builder()
.traceId(1L)
.name("test")
.id(1L)
.timestamp(serverTimestamp).duration(serverDuration)
.addAnnotation(Annotation.create(serverTimestamp, SERVER_RECV, APP_ENDPOINT))
.addAnnotation(Annotation.create(serverTimestamp + serverDuration, SERVER_SEND, APP_ENDPOINT))
.build();
Span completeSpan = clientPart.toBuilder()
.merge(serverPart)
.build();
assertThat(completeSpan.timestamp).isEqualTo(clientTimestamp);
assertThat(completeSpan.duration).isEqualTo(clientDuration);
}
/**
* Some instrumentation set name to "unknown" or empty. This ensures dummy span names lose on
* merge.
*/
@Test
public void mergeOverridesDummySpanNames() {
for (String nonName : Arrays.asList("", "unknown")) {
Span unknown = Span.builder().traceId(1).id(2).name(nonName).build();
Span get = unknown.toBuilder().name("get").build();
assertThat(unknown.toBuilder().merge(get).build().name).isEqualTo("get");
assertThat(get.toBuilder().merge(unknown).build().name).isEqualTo("get");
}
}
@Test
public void mergeTraceIdHigh() {
Span span = Span.builder()
.merge(Span.builder().traceId(1).id(2).name("foo").traceIdHigh(1L).build())
.build();
assertThat(span.name).isEqualTo("foo");
assertThat(span.traceIdHigh).isEqualTo(1L);
}
@Test
public void serviceNames_includeBinaryAnnotations() {
Span span = Span.builder()
.traceId(1L)
.name("GET")
.id(1L)
.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR, APP_ENDPOINT))
.build();
assertThat(span.serviceNames())
.containsOnly(APP_ENDPOINT.serviceName);
}
@Test
public void serviceNames_ignoresAnnotationsWithEmptyServiceNames() {
Span span = Span.builder()
.traceId(12345)
.id(666)
.name("methodcall")
.addAnnotation(Annotation.create(1L, "test", Endpoint.create("", 127 << 24 | 1)))
.addAnnotation(Annotation.create(2L, Constants.SERVER_RECV, APP_ENDPOINT))
.build();
assertThat(span.serviceNames())
.containsOnly(APP_ENDPOINT.serviceName);
}
/** This helps tests not flake out when binary annotations aren't returned in insertion order */
@Test
public void sortsBinaryAnnotationsByKey() {
BinaryAnnotation foo = BinaryAnnotation.create("foo", "bar", APP_ENDPOINT);
BinaryAnnotation baz = BinaryAnnotation.create("baz", "qux", APP_ENDPOINT);
Span span = Span.builder()
.traceId(12345)
.id(666)
.name("methodcall")
.addBinaryAnnotation(foo)
.addBinaryAnnotation(baz)
.build();
assertThat(span.binaryAnnotations)
.containsExactly(baz, foo);
}
/** Catches common error when zero is passed instead of null for a timestamp */
@Test
public void coercesTimestampZeroToNull() {
Span span = Span.builder()
.traceId(1L)
.name("GET")
.id(1L)
.timestamp(0L)
.build();
assertThat(span.timestamp)
.isNull();
}
/**
* Catches common error when zero is passed instead of null for a duration. Durations of less than
* a microsecond must be recorded as 1.
*/
@Test
public void coercesDurationZeroToNull() {
Span span = Span.builder()
.traceId(1L)
.name("GET")
.id(1L)
.duration(0L)
.build();
assertThat(span.duration)
.isNull();
}
@Test
public void serialization() throws Exception {
Span span = TestObjects.TRACE.get(0);
Buffer buffer = new Buffer();
new ObjectOutputStream(buffer.outputStream()).writeObject(span);
assertThat(new ObjectInputStream(buffer.inputStream()).readObject())
.isEqualTo(span);
}
@Test
public void serializationUsesThrift() throws Exception {
Span span = TestObjects.TRACE.get(0);
Buffer buffer = new Buffer();
new ObjectOutputStream(buffer.outputStream()).writeObject(span);
byte[] thrift = Codec.THRIFT.writeSpan(span);
assertThat(buffer.indexOf(ByteString.of(thrift)))
.isPositive();
}
}