opentelemetry-java-instrume.../instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsHttpServerTest...

344 lines
10 KiB
Groovy

/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import spock.lang.Unroll
import java.util.concurrent.TimeUnit
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
import static io.opentelemetry.api.trace.SpanKind.SERVER
import static io.opentelemetry.api.trace.StatusCode.ERROR
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
import static java.util.concurrent.TimeUnit.SECONDS
import static org.junit.jupiter.api.Assumptions.assumeTrue
abstract class AbstractJaxRsHttpServerTest<S> extends HttpServerTest<S> implements AgentTestTrait {
abstract void awaitBarrier(int amount, TimeUnit timeUnit)
def "test super method without @Path"() {
given:
def response = client.get(address.resolve("test-resource-super").toString()).aggregate().join()
expect:
response.status().code() == SUCCESS.status
response.contentUtf8() == SUCCESS.body
assertTraces(1) {
trace(0, 2) {
span(0) {
hasNoParent()
kind SERVER
name "GET " + getContextPath() + "/test-resource-super"
}
span(1) {
name "controller"
kind INTERNAL
childOf span(0)
}
}
}
}
def "test interface method with @Path"() {
assumeTrue(testInterfaceMethodWithPath())
given:
def response = client.get(address.resolve("test-resource-interface/call").toString()).aggregate().join()
expect:
response.status().code() == SUCCESS.status
response.contentUtf8() == SUCCESS.body
assertTraces(1) {
trace(0, 2) {
span(0) {
hasNoParent()
kind SERVER
name "GET " + getContextPath() + "/test-resource-interface/call"
}
span(1) {
name "controller"
kind INTERNAL
childOf span(0)
}
}
}
}
def "test sub resource locator"() {
given:
def response = client.get(address.resolve("test-sub-resource-locator/call/sub").toString()).aggregate().join()
expect:
response.status().code() == SUCCESS.status
response.contentUtf8() == SUCCESS.body
assertTraces(1) {
trace(0, 5) {
span(0) {
hasNoParent()
kind SERVER
name "GET " + getContextPath() + "/test-sub-resource-locator/call/sub"
}
span(1) {
name "JaxRsSubResourceLocatorTestResource.call"
kind INTERNAL
childOf span(0)
}
span(2) {
name "controller"
kind INTERNAL
childOf span(1)
}
span(3) {
name "SubResource.call"
kind INTERNAL
childOf span(0)
}
span(4) {
name "controller"
kind INTERNAL
childOf span(3)
}
}
}
}
@Unroll
def "should handle #desc AsyncResponse"() {
given:
def url = address.resolve("async?action=${action}").toString()
when: "async call is started"
def futureResponse = client.get(url).aggregate()
then: "there are no traces yet"
assertTraces(0) {
}
when: "barrier is released and resource class sends response"
awaitBarrier(1, SECONDS)
def response = futureResponse.join()
then:
response.status().code() == statusCode
bodyPredicate(response.contentUtf8())
def spanCount = 2
def hasSendError = asyncCancelHasSendError() && action == "cancel"
if (hasSendError) {
spanCount++
}
assertTraces(1) {
trace(0, spanCount) {
asyncServerSpan(it, 0, url, statusCode)
handlerSpan(it, 1, span(0), "asyncOp", isCancelled, isError, errorMessage)
if (hasSendError) {
sendErrorSpan(it, 2, span(1))
}
}
}
where:
desc | action | statusCode | bodyPredicate | isCancelled | isError | errorMessage
"successful" | "succeed" | 200 | { it == "success" } | false | false | null
"failing" | "throw" | 500 | { it == "failure" } | false | true | "failure"
"canceled" | "cancel" | 503 | { it instanceof String } | true | false | null
}
@Unroll
def "should handle #desc CompletionStage (JAX-RS 2.1+ only)"() {
assumeTrue(shouldTestCompletableStageAsync())
given:
def url = address.resolve("async-completion-stage?action=${action}").toString()
when: "async call is started"
def futureResponse = client.get(url).aggregate()
then: "there are no traces yet"
assertTraces(0) {
}
when: "barrier is released and resource class sends response"
awaitBarrier(1, SECONDS)
def response = futureResponse.join()
then:
response.status().code() == statusCode
bodyPredicate(response.contentUtf8())
assertTraces(1) {
trace(0, 2) {
asyncServerSpan(it, 0, url, statusCode)
handlerSpan(it, 1, span(0), "jaxRs21Async", false, isError, errorMessage)
}
}
where:
desc | action | statusCode | bodyPredicate | isError | errorMessage
"successful" | "succeed" | 200 | { it == "success" } | false | null
"failing" | "throw" | 500 | { it == "failure" } | true | "failure"
}
@Override
boolean hasHandlerSpan(ServerEndpoint endpoint) {
true
}
@Override
boolean testNotFound() {
false
}
@Override
boolean testPathParam() {
true
}
boolean testInterfaceMethodWithPath() {
true
}
boolean asyncCancelHasSendError() {
false
}
boolean shouldTestCompletableStageAsync() {
Boolean.getBoolean("testLatestDeps")
}
@Override
boolean hasResponseCustomizer(ServerEndpoint endpoint) {
true
}
@Override
void serverSpan(TraceAssert trace,
int index,
String traceID = null,
String parentID = null,
String method = "GET",
Long responseContentLength = null,
ServerEndpoint endpoint = SUCCESS,
String spanID = null) {
serverSpan(trace, index, traceID, parentID, spanID, method,
endpoint == PATH_PARAM ? getContextPath() + "/path/{id}/param" : endpoint.resolvePath(address).path,
endpoint.resolve(address),
endpoint.status,
endpoint.query)
}
void asyncServerSpan(TraceAssert trace,
int index,
String url,
int statusCode) {
def rawUrl = URI.create(url).toURL()
serverSpan(trace, index, null, null, null, "GET",
rawUrl.path,
rawUrl.toURI(),
statusCode,
null)
}
void serverSpan(TraceAssert trace,
int index,
String traceID,
String parentID,
String spanID,
String method,
String path,
URI fullUrl,
int statusCode,
String query) {
trace.span(index) {
name method + " " + path
kind SERVER
if (statusCode >= 500) {
status ERROR
}
if (traceID != null) {
traceId traceID
}
if (parentID != null) {
parentSpanId parentID
} else {
hasNoParent()
}
if (spanID != null) {
spanId spanID
}
attributes {
"net.protocol.name" "http"
"net.protocol.version" "1.1"
"$SemanticAttributes.NET_HOST_NAME" fullUrl.host
"$SemanticAttributes.NET_HOST_PORT" fullUrl.port
"$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1"
"$SemanticAttributes.NET_SOCK_PEER_PORT" Long
"$SemanticAttributes.NET_SOCK_HOST_ADDR" "127.0.0.1"
"$SemanticAttributes.HTTP_SCHEME" fullUrl.getScheme()
"$SemanticAttributes.HTTP_TARGET" fullUrl.getPath() + (fullUrl.getQuery() != null ? "?" + fullUrl.getQuery() : "")
"$SemanticAttributes.HTTP_METHOD" method
"$SemanticAttributes.HTTP_STATUS_CODE" statusCode
"$SemanticAttributes.USER_AGENT_ORIGINAL" TEST_USER_AGENT
"$SemanticAttributes.HTTP_CLIENT_IP" TEST_CLIENT_IP
// Optional
"$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long }
"$SemanticAttributes.HTTP_ROUTE" path
if (fullUrl.getPath().endsWith(ServerEndpoint.CAPTURE_HEADERS.getPath())) {
"http.request.header.x_test_request" { it == ["test"] }
"http.response.header.x_test_response" { it == ["test"] }
}
}
}
}
@Override
void handlerSpan(TraceAssert trace,
int index,
Object parent,
String method = "GET",
ServerEndpoint endpoint = SUCCESS) {
handlerSpan(trace, index, parent,
endpoint.name().toLowerCase(),
false,
endpoint == EXCEPTION,
EXCEPTION.body)
}
void handlerSpan(TraceAssert trace,
int index,
Object parent,
String methodName,
boolean isCancelled,
boolean isError,
String exceptionMessage = null) {
trace.span(index) {
name "JaxRsTestResource.${methodName}"
kind INTERNAL
if (isError) {
status ERROR
errorEvent(Exception, exceptionMessage)
}
childOf((SpanData) parent)
attributes {
"$SemanticAttributes.CODE_NAMESPACE" "test.JaxRsTestResource"
"$SemanticAttributes.CODE_FUNCTION" methodName
if (isCancelled) {
"jaxrs.canceled" true
}
}
}
}
}