diff --git a/instrumentation/http-url-connection/javaagent/src/test/groovy/HttpUrlConnectionResponseCodeOnlyTest.groovy b/instrumentation/http-url-connection/javaagent/src/test/groovy/HttpUrlConnectionResponseCodeOnlyTest.groovy deleted file mode 100644 index 3f2ecc60df46..000000000000 --- a/instrumentation/http-url-connection/javaagent/src/test/groovy/HttpUrlConnectionResponseCodeOnlyTest.groovy +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpClientTest - -class HttpUrlConnectionResponseCodeOnlyTest extends HttpClientTest implements AgentTestTrait { - - @Override - HttpURLConnection buildRequest(String method, URI uri, Map headers) { - return uri.toURL().openConnection() as HttpURLConnection - } - - @Override - int sendRequest(HttpURLConnection connection, String method, URI uri, Map headers) { - try { - connection.setRequestMethod(method) - connection.connectTimeout = CONNECT_TIMEOUT_MS - if (uri.toString().contains("/read-timeout")) { - connection.readTimeout = READ_TIMEOUT_MS - } - headers.each { connection.setRequestProperty(it.key, it.value) } - connection.setRequestProperty("Connection", "close") - return connection.getResponseCode() - } finally { - connection.disconnect() - } - } - - @Override - int maxRedirects() { - 20 - } - - @Override - Integer responseCodeOnRedirectError() { - return 302 - } - - @Override - boolean testReusedRequest() { - // HttpURLConnection can't be reused - return false - } - - @Override - boolean testCallback() { - return false - } - - @Override - boolean testReadTimeout() { - true - } -} diff --git a/instrumentation/http-url-connection/javaagent/src/test/groovy/HttpUrlConnectionTest.groovy b/instrumentation/http-url-connection/javaagent/src/test/groovy/HttpUrlConnectionTest.groovy deleted file mode 100644 index 0b518a08d61c..000000000000 --- a/instrumentation/http-url-connection/javaagent/src/test/groovy/HttpUrlConnectionTest.groovy +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.trace.Span -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpClientTest -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import spock.lang.Unroll - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.SERVER -import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NetTransportValues.IP_TCP - -class HttpUrlConnectionTest extends HttpClientTest implements AgentTestTrait { - - static final RESPONSE = "Hello." - static final STATUS = 200 - - @Override - HttpURLConnection buildRequest(String method, URI uri, Map headers) { - return uri.toURL().openConnection() as HttpURLConnection - } - - @Override - int sendRequest(HttpURLConnection connection, String method, URI uri, Map headers) { - if (uri.toString().contains("/read-timeout")) { - connection.readTimeout = READ_TIMEOUT_MS - } - try { - connection.setRequestMethod(method) - headers.each { connection.setRequestProperty(it.key, it.value) } - connection.setRequestProperty("Connection", "close") - connection.useCaches = true - connection.connectTimeout = CONNECT_TIMEOUT_MS - def parentSpan = Span.current() - def stream = connection.inputStream - assert Span.current() == parentSpan - stream.readLines() - stream.close() - return connection.getResponseCode() - } finally { - connection.disconnect() - } - } - - @Override - int maxRedirects() { - 20 - } - - @Override - Integer responseCodeOnRedirectError() { - return 302 - } - - @Override - boolean testReusedRequest() { - // HttpURLConnection can't be reused - return false - } - - @Override - boolean testCallback() { - return false - } - - @Override - boolean testReadTimeout() { - true - } - - @Unroll - def "trace request (useCaches: #useCaches)"() { - setup: - def url = resolveAddress("/success").toURL() - runWithSpan("someTrace") { - HttpURLConnection connection = url.openConnection() - connection.useCaches = useCaches - assert Span.current().getSpanContext().isValid() - def stream = connection.inputStream - def lines = stream.readLines() - stream.close() - assert connection.getResponseCode() == STATUS - assert lines == [RESPONSE] - - // call again to ensure the cycling is ok - connection = url.openConnection() - connection.useCaches = useCaches - assert Span.current().getSpanContext().isValid() - // call before input stream to test alternate behavior - assert connection.getResponseCode() == STATUS - connection.inputStream - stream = connection.inputStream // one more to ensure state is working - lines = stream.readLines() - stream.close() - assert lines == [RESPONSE] - } - - expect: - assertTraces(1) { - trace(0, 5) { - span(0) { - name "someTrace" - hasNoParent() - attributes { - } - } - span(1) { - name "HTTP GET" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.NET_TRANSPORT" IP_TCP - "$SemanticAttributes.NET_PEER_NAME" "localhost" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" "$url" - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_STATUS_CODE" STATUS - "$SemanticAttributes.HTTP_FLAVOR" "1.1" - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long - } - } - span(2) { - name "test-http-server" - kind SERVER - childOf span(1) - attributes { - } - } - span(3) { - name "HTTP GET" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.NET_TRANSPORT" IP_TCP - "$SemanticAttributes.NET_PEER_NAME" "localhost" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" "$url" - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_STATUS_CODE" STATUS - "$SemanticAttributes.HTTP_FLAVOR" "1.1" - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long - } - } - span(4) { - name "test-http-server" - kind SERVER - childOf span(3) - attributes { - } - } - } - } - - where: - useCaches << [false, true] - } - - def "test broken API usage (#iteration)"() { - setup: - def url = resolveAddress("/success").toURL() - HttpURLConnection connection = runWithSpan("someTrace") { - HttpURLConnection con = url.openConnection() - con.setRequestProperty("Connection", "close") - assert Span.current().getSpanContext().isValid() - assert con.getResponseCode() == STATUS - return con - } - - expect: - assertTraces(1) { - trace(0, 3) { - span(0) { - name "someTrace" - hasNoParent() - attributes { - } - } - span(1) { - name "HTTP GET" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.NET_PEER_NAME" "localhost" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.NET_TRANSPORT" IP_TCP - "$SemanticAttributes.HTTP_URL" "$url" - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_STATUS_CODE" STATUS - "$SemanticAttributes.HTTP_FLAVOR" "1.1" - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long - } - } - serverSpan(it, 2, span(1)) - } - } - - cleanup: - connection.disconnect() - - where: - iteration << (1..10) - } - - def "test post request"() { - setup: - def url = resolveAddress("/success").toURL() - runWithSpan("someTrace") { - HttpURLConnection connection = url.openConnection() - connection.setRequestMethod("POST") - - String urlParameters = "q=ASDF&w=&e=&r=12345&t=" - - // Send post request - connection.setDoOutput(true) - DataOutputStream wr = new DataOutputStream(connection.getOutputStream()) - wr.writeBytes(urlParameters) - wr.flush() - wr.close() - - assert connection.getResponseCode() == STATUS - - def stream = connection.inputStream - def lines = stream.readLines() - stream.close() - assert lines == [RESPONSE] - } - - expect: - assertTraces(1) { - trace(0, 3) { - span(0) { - name "someTrace" - hasNoParent() - attributes { - } - } - span(1) { - name "HTTP POST" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.NET_TRANSPORT" IP_TCP - "$SemanticAttributes.NET_PEER_NAME" "localhost" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" "$url" - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_STATUS_CODE" STATUS - "$SemanticAttributes.HTTP_FLAVOR" "1.1" - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" Long - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long - } - } - span(2) { - name "test-http-server" - kind SERVER - childOf span(1) - attributes { - } - } - } - } - } - - def "sun.net.www.protocol.http.HttpURLConnection.getOutputStream should transform GET into POST"() { - setup: - def url = resolveAddress("/success").toURL() - runWithSpan("someTrace") { - - HttpURLConnection connection = url.openConnection() - - def connectionClassName = connection.getClass().getName() - - assert "sun.net.www.protocol.http.HttpURLConnection".equals(connectionClassName) - - connection.setRequestMethod("GET") - - String urlParameters = "q=ASDF&w=&e=&r=12345&t=" - - // Send POST request - connection.setDoOutput(true) - DataOutputStream wr = new DataOutputStream(connection.getOutputStream()) - wr.writeBytes(urlParameters) - wr.flush() - wr.close() - - assert connection.getResponseCode() == STATUS - - def stream = connection.inputStream - def lines = stream.readLines() - stream.close() - assert lines == [RESPONSE] - } - - expect: - assertTraces(1) { - trace(0, 3) { - span(0) { - name "someTrace" - hasNoParent() - attributes { - } - } - span(1) { - name "HTTP POST" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.NET_TRANSPORT" IP_TCP - "$SemanticAttributes.NET_PEER_NAME" "localhost" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" "$url" - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_STATUS_CODE" STATUS - "$SemanticAttributes.HTTP_FLAVOR" "1.1" - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" Long - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long - } - } - span(2) { - name "test-http-server" - kind SERVER - childOf span(1) - attributes { - } - } - } - } - } - -} diff --git a/instrumentation/http-url-connection/javaagent/src/test/groovy/HttpUrlConnectionUseCachesFalseTest.groovy b/instrumentation/http-url-connection/javaagent/src/test/groovy/HttpUrlConnectionUseCachesFalseTest.groovy deleted file mode 100644 index 71fea8f80bbc..000000000000 --- a/instrumentation/http-url-connection/javaagent/src/test/groovy/HttpUrlConnectionUseCachesFalseTest.groovy +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.trace.Span -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpClientTest - -class HttpUrlConnectionUseCachesFalseTest extends HttpClientTest implements AgentTestTrait { - - @Override - HttpURLConnection buildRequest(String method, URI uri, Map headers) { - return uri.toURL().openConnection() as HttpURLConnection - } - - @Override - int sendRequest(HttpURLConnection connection, String method, URI uri, Map headers) { - try { - connection.setRequestMethod(method) - headers.each { connection.setRequestProperty(it.key, it.value) } - connection.setRequestProperty("Connection", "close") - connection.useCaches = false - connection.connectTimeout = CONNECT_TIMEOUT_MS - if (uri.toString().contains("/read-timeout")) { - connection.readTimeout = READ_TIMEOUT_MS - } - def parentSpan = Span.current() - def stream = connection.inputStream - assert Span.current() == parentSpan - stream.readLines() - stream.close() - return connection.getResponseCode() - } finally { - connection.disconnect() - } - } - - @Override - int maxRedirects() { - 20 - } - - @Override - Integer responseCodeOnRedirectError() { - return 302 - } - - @Override - boolean testReusedRequest() { - // HttpURLConnection can't be reused - return false - } - - @Override - boolean testCallback() { - return false - } - - @Override - boolean testReadTimeout() { - true - } -} diff --git a/instrumentation/http-url-connection/javaagent/src/test/groovy/UrlConnectionTest.groovy b/instrumentation/http-url-connection/javaagent/src/test/groovy/UrlConnectionTest.groovy deleted file mode 100644 index f2c7bcda7524..000000000000 --- a/instrumentation/http-url-connection/javaagent/src/test/groovy/UrlConnectionTest.groovy +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.trace.Span -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.StatusCode.ERROR -import static io.opentelemetry.instrumentation.test.utils.PortUtils.UNUSABLE_PORT -import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NetTransportValues.IP_TCP - -class UrlConnectionTest extends AgentInstrumentationSpecification { - - def "trace request with connection failure #scheme"() { - when: - runWithSpan("someTrace") { - URLConnection connection = url.openConnection() - connection.setConnectTimeout(10000) - connection.setReadTimeout(10000) - assert Span.current() != null - connection.inputStream - } - - then: - thrown ConnectException - - expect: - assertTraces(1) { - trace(0, 2) { - span(0) { - name "someTrace" - hasNoParent() - status ERROR - errorEvent ConnectException, String - } - span(1) { - name "HTTP GET" - kind CLIENT - childOf span(0) - status ERROR - errorEvent ConnectException, String - attributes { - "$SemanticAttributes.NET_TRANSPORT" IP_TCP - "$SemanticAttributes.NET_PEER_NAME" "localhost" - "$SemanticAttributes.NET_PEER_PORT" UNUSABLE_PORT - "$SemanticAttributes.HTTP_URL" "$url" - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_FLAVOR" "1.1" - } - } - } - } - - where: - scheme << ["http", "https"] - - url = new URI("$scheme://localhost:$UNUSABLE_PORT").toURL() - } -} diff --git a/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionResponseCodeOnlyTest.java b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionResponseCodeOnlyTest.java new file mode 100644 index 000000000000..13e6ef3e5842 --- /dev/null +++ b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionResponseCodeOnlyTest.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.httpurlconnection; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.Map; +import org.junit.jupiter.api.extension.RegisterExtension; + +class HttpUrlConnectionResponseCodeOnlyTest extends AbstractHttpClientTest { + @RegisterExtension + static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent(); + + @Override + public HttpURLConnection buildRequest(String method, URI uri, Map headers) + throws Exception { + return (HttpURLConnection) uri.toURL().openConnection(); + } + + @Override + public int sendRequest( + HttpURLConnection connection, String method, URI uri, Map headers) + throws Exception { + try { + connection.setRequestMethod(method); + connection.setConnectTimeout((int) CONNECTION_TIMEOUT.toMillis()); + if (uri.toString().contains("/read-timeout")) { + connection.setReadTimeout((int) READ_TIMEOUT.toMillis()); + } + + headers.forEach(connection::setRequestProperty); + connection.setRequestProperty("Connection", "close"); + return connection.getResponseCode(); + } finally { + connection.disconnect(); + } + } + + @Override + public int maxRedirects() { + return 20; + } + + @Override + public Integer responseCodeOnRedirectError() { + return 302; + } + + @Override + public boolean testReusedRequest() { + // HttpURLConnection can't be reused + return false; + } + + @Override + public boolean testCallback() { + return false; + } + + @Override + public boolean testReadTimeout() { + return true; + } +} diff --git a/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionTest.java b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionTest.java new file mode 100644 index 000000000000..bacbda1dabdb --- /dev/null +++ b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionTest.java @@ -0,0 +1,316 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.httpurlconnection; + +import static io.opentelemetry.api.trace.SpanKind.CLIENT; +import static io.opentelemetry.api.trace.SpanKind.SERVER; +import static io.opentelemetry.javaagent.instrumentation.httpurlconnection.StreamUtils.readLines; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NetTransportValues.IP_TCP; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.assertj.core.api.AbstractLongAssert; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class HttpUrlConnectionTest extends AbstractHttpClientTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent(); + + static final List RESPONSE = Collections.singletonList("Hello."); + static final int STATUS = 200; + + @Override + public HttpURLConnection buildRequest(String method, URI uri, Map headers) + throws Exception { + return (HttpURLConnection) uri.toURL().openConnection(); + } + + @Override + public int sendRequest( + HttpURLConnection connection, String method, URI uri, Map headers) + throws Exception { + if (uri.toString().contains("/read-timeout")) { + connection.setReadTimeout((int) READ_TIMEOUT.toMillis()); + } + try { + connection.setRequestMethod(method); + headers.forEach(connection::setRequestProperty); + connection.setRequestProperty("Connection", "close"); + connection.setUseCaches(true); + connection.setConnectTimeout((int) CONNECTION_TIMEOUT.toMillis()); + Span parentSpan = Span.current(); + InputStream stream = connection.getInputStream(); + assertThat(Span.current()).isEqualTo(parentSpan); + stream.close(); + return connection.getResponseCode(); + } finally { + connection.disconnect(); + } + } + + @Override + public int maxRedirects() { + return 20; + } + + @Override + public Integer responseCodeOnRedirectError() { + return 302; + } + + @Override + public boolean testReusedRequest() { + // HttpURLConnection can't be reused + return false; + } + + @Override + public boolean testCallback() { + return false; + } + + @Override + public boolean testReadTimeout() { + return true; + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void traceRequest(boolean useCache) throws IOException { + URL url = resolveAddress("/success").toURL(); + + testing.runWithSpan( + "someTrace", + () -> { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setUseCaches(useCache); + assertThat(Span.current().getSpanContext().isValid()).isTrue(); + InputStream stream = connection.getInputStream(); + List lines = readLines(stream); + stream.close(); + assertThat(connection.getResponseCode()).isEqualTo(STATUS); + assertThat(lines).isEqualTo(RESPONSE); + + // call again to ensure the cycling is ok + connection = (HttpURLConnection) url.openConnection(); + connection.setUseCaches(useCache); + assertThat(Span.current().getSpanContext().isValid()).isTrue(); + // call before input stream to test alternate behavior + assertThat(connection.getResponseCode()).isEqualTo(STATUS); + connection.getInputStream(); + stream = connection.getInputStream(); // one more to ensure state is working + lines = readLines(stream); + stream.close(); + assertThat(lines).isEqualTo(RESPONSE); + }); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("someTrace").hasNoParent(), + span -> + span.hasName("HTTP GET") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_TRANSPORT, IP_TCP), + equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), + equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()), + equalTo(SemanticAttributes.HTTP_URL, url.toString()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS), + equalTo(SemanticAttributes.HTTP_FLAVOR, "1.1"), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isNotNegative)), + span -> + span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(1)), + span -> + span.hasName("HTTP GET") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_TRANSPORT, IP_TCP), + equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), + equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()), + equalTo(SemanticAttributes.HTTP_URL, url.toString()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS), + equalTo(SemanticAttributes.HTTP_FLAVOR, "1.1"), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isNotNegative)), + span -> + span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(3)))); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + public void testBrokenApiUsage() throws IOException { + URL url = resolveAddress("/success").toURL(); + HttpURLConnection connection = + testing.runWithSpan( + "someTrace", + () -> { + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestProperty("Connection", "close"); + assertThat(Span.current().getSpanContext().isValid()).isTrue(); + assertThat(con.getResponseCode()).isEqualTo(STATUS); + return con; + }); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("someTrace").hasNoParent(), + span -> + span.hasName("HTTP GET") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_TRANSPORT, IP_TCP), + equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), + equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()), + equalTo(SemanticAttributes.HTTP_URL, url.toString()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS), + equalTo(SemanticAttributes.HTTP_FLAVOR, "1.1"), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isNotNegative)), + span -> + span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(1)))); + + connection.disconnect(); + } + + @Test + public void testPostRequest() throws IOException { + URL url = resolveAddress("/success").toURL(); + testing.runWithSpan( + "someTrace", + () -> { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + + String urlParameters = "q=ASDF&w=&e=&r=12345&t="; + + // Send post request + connection.setDoOutput(true); + DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + wr.writeBytes(urlParameters); + wr.flush(); + wr.close(); + + assertThat(connection.getResponseCode()).isEqualTo(STATUS); + + InputStream stream = connection.getInputStream(); + List lines = readLines(stream); + stream.close(); + assertThat(lines).isEqualTo(RESPONSE); + }); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("someTrace").hasNoParent(), + span -> + span.hasName("HTTP POST") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_TRANSPORT, IP_TCP), + equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), + equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()), + equalTo(SemanticAttributes.HTTP_URL, url.toString()), + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS), + equalTo(SemanticAttributes.HTTP_FLAVOR, "1.1"), + satisfies( + SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, + AbstractLongAssert::isNotNegative), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isNotNegative)), + span -> + span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(1)))); + } + + @Test + public void getOutputStreamShouldTransformGetIntoPost() throws IOException { + URL url = resolveAddress("/success").toURL(); + testing.runWithSpan( + "someTrace", + () -> { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + assertThat(connection.getClass().getName()) + .isEqualTo("sun.net.www.protocol.http.HttpURLConnection"); + + connection.setRequestMethod("GET"); + + String urlParameters = "q=ASDF&w=&e=&r=12345&t="; + + // Send POST request + connection.setDoOutput(true); + DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + wr.writeBytes(urlParameters); + wr.flush(); + wr.close(); + + assertThat(connection.getResponseCode()).isEqualTo(STATUS); + + InputStream stream = connection.getInputStream(); + List lines = readLines(stream); + stream.close(); + assertThat(lines).isEqualTo(RESPONSE); + }); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("someTrace").hasNoParent(), + span -> + span.hasName("HTTP POST") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_TRANSPORT, IP_TCP), + equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), + equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()), + equalTo(SemanticAttributes.HTTP_URL, url.toString()), + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS), + equalTo(SemanticAttributes.HTTP_FLAVOR, "1.1"), + satisfies( + SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, + AbstractLongAssert::isNotNegative), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isNotNegative)), + span -> + span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(1)))); + } +} diff --git a/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionUseCachesFalseTest.java b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionUseCachesFalseTest.java new file mode 100644 index 000000000000..c922459eece9 --- /dev/null +++ b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionUseCachesFalseTest.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.httpurlconnection; + +import static io.opentelemetry.javaagent.instrumentation.httpurlconnection.StreamUtils.readLines; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.Map; +import org.junit.jupiter.api.extension.RegisterExtension; + +class HttpUrlConnectionUseCachesFalseTest extends AbstractHttpClientTest { + @RegisterExtension + static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent(); + + @Override + public HttpURLConnection buildRequest(String method, URI uri, Map headers) + throws Exception { + return (HttpURLConnection) uri.toURL().openConnection(); + } + + @Override + public int sendRequest( + HttpURLConnection connection, String method, URI uri, Map headers) + throws Exception { + try { + connection.setRequestMethod(method); + headers.forEach(connection::setRequestProperty); + connection.setRequestProperty("Connection", "close"); + connection.setUseCaches(false); + connection.setConnectTimeout((int) CONNECTION_TIMEOUT.toMillis()); + if (uri.toString().contains("/read-timeout")) { + connection.setReadTimeout((int) READ_TIMEOUT.toMillis()); + } + Span parentSpan = Span.current(); + InputStream stream = connection.getInputStream(); + assertThat(Span.current()).isEqualTo(parentSpan); + readLines(stream); + stream.close(); + return connection.getResponseCode(); + } finally { + connection.disconnect(); + } + } + + @Override + public int maxRedirects() { + return 20; + } + + @Override + public Integer responseCodeOnRedirectError() { + return 302; + } + + @Override + public boolean testReusedRequest() { + // HttpURLConnection can't be reused + return false; + } + + @Override + public boolean testCallback() { + return false; + } + + @Override + public boolean testReadTimeout() { + return true; + } +} diff --git a/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/StreamUtils.java b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/StreamUtils.java new file mode 100644 index 000000000000..1af8d4212936 --- /dev/null +++ b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/StreamUtils.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.httpurlconnection; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.Strings; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +final class StreamUtils { + static List readLines(InputStream stream) throws IOException { + List lines = new ArrayList<>(); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, UTF_8)); + while (reader.ready()) { + String line = reader.readLine(); + if (!Strings.isNullOrEmpty(line)) { + lines.add(line); + } + } + + return lines; + } + + private StreamUtils() {} +} diff --git a/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/UrlConnectionTest.java b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/UrlConnectionTest.java new file mode 100644 index 000000000000..f3e7de236a06 --- /dev/null +++ b/instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/UrlConnectionTest.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.httpurlconnection; + +import static io.opentelemetry.api.trace.SpanKind.CLIENT; +import static io.opentelemetry.api.trace.SpanKind.INTERNAL; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NetTransportValues.IP_TCP; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.instrumentation.test.utils.PortUtils; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class UrlConnectionTest { + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @ParameterizedTest + @ValueSource(strings = {"http", "https"}) + public void traceRequestWithConnectionFailure(String scheme) { + String uri = scheme + "://localhost:" + PortUtils.UNUSABLE_PORT; + + Throwable thrown = + catchThrowable( + () -> + testing.runWithSpan( + "someTrace", + () -> { + URL url = new URI(uri).toURL(); + URLConnection connection = url.openConnection(); + connection.setConnectTimeout(10000); + connection.setReadTimeout(10000); + assertThat(Span.current().getSpanContext().isValid()).isTrue(); + connection.getInputStream(); + })); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("someTrace") + .hasKind(INTERNAL) + .hasNoParent() + .hasStatus(StatusData.error()) + .hasException(thrown), + span -> + span.hasName("HTTP GET") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasStatus(StatusData.error()) + .hasException(thrown) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_TRANSPORT, IP_TCP), + equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), + equalTo(SemanticAttributes.NET_PEER_PORT, PortUtils.UNUSABLE_PORT), + equalTo(SemanticAttributes.HTTP_URL, uri), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_FLAVOR, "1.1")))); + } +}