001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.client.trace.hamcrest;
019
020import static org.apache.hadoop.hbase.client.trace.hamcrest.AttributesMatchers.containsEntry;
021import static org.hamcrest.Matchers.equalTo;
022import static org.hamcrest.Matchers.is;
023
024import io.opentelemetry.api.common.Attributes;
025import io.opentelemetry.api.trace.SpanKind;
026import io.opentelemetry.api.trace.StatusCode;
027import io.opentelemetry.sdk.trace.data.EventData;
028import io.opentelemetry.sdk.trace.data.SpanData;
029import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
030import java.time.Duration;
031import java.util.Objects;
032import org.hamcrest.Description;
033import org.hamcrest.FeatureMatcher;
034import org.hamcrest.Matcher;
035import org.hamcrest.TypeSafeMatcher;
036
037/**
038 * Helper methods for matching against instances of {@link SpanData}.
039 */
040public final class SpanDataMatchers {
041
042  private SpanDataMatchers() {
043  }
044
045  public static Matcher<SpanData> hasAttributes(Matcher<Attributes> matcher) {
046    return new FeatureMatcher<SpanData, Attributes>(matcher, "SpanData having attributes that ",
047      "attributes") {
048      @Override
049      protected Attributes featureValueOf(SpanData item) {
050        return item.getAttributes();
051      }
052    };
053  }
054
055  public static Matcher<SpanData> hasDuration(Matcher<Duration> matcher) {
056    return new FeatureMatcher<SpanData, Duration>(matcher, "SpanData having duration that ",
057      "duration") {
058      @Override
059      protected Duration featureValueOf(SpanData item) {
060        return Duration.ofNanos(item.getEndEpochNanos() - item.getStartEpochNanos());
061      }
062    };
063  }
064
065  public static Matcher<SpanData> hasEnded() {
066    return new TypeSafeMatcher<SpanData>() {
067      @Override
068      protected boolean matchesSafely(SpanData item) {
069        return item.hasEnded();
070      }
071
072      @Override
073      public void describeTo(Description description) {
074        description.appendText("SpanData that hasEnded");
075      }
076    };
077  }
078
079  public static Matcher<SpanData> hasEvents(Matcher<Iterable<? super EventData>> matcher) {
080    return new FeatureMatcher<SpanData, Iterable<? super EventData>>(matcher,
081      "SpanData having events that", "events") {
082      @Override
083      protected Iterable<? super EventData> featureValueOf(SpanData item) {
084        return item.getEvents();
085      }
086    };
087  }
088
089  public static Matcher<SpanData> hasExceptionWithType(Matcher<? super String> matcher) {
090    return hasException(containsEntry(is(SemanticAttributes.EXCEPTION_TYPE), matcher));
091  }
092
093  public static Matcher<SpanData> hasException(Matcher<? super Attributes> matcher) {
094    return new FeatureMatcher<SpanData, Attributes>(matcher,
095      "SpanData having Exception with Attributes that", "exception attributes") {
096      @Override
097      protected Attributes featureValueOf(SpanData actual) {
098        return actual.getEvents().stream()
099          .filter(e -> Objects.equals(SemanticAttributes.EXCEPTION_EVENT_NAME, e.getName()))
100          .map(EventData::getAttributes).findFirst().orElse(null);
101      }
102    };
103  }
104
105  public static Matcher<SpanData> hasKind(SpanKind kind) {
106    return new FeatureMatcher<SpanData, SpanKind>(equalTo(kind), "SpanData with kind that",
107      "SpanKind") {
108      @Override
109      protected SpanKind featureValueOf(SpanData item) {
110        return item.getKind();
111      }
112    };
113  }
114
115  public static Matcher<SpanData> hasName(String name) {
116    return hasName(equalTo(name));
117  }
118
119  public static Matcher<SpanData> hasName(Matcher<String> matcher) {
120    return new FeatureMatcher<SpanData, String>(matcher, "SpanKind with a name that", "name") {
121      @Override
122      protected String featureValueOf(SpanData item) {
123        return item.getName();
124      }
125    };
126  }
127
128  public static Matcher<SpanData> hasParentSpanId(String parentSpanId) {
129    return hasParentSpanId(equalTo(parentSpanId));
130  }
131
132  public static Matcher<SpanData> hasParentSpanId(SpanData parent) {
133    return hasParentSpanId(parent.getSpanId());
134  }
135
136  public static Matcher<SpanData> hasParentSpanId(Matcher<String> matcher) {
137    return new FeatureMatcher<SpanData, String>(matcher, "SpanKind with a parentSpanId that",
138      "parentSpanId") {
139      @Override
140      protected String featureValueOf(SpanData item) {
141        return item.getParentSpanId();
142      }
143    };
144  }
145
146  public static Matcher<SpanData> hasStatusWithCode(Matcher<StatusCode> matcher) {
147    return new FeatureMatcher<SpanData, StatusCode>(matcher, "SpanData with StatusCode that",
148      "statusWithCode") {
149      @Override
150      protected StatusCode featureValueOf(SpanData actual) {
151        return actual.getStatus().getStatusCode();
152      }
153    };
154  }
155
156  public static Matcher<SpanData> hasStatusWithCode(StatusCode statusCode) {
157    return hasStatusWithCode(is(equalTo(statusCode)));
158  }
159
160  public static Matcher<SpanData> hasTraceId(String traceId) {
161    return hasTraceId(is(equalTo(traceId)));
162  }
163
164  public static Matcher<SpanData> hasTraceId(Matcher<String> matcher) {
165    return new FeatureMatcher<SpanData, String>(matcher, "SpanData with a traceId that ",
166      "traceId") {
167      @Override
168      protected String featureValueOf(SpanData item) {
169        return item.getTraceId();
170      }
171    };
172  }
173}