Java tutorial
/* * Copyright 2013-2015 the original author or 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 integration; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.stream.Collectors; 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.context.SpringBootTest; import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import integration.MessagingApplicationTests.IntegrationSpanCollectorConfig; import sample.SampleMessagingApplication; import tools.AbstractIntegrationTest; import zipkin.Constants; import zipkin.Span; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { IntegrationSpanCollectorConfig.class, SampleMessagingApplication.class }, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @TestPropertySource(properties = { "sample.zipkin.enabled=true" }) @DirtiesContext public class MessagingApplicationTests extends AbstractIntegrationTest { private static int port = 3381; private static String sampleAppUrl = "http://localhost:" + port; @Autowired IntegrationTestZipkinSpanReporter integrationTestSpanCollector; @After public void cleanup() { this.integrationTestSpanCollector.hashedSpans.clear(); } @Test public void should_have_passed_trace_id_when_message_is_about_to_be_sent() { long traceId = new Random().nextLong(); await().atMost(5, SECONDS) .until(httpMessageWithTraceIdInHeadersIsSuccessfullySent(sampleAppUrl + "/", traceId)); await().atMost(5, SECONDS).until(() -> { thenAllSpansHaveTraceIdEqualTo(traceId); }); } @Test public void should_have_passed_trace_id_and_generate_new_span_id_when_message_is_about_to_be_sent() { long traceId = new Random().nextLong(); long spanId = new Random().nextLong(); await().atMost(5, SECONDS) .until(httpMessageWithTraceIdInHeadersIsSuccessfullySent(sampleAppUrl + "/", traceId, spanId)); await().atMost(5, SECONDS).until(() -> { thenAllSpansHaveTraceIdEqualTo(traceId); thenTheSpansHaveProperParentStructure(); }); } @Test public void should_have_passed_trace_id_with_annotations_in_async_thread_when_message_is_about_to_be_sent() { long traceId = new Random().nextLong(); await().atMost(5, SECONDS) .until(httpMessageWithTraceIdInHeadersIsSuccessfullySent(sampleAppUrl + "/xform", traceId)); await().atMost(5, SECONDS).until(() -> { thenAllSpansHaveTraceIdEqualTo(traceId); thenThereIsAtLeastOneBinaryAnnotationWithKey("background-sleep-millis"); }); } private void thenThereIsAtLeastOneBinaryAnnotationWithKey(String binaryAnnotationKey) { then(this.integrationTestSpanCollector.hashedSpans.stream().map(s -> s.binaryAnnotations) .flatMap(Collection::stream).anyMatch(b -> b.key.equals(binaryAnnotationKey))).isTrue(); } private void thenAllSpansHaveTraceIdEqualTo(long traceId) { then(this.integrationTestSpanCollector.hashedSpans.stream().allMatch(span -> span.traceId == traceId)) .describedAs("All spans have same trace id").isTrue(); } private void thenTheSpansHaveProperParentStructure() { Optional<Span> firstHttpSpan = findFirstHttpRequestSpan(); List<Span> eventSpans = findAllEventRelatedSpans(); Optional<Span> eventSentSpan = findSpanWithAnnotation(Constants.SERVER_SEND); Optional<Span> eventReceivedSpan = findSpanWithAnnotation(Constants.CLIENT_RECV); Optional<Span> lastHttpSpansParent = findLastHttpSpansParent(); // "http:/parent/" -> "home" -> "message:messages" -> "http:/foo" (CS + CR) -> "http:/foo" (SS) -> "foo" Collections.sort(this.integrationTestSpanCollector.hashedSpans, (s1, s2) -> s1.timestamp.compareTo(s2.timestamp)); thenAllSpansArePresent(firstHttpSpan, eventSpans, lastHttpSpansParent, eventSentSpan, eventReceivedSpan); then(this.integrationTestSpanCollector.hashedSpans).as("There were 6 spans").hasSize(6); log.info("Checking the parent child structure"); List<Optional<Span>> parentChild = this.integrationTestSpanCollector.hashedSpans.stream() .filter(span -> span.parentId != null).map(span -> this.integrationTestSpanCollector.hashedSpans .stream().filter(span1 -> span1.id == span.parentId).findAny()) .collect(Collectors.toList()); log.info("List of parents and children " + parentChild); then(parentChild.stream().allMatch(Optional::isPresent)).isTrue(); } private Optional<Span> findLastHttpSpansParent() { return this.integrationTestSpanCollector.hashedSpans.stream() .filter(span -> "http:/foo".equals(span.name) && !span.annotations.isEmpty()).findFirst(); } private Optional<Span> findSpanWithAnnotation(String annotationName) { return this.integrationTestSpanCollector.hashedSpans.stream() .filter(span -> span.annotations.stream() .filter(annotation -> annotationName.equals(annotation.value)).findFirst().isPresent()) .findFirst(); } private List<Span> findAllEventRelatedSpans() { return this.integrationTestSpanCollector.hashedSpans.stream() .filter(span -> "message:messages".equals(span.name) && span.parentId != null) .collect(Collectors.toList()); } private Optional<Span> findFirstHttpRequestSpan() { return this.integrationTestSpanCollector.hashedSpans.stream() // home is the name of the method .filter(span -> "home".equals(span.name)).findFirst(); } private void thenAllSpansArePresent(Optional<Span> firstHttpSpan, List<Span> eventSpans, Optional<Span> lastHttpSpan, Optional<Span> eventSentSpan, Optional<Span> eventReceivedSpan) { log.info("Found following spans"); log.info("First http span " + firstHttpSpan); log.info("Event spans " + eventSpans); log.info("Event sent span " + eventSentSpan); log.info("Event received span " + eventReceivedSpan); log.info("Last http span " + lastHttpSpan); log.info("All found spans \n" + this.integrationTestSpanCollector.hashedSpans.stream().map(Span::toString) .collect(Collectors.joining("\n"))); then(firstHttpSpan.isPresent()).isTrue(); then(eventSpans).isNotEmpty(); then(eventSentSpan.isPresent()).isTrue(); then(eventReceivedSpan.isPresent()).isTrue(); then(lastHttpSpan.isPresent()).isTrue(); } @Configuration public static class IntegrationSpanCollectorConfig { @Bean ZipkinSpanReporter integrationTestZipkinSpanReporter() { return new IntegrationTestZipkinSpanReporter(); } } }