Browse Source

More HTTP testing refactor prework (#7630)

This is another follow-up from #7616. This makes the test options class
immutable and uses `@AutoValue` and `@AutoValue.Builder`. As a result, a
bunch of the configuration/setup code for these said options now flings
around a builder instance. This isn't great, but I think it's an
incremental improvement that can be seen in the `@BeforeAll
AbstractHttpClientTest.setupOptions()` method, where the immutable
options are (finally) instantiated.
jason plumb 2 years ago
parent
commit
db6b764421
23 changed files with 298 additions and 276 deletions
  1. 3 1
      instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpClientInstrumentationTest.scala
  2. 11 11
      instrumentation/apache-httpasyncclient-4.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientTest.java
  3. 17 17
      instrumentation/apache-httpclient/apache-httpclient-4.3/testing/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/AbstractApacheHttpClientTest.java
  4. 4 4
      instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/AbstractApacheHttpClientTest.java
  5. 3 3
      instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientTest.java
  6. 5 5
      instrumentation/armeria-1.3/library/src/test/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpClientTest.java
  7. 4 4
      instrumentation/armeria-1.3/testing/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/AbstractArmeriaHttpClientTest.java
  8. 4 4
      instrumentation/google-http-client-1.19/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/AbstractGoogleHttpClientTest.java
  9. 4 4
      instrumentation/java-http-client/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpClientTest.java
  10. 4 4
      instrumentation/okhttp/okhttp-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2Test.java
  11. 4 4
      instrumentation/okhttp/okhttp-3.0/testing/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/AbstractOkHttp3Test.java
  12. 8 8
      instrumentation/ratpack/ratpack-1.4/testing/src/main/java/io/opentelemetry/instrumentation/ratpack/client/AbstractRatpackHttpClientTest.java
  13. 3 3
      instrumentation/ratpack/ratpack-1.4/testing/src/main/java/io/opentelemetry/instrumentation/ratpack/client/AbstractRatpackPooledHttpClientTest.java
  14. 6 6
      instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/AbstractRatpackHttpClientTest.java
  15. 7 7
      instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/AbstractReactorNettyHttpClientTest.java
  16. 3 3
      instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/ReactorNettyHttpClientTest.java
  17. 3 3
      instrumentation/spring/spring-web/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebInstrumentationTest.java
  18. 3 3
      instrumentation/spring/spring-web/spring-web-3.1/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/web/SpringRestTemplateTest.java
  19. 5 5
      instrumentation/spring/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/client/AbstractSpringWebfluxClientInstrumentationTest.java
  20. 3 0
      testing-common/build.gradle.kts
  21. 1 1
      testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/HttpClientTest.groovy
  22. 58 56
      testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpClientTest.java
  23. 135 120
      testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/HttpClientTestOptions.java

+ 3 - 1
instrumentation/akka/akka-http-10.0/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/akkahttp/AkkaHttpClientInstrumentationTest.scala

@@ -95,7 +95,9 @@ class AkkaHttpClientInstrumentationTest
       }
   }
 
-  override protected def configure(options: HttpClientTestOptions): Unit = {
+  override protected def configure(
+      options: HttpClientTestOptions.Builder
+  ): Unit = {
     options.disableTestRedirects()
     // singleConnection test would require instrumentation to support requests made through pools
     // (newHostConnectionPool, superPool, etc), which is currently not supported.

+ 11 - 11
instrumentation/apache-httpasyncclient-4.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientTest.java

@@ -99,8 +99,8 @@ class ApacheHttpAsyncClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      configureTest(options);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      configureTest(optionsBuilder);
     }
   }
 
@@ -138,8 +138,8 @@ class ApacheHttpAsyncClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      configureTest(options);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      configureTest(optionsBuilder);
     }
   }
 
@@ -176,8 +176,8 @@ class ApacheHttpAsyncClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      configureTest(options);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      configureTest(optionsBuilder);
     }
   }
 
@@ -224,11 +224,11 @@ class ApacheHttpAsyncClientTest {
     };
   }
 
-  void configureTest(HttpClientTestOptions options) {
-    options.setUserAgent("httpasyncclient");
-    options.setResponseCodeOnRedirectError(302);
-    options.enableTestReadTimeout();
-    options.setHttpAttributes(
+  void configureTest(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.setUserAgent("httpasyncclient");
+    optionsBuilder.setResponseCodeOnRedirectError(302);
+    optionsBuilder.enableTestReadTimeout();
+    optionsBuilder.setHttpAttributes(
         endpoint -> {
           Set<AttributeKey<?>> attributes =
               new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES);

+ 17 - 17
instrumentation/apache-httpclient/apache-httpclient-4.3/testing/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/AbstractApacheHttpClientTest.java

@@ -96,8 +96,8 @@ public abstract class AbstractApacheHttpClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      configureTest(options);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      configureTest(optionsBuilder);
     }
   }
 
@@ -142,8 +142,8 @@ public abstract class AbstractApacheHttpClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      configureTest(options);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      configureTest(optionsBuilder);
     }
   }
 
@@ -183,8 +183,8 @@ public abstract class AbstractApacheHttpClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      configureTest(options);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      configureTest(optionsBuilder);
     }
   }
 
@@ -229,8 +229,8 @@ public abstract class AbstractApacheHttpClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      configureTest(options);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      configureTest(optionsBuilder);
     }
   }
 
@@ -265,8 +265,8 @@ public abstract class AbstractApacheHttpClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      configureTest(options);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      configureTest(optionsBuilder);
     }
   }
 
@@ -301,8 +301,8 @@ public abstract class AbstractApacheHttpClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      configureTest(options);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      configureTest(optionsBuilder);
     }
   }
 
@@ -335,11 +335,11 @@ public abstract class AbstractApacheHttpClientTest {
     };
   }
 
-  static void configureTest(HttpClientTestOptions options) {
-    options.setUserAgent("apachehttpclient");
-    options.setResponseCodeOnRedirectError(302);
-    options.enableTestReadTimeout();
-    options.setHttpAttributes(
+  static void configureTest(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.setUserAgent("apachehttpclient");
+    optionsBuilder.setResponseCodeOnRedirectError(302);
+    optionsBuilder.enableTestReadTimeout();
+    optionsBuilder.setHttpAttributes(
         endpoint -> {
           Set<AttributeKey<?>> attributes =
               new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES);

+ 4 - 4
instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/AbstractApacheHttpClientTest.java

@@ -33,10 +33,10 @@ abstract class AbstractApacheHttpClientTest<T extends HttpRequest>
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.setUserAgent(userAgent());
-    options.enableTestReadTimeout();
-    options.setHttpAttributes(this::getHttpAttributes);
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.setUserAgent(userAgent());
+    optionsBuilder.enableTestReadTimeout();
+    optionsBuilder.setHttpAttributes(this::getHttpAttributes);
   }
 
   protected Set<AttributeKey<?>> getHttpAttributes(URI endpoint) {

+ 3 - 3
instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpAsyncClientTest.java

@@ -111,9 +111,9 @@ class ApacheHttpAsyncClientTest {
     }
 
     @Override
-    protected void configure(HttpClientTestOptions options) {
-      super.configure(options);
-      options.setResponseCodeOnRedirectError(302);
+    protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+      super.configure(optionsBuilder);
+      optionsBuilder.setResponseCodeOnRedirectError(302);
     }
   }
 

+ 5 - 5
instrumentation/armeria-1.3/library/src/test/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpClientTest.java

@@ -23,15 +23,15 @@ class ArmeriaHttpClientTest extends AbstractArmeriaHttpClientTest {
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    super.configure(options);
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    super.configure(optionsBuilder);
 
     // library instrumentation doesn't have a good way of suppressing nested CLIENT spans yet
-    options.disableTestWithClientParent();
+    optionsBuilder.disableTestWithClientParent();
 
     // Agent users have automatic propagation through executor instrumentation, but library users
     // should do manually using Armeria patterns.
-    options.disableTestCallbackWithParent();
-    options.disableTestErrorWithCallback();
+    optionsBuilder.disableTestCallbackWithParent();
+    optionsBuilder.disableTestErrorWithCallback();
   }
 }

+ 4 - 4
instrumentation/armeria-1.3/testing/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/AbstractArmeriaHttpClientTest.java

@@ -94,12 +94,12 @@ public abstract class AbstractArmeriaHttpClientTest extends AbstractHttpClientTe
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
     // Not supported yet: https://github.com/line/armeria/issues/2489
-    options.disableTestRedirects();
+    optionsBuilder.disableTestRedirects();
     // armeria requests can't be reused
-    options.disableTestReusedRequest();
-    options.enableTestReadTimeout();
+    optionsBuilder.disableTestReusedRequest();
+    optionsBuilder.enableTestReadTimeout();
   }
 
   @Test

+ 4 - 4
instrumentation/google-http-client-1.19/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/AbstractGoogleHttpClientTest.java

@@ -113,16 +113,16 @@ public abstract class AbstractGoogleHttpClientTest extends AbstractHttpClientTes
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
     // executeAsync does not actually allow asynchronous execution since it returns a standard
     // Future which cannot have callbacks attached. We instrument execute and executeAsync
     // differently so test both but do not need to run our normal asynchronous tests, which check
     // context propagation, as there is no possible context propagation.
-    options.disableTestCallback();
+    optionsBuilder.disableTestCallback();
 
-    options.enableTestReadTimeout();
+    optionsBuilder.enableTestReadTimeout();
 
     // Circular redirects don't throw an exception with Google Http Client
-    options.disableTestCircularRedirects();
+    optionsBuilder.disableTestCircularRedirects();
   }
 }

+ 4 - 4
instrumentation/java-http-client/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpClientTest.java

@@ -66,11 +66,11 @@ public class JdkHttpClientTest extends AbstractHttpClientTest<HttpRequest> {
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.disableTestCircularRedirects();
-    options.enableTestReadTimeout();
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.disableTestCircularRedirects();
+    optionsBuilder.enableTestReadTimeout();
     // TODO nested client span is not created, but context is still injected
     //  which is not what the test expects
-    options.disableTestWithClientParent();
+    optionsBuilder.disableTestWithClientParent();
   }
 }

+ 4 - 4
instrumentation/okhttp/okhttp-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2Test.java

@@ -92,11 +92,11 @@ public class OkHttp2Test extends AbstractHttpClientTest<Request> {
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.disableTestCircularRedirects();
-    options.enableTestReadTimeout();
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.disableTestCircularRedirects();
+    optionsBuilder.enableTestReadTimeout();
 
-    options.setHttpAttributes(
+    optionsBuilder.setHttpAttributes(
         uri -> {
           Set<AttributeKey<?>> attributes =
               new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES);

+ 4 - 4
instrumentation/okhttp/okhttp-3.0/testing/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/AbstractOkHttp3Test.java

@@ -110,11 +110,11 @@ public abstract class AbstractOkHttp3Test extends AbstractHttpClientTest<Request
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.disableTestCircularRedirects();
-    options.enableTestReadTimeout();
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.disableTestCircularRedirects();
+    optionsBuilder.enableTestReadTimeout();
 
-    options.setHttpAttributes(
+    optionsBuilder.setHttpAttributes(
         uri -> {
           Set<AttributeKey<?>> attributes =
               new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES);

+ 8 - 8
instrumentation/ratpack/ratpack-1.4/testing/src/main/java/io/opentelemetry/instrumentation/ratpack/client/AbstractRatpackHttpClientTest.java

@@ -108,8 +108,8 @@ public abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTe
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.setSingleConnectionFactory(
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.setSingleConnectionFactory(
         (host, port) ->
             (path, headers) -> {
               URI uri = resolveAddress(path);
@@ -118,7 +118,7 @@ public abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTe
                   .getValueOrThrow();
             });
 
-    options.setExpectedClientSpanNameMapper(
+    optionsBuilder.setExpectedClientSpanNameMapper(
         (uri, method) -> {
           switch (uri.toString()) {
             case "http://localhost:61/": // unopened port
@@ -130,7 +130,7 @@ public abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTe
           }
         });
 
-    options.setClientSpanErrorMapper(
+    optionsBuilder.setClientSpanErrorMapper(
         (uri, exception) -> {
           if (uri.toString().equals("https://192.0.2.1/")) {
             return new ConnectTimeoutException("connection timed out: /192.0.2.1:443");
@@ -142,14 +142,14 @@ public abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTe
           return exception;
         });
 
-    options.setHttpAttributes(this::computeHttpAttributes);
+    optionsBuilder.setHttpAttributes(this::computeHttpAttributes);
 
-    options.disableTestRedirects();
+    optionsBuilder.disableTestRedirects();
 
     // these tests will pass, but they don't really test anything since REQUEST is Void
-    options.disableTestReusedRequest();
+    optionsBuilder.disableTestReusedRequest();
 
-    options.enableTestReadTimeout();
+    optionsBuilder.enableTestReadTimeout();
   }
 
   protected Set<AttributeKey<?>> computeHttpAttributes(URI uri) {

+ 3 - 3
instrumentation/ratpack/ratpack-1.4/testing/src/main/java/io/opentelemetry/instrumentation/ratpack/client/AbstractRatpackPooledHttpClientTest.java

@@ -16,11 +16,11 @@ public abstract class AbstractRatpackPooledHttpClientTest extends AbstractRatpac
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    super.configure(options);
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    super.configure(optionsBuilder);
 
     // this test is already run for RatpackHttpClientTest
     // returning null here to avoid running the same test twice
-    options.setSingleConnectionFactory((host, port) -> null);
+    optionsBuilder.setSingleConnectionFactory((host, port) -> null);
   }
 }

+ 6 - 6
instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/AbstractRatpackHttpClientTest.java

@@ -120,8 +120,8 @@ abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTest<Void
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.setSingleConnectionFactory(
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.setSingleConnectionFactory(
         (host, port) ->
             (path, headers) -> {
               URI uri = resolveAddress(path);
@@ -131,7 +131,7 @@ abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTest<Void
                   .getValueOrThrow();
             });
 
-    options.setClientSpanErrorMapper(
+    optionsBuilder.setClientSpanErrorMapper(
         (uri, exception) -> {
           if (uri.toString().equals("https://192.0.2.1/")) {
             return new ConnectTimeoutException("Connect timeout (PT2S) connecting to " + uri);
@@ -144,8 +144,8 @@ abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTest<Void
           return exception;
         });
 
-    options.disableTestRedirects();
-    options.disableTestReusedRequest();
-    options.enableTestReadTimeout();
+    optionsBuilder.disableTestRedirects();
+    optionsBuilder.disableTestReusedRequest();
+    optionsBuilder.enableTestReadTimeout();
   }
 }

+ 7 - 7
instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/AbstractReactorNettyHttpClientTest.java

@@ -103,13 +103,13 @@ abstract class AbstractReactorNettyHttpClientTest
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.disableTestRedirects();
-    options.enableTestReadTimeout();
-    options.setUserAgent(USER_AGENT);
-    options.enableTestCallbackWithImplicitParent();
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.disableTestRedirects();
+    optionsBuilder.enableTestReadTimeout();
+    optionsBuilder.setUserAgent(USER_AGENT);
+    optionsBuilder.enableTestCallbackWithImplicitParent();
 
-    options.setClientSpanErrorMapper(
+    optionsBuilder.setClientSpanErrorMapper(
         (uri, exception) -> {
           if (exception.getClass().getName().endsWith("ReactiveException")) {
             // unopened port or non routable address
@@ -121,7 +121,7 @@ abstract class AbstractReactorNettyHttpClientTest
           return exception;
         });
 
-    options.setHttpAttributes(this::getHttpAttributes);
+    optionsBuilder.setHttpAttributes(this::getHttpAttributes);
   }
 
   protected Set<AttributeKey<?>> getHttpAttributes(URI uri) {

+ 3 - 3
instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/ReactorNettyHttpClientTest.java

@@ -27,10 +27,10 @@ class ReactorNettyHttpClientTest extends AbstractReactorNettyHttpClientTest {
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    super.configure(options);
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    super.configure(optionsBuilder);
 
-    options.setSingleConnectionFactory(
+    optionsBuilder.setSingleConnectionFactory(
         (host, port) -> {
           HttpClient httpClient =
               HttpClient.newConnection()

+ 3 - 3
instrumentation/spring/spring-web/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebInstrumentationTest.java

@@ -87,9 +87,9 @@ public class SpringWebInstrumentationTest extends AbstractHttpClientTest<HttpEnt
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.disableTestCircularRedirects();
-    options.setHttpAttributes(
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.disableTestCircularRedirects();
+    optionsBuilder.setHttpAttributes(
         uri -> {
           Set<AttributeKey<?>> attributes =
               new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES);

+ 3 - 3
instrumentation/spring/spring-web/spring-web-3.1/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/web/SpringRestTemplateTest.java

@@ -85,8 +85,8 @@ public class SpringRestTemplateTest extends AbstractHttpClientTest<HttpEntity<St
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.setMaxRedirects(20);
-    options.setResponseCodeOnRedirectError(302);
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.setMaxRedirects(20);
+    optionsBuilder.setResponseCodeOnRedirectError(302);
   }
 }

+ 5 - 5
instrumentation/spring/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/client/AbstractSpringWebfluxClientInstrumentationTest.java

@@ -64,10 +64,10 @@ public abstract class AbstractSpringWebfluxClientInstrumentationTest
   }
 
   @Override
-  protected void configure(HttpClientTestOptions options) {
-    options.disableTestRedirects();
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
+    optionsBuilder.disableTestRedirects();
 
-    options.setHttpAttributes(
+    optionsBuilder.setHttpAttributes(
         uri -> {
           Set<AttributeKey<?>> attributes =
               new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES);
@@ -75,7 +75,7 @@ public abstract class AbstractSpringWebfluxClientInstrumentationTest
           return attributes;
         });
 
-    options.setClientSpanErrorMapper(
+    optionsBuilder.setClientSpanErrorMapper(
         (uri, throwable) -> {
           if (!throwable.getClass().getName().endsWith("WebClientRequestException")) {
             String uriString = uri.toString();
@@ -90,7 +90,7 @@ public abstract class AbstractSpringWebfluxClientInstrumentationTest
           return throwable;
         });
 
-    options.setSingleConnectionFactory(
+    optionsBuilder.setSingleConnectionFactory(
         (host, port) -> new SpringWebfluxSingleConnection(host, port, this::instrument));
   }
 

+ 3 - 0
testing-common/build.gradle.kts

@@ -51,6 +51,9 @@ dependencies {
 
   compileOnly(project(":testing:armeria-shaded-for-testing", configuration = "shadow"))
 
+  compileOnly("com.google.auto.value:auto-value-annotations")
+  annotationProcessor("com.google.auto.value:auto-value")
+
   implementation("io.opentelemetry.proto:opentelemetry-proto")
 
   implementation("net.bytebuddy:byte-buddy")

+ 1 - 1
testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/HttpClientTest.groovy

@@ -11,8 +11,8 @@ import io.opentelemetry.instrumentation.test.InstrumentationSpecification
 import io.opentelemetry.instrumentation.test.asserts.TraceAssert
 import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest
 import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult
-import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions
 import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestServer
+import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions
 import io.opentelemetry.instrumentation.testing.junit.http.SingleConnection
 import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions
 import io.opentelemetry.sdk.trace.data.SpanData

+ 58 - 56
testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpClientTest.java

@@ -67,59 +67,61 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
   protected InstrumentationTestRunner testing;
   private HttpClientTestServer server;
 
-  private final HttpClientTestOptions options = new HttpClientTestOptions();
+  private HttpClientTestOptions options;
 
   @BeforeAll
   void setupOptions() {
+    HttpClientTestOptions.Builder builder = HttpClientTestOptions.builder();
     // TODO(anuraaga): Have subclasses configure options directly and remove mapping of legacy
     // protected methods.
-    options.setHttpAttributes(this::httpAttributes);
-    options.setExpectedClientSpanNameMapper(this::expectedClientSpanName);
+    builder.setHttpAttributes(this::httpAttributes);
+    builder.setExpectedClientSpanNameMapper(this::expectedClientSpanName);
     Integer responseCodeOnError = responseCodeOnRedirectError();
     if (responseCodeOnError != null) {
-      options.setResponseCodeOnRedirectError(responseCodeOnError);
+      builder.setResponseCodeOnRedirectError(responseCodeOnError);
     }
-    options.setUserAgent(userAgent());
-    options.setClientSpanErrorMapper(this::clientSpanError);
-    options.setSingleConnectionFactory(this::createSingleConnection);
+    builder.setUserAgent(userAgent());
+    builder.setClientSpanErrorMapper(this::clientSpanError);
+    builder.setSingleConnectionFactory(this::createSingleConnection);
     if (!testWithClientParent()) {
-      options.disableTestWithClientParent();
+      builder.disableTestWithClientParent();
     }
     if (!testRedirects()) {
-      options.disableTestRedirects();
+      builder.disableTestRedirects();
     }
     if (!testCircularRedirects()) {
-      options.disableTestCircularRedirects();
+      builder.disableTestCircularRedirects();
     }
-    options.setMaxRedirects(maxRedirects());
+    builder.setMaxRedirects(maxRedirects());
     if (!testReusedRequest()) {
-      options.disableTestReusedRequest();
+      builder.disableTestReusedRequest();
     }
     if (!testConnectionFailure()) {
-      options.disableTestConnectionFailure();
+      builder.disableTestConnectionFailure();
     }
     if (testReadTimeout()) {
-      options.enableTestReadTimeout();
+      builder.enableTestReadTimeout();
     }
     if (!testRemoteConnection()) {
-      options.disableTestRemoteConnection();
+      builder.disableTestRemoteConnection();
     }
     if (!testHttps()) {
-      options.disableTestHttps();
+      builder.disableTestHttps();
     }
     if (!testCallback()) {
-      options.disableTestCallback();
+      builder.disableTestCallback();
     }
     if (!testCallbackWithParent()) {
-      options.disableTestCallbackWithParent();
+      builder.disableTestCallbackWithParent();
     }
     if (!testErrorWithCallback()) {
-      options.disableTestErrorWithCallback();
+      builder.disableTestErrorWithCallback();
     }
     if (testCallbackWithImplicitParent()) {
-      options.enableTestCallbackWithImplicitParent();
+      builder.enableTestCallbackWithImplicitParent();
     }
-    configure(options);
+    configure(builder);
+    options = builder.build();
   }
 
   @BeforeEach
@@ -182,7 +184,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
   @ValueSource(strings = {"PUT", "POST"})
   void shouldSuppressNestedClientSpanIfAlreadyUnderParentClientSpan(String method)
       throws Exception {
-    assumeTrue(options.testWithClientParent);
+    assumeTrue(options.getTestWithClientParent());
 
     URI uri = resolveAddress("/success");
     int responseCode =
@@ -201,8 +203,8 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void requestWithCallbackAndParent() throws Throwable {
-    assumeTrue(options.testCallback);
-    assumeTrue(options.testCallbackWithParent);
+    assumeTrue(options.getTestCallback());
+    assumeTrue(options.getTestCallbackWithParent());
 
     String method = "GET";
     URI uri = resolveAddress("/success");
@@ -226,8 +228,8 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void requestWithCallbackAndNoParent() throws Throwable {
-    assumeTrue(options.testCallback);
-    assumeFalse(options.testCallbackWithImplicitParent);
+    assumeTrue(options.getTestCallback());
+    assumeFalse(options.getTestCallbackWithImplicitParent());
 
     String method = "GET";
     URI uri = resolveAddress("/success");
@@ -250,7 +252,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void requestWithCallbackAndImplicitParent() throws Throwable {
-    assumeTrue(options.testCallbackWithImplicitParent);
+    assumeTrue(options.getTestCallbackWithImplicitParent());
 
     String method = "GET";
     URI uri = resolveAddress("/success");
@@ -276,7 +278,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
     // TODO quite a few clients create an extra span for the redirect
     // This test should handle both types or we should unify how the clients work
 
-    assumeTrue(options.testRedirects);
+    assumeTrue(options.getTestRedirects());
 
     String method = "GET";
     URI uri = resolveAddress("/redirect");
@@ -299,7 +301,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
     // TODO quite a few clients create an extra span for the redirect
     // This test should handle both types or we should unify how the clients work
 
-    assumeTrue(options.testRedirects);
+    assumeTrue(options.getTestRedirects());
 
     String method = "GET";
     URI uri = resolveAddress("/another-redirect");
@@ -320,8 +322,8 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void circularRedirects() {
-    assumeTrue(options.testRedirects);
-    assumeTrue(options.testCircularRedirects);
+    assumeTrue(options.getTestRedirects());
+    assumeTrue(options.getTestCircularRedirects());
 
     String method = "GET";
     URI uri = resolveAddress("/circular-redirect");
@@ -333,17 +335,17 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
     } else {
       ex = thrown;
     }
-    Throwable clientError = options.clientSpanErrorMapper.apply(uri, ex);
+    Throwable clientError = options.getClientSpanErrorMapper().apply(uri, ex);
 
     testing.waitAndAssertTraces(
         trace -> {
           List<Consumer<SpanDataAssert>> assertions = new ArrayList<>();
           assertions.add(
               span ->
-                  assertClientSpan(span, uri, method, options.responseCodeOnRedirectError)
+                  assertClientSpan(span, uri, method, options.getResponseCodeOnRedirectError())
                       .hasNoParent()
                       .hasException(clientError));
-          for (int i = 0; i < options.maxRedirects; i++) {
+          for (int i = 0; i < options.getMaxRedirects(); i++) {
             assertions.add(span -> assertServerSpan(span).hasParent(trace.getSpan(0)));
           }
           trace.hasSpansSatisfyingExactly(assertions);
@@ -352,7 +354,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void redirectToSecuredCopiesAuthHeader() throws Exception {
-    assumeTrue(options.testRedirects);
+    assumeTrue(options.getTestRedirects());
 
     String method = "GET";
     URI uri = resolveAddress("/to-secured");
@@ -396,7 +398,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void reuseRequest() throws Exception {
-    assumeTrue(options.testReusedRequest);
+    assumeTrue(options.getTestReusedRequest());
 
     String method = "GET";
     URI uri = resolveAddress("/success");
@@ -443,7 +445,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void connectionErrorUnopenedPort() {
-    assumeTrue(options.testConnectionFailure);
+    assumeTrue(options.getTestConnectionFailure());
 
     String method = "GET";
     URI uri = URI.create("http://localhost:" + PortUtils.UNUSABLE_PORT + '/');
@@ -456,7 +458,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
     } else {
       ex = thrown;
     }
-    Throwable clientError = options.clientSpanErrorMapper.apply(uri, ex);
+    Throwable clientError = options.getClientSpanErrorMapper().apply(uri, ex);
 
     testing.waitAndAssertTraces(
         trace -> {
@@ -476,9 +478,9 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void connectionErrorUnopenedPortWithCallback() throws Exception {
-    assumeTrue(options.testConnectionFailure);
-    assumeTrue(options.testCallback);
-    assumeTrue(options.testErrorWithCallback);
+    assumeTrue(options.getTestConnectionFailure());
+    assumeTrue(options.getTestCallback());
+    assumeTrue(options.getTestErrorWithCallback());
 
     String method = "GET";
     URI uri = URI.create("http://localhost:" + PortUtils.UNUSABLE_PORT + '/');
@@ -497,7 +499,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
     } else {
       ex = thrown;
     }
-    Throwable clientError = options.clientSpanErrorMapper.apply(uri, ex);
+    Throwable clientError = options.getClientSpanErrorMapper().apply(uri, ex);
 
     testing.waitAndAssertTraces(
         trace -> {
@@ -525,10 +527,10 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void connectionErrorNonRoutableAddress() {
-    assumeTrue(options.testRemoteConnection);
+    assumeTrue(options.getTestRemoteConnection());
 
     String method = "HEAD";
-    URI uri = URI.create(options.testHttps ? "https://192.0.2.1/" : "http://192.0.2.1/");
+    URI uri = URI.create(options.getTestHttps() ? "https://192.0.2.1/" : "http://192.0.2.1/");
 
     Throwable thrown =
         catchThrowable(() -> testing.runWithSpan("parent", () -> doRequest(method, uri)));
@@ -538,7 +540,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
     } else {
       ex = thrown;
     }
-    Throwable clientError = options.clientSpanErrorMapper.apply(uri, ex);
+    Throwable clientError = options.getClientSpanErrorMapper().apply(uri, ex);
 
     testing.waitAndAssertTraces(
         trace -> {
@@ -558,7 +560,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void readTimedOut() {
-    assumeTrue(options.testReadTimeout);
+    assumeTrue(options.getTestReadTimeout());
 
     String method = "GET";
     URI uri = resolveAddress("/read-timeout");
@@ -571,7 +573,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
     } else {
       ex = thrown;
     }
-    Throwable clientError = options.clientSpanErrorMapper.apply(uri, ex);
+    Throwable clientError = options.getClientSpanErrorMapper().apply(uri, ex);
 
     testing.waitAndAssertTraces(
         trace -> {
@@ -596,8 +598,8 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
       disabledReason = "IBM JVM has different protocol support for TLS")
   @Test
   void httpsRequest() throws Exception {
-    assumeTrue(options.testRemoteConnection);
-    assumeTrue(options.testHttps);
+    assumeTrue(options.getTestRemoteConnection());
+    assumeTrue(options.getTestHttps());
 
     String method = "GET";
     URI uri = URI.create("https://localhost:" + server.httpsPort() + "/success");
@@ -693,8 +695,8 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
   @Test
   void highConcurrencyWithCallback() {
-    assumeTrue(options.testCallback);
-    assumeTrue(options.testCallbackWithParent);
+    assumeTrue(options.getTestCallback());
+    assumeTrue(options.getTestCallbackWithParent());
 
     int count = 50;
     String method = "GET";
@@ -775,7 +777,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
   @Test
   void highConcurrencyOnSingleConnection() {
     SingleConnection singleConnection =
-        options.singleConnectionFactory.apply("localhost", server.httpPort());
+        options.getSingleConnectionFactory().apply("localhost", server.httpPort());
     assumeTrue(singleConnection != null);
 
     int count = 50;
@@ -849,8 +851,8 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
   // Visible for spock bridge.
   SpanDataAssert assertClientSpan(
       SpanDataAssert span, URI uri, String method, Integer responseCode) {
-    Set<AttributeKey<?>> httpClientAttributes = options.httpAttributes.apply(uri);
-    return span.hasName(options.expectedClientSpanNameMapper.apply(uri, method))
+    Set<AttributeKey<?>> httpClientAttributes = options.getHttpAttributes().apply(uri);
+    return span.hasName(options.getExpectedClientSpanNameMapper().apply(uri, method))
         .hasKind(SpanKind.CLIENT)
         .hasAttributesSatisfying(
             attrs -> {
@@ -911,7 +913,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
                         SemanticAttributes.HttpFlavorValues.HTTP_1_1);
               }
               if (httpClientAttributes.contains(SemanticAttributes.HTTP_USER_AGENT)) {
-                String userAgent = options.userAgent;
+                String userAgent = options.getUserAgent();
                 if (userAgent != null) {
                   assertThat(attrs)
                       .hasEntrySatisfying(
@@ -1039,7 +1041,7 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
     return true;
   }
 
-  protected void configure(HttpClientTestOptions options) {}
+  protected void configure(HttpClientTestOptions.Builder optionsBuilder) {}
 
   private int doRequest(String method, URI uri) throws Exception {
     return doRequest(method, uri, Collections.emptyMap());

+ 135 - 120
testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/HttpClientTestOptions.java

@@ -5,6 +5,7 @@
 
 package io.opentelemetry.instrumentation.testing.junit.http;
 
+import com.google.auto.value.AutoValue;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
@@ -15,9 +16,10 @@ import java.util.HashSet;
 import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.Function;
+import javax.annotation.Nullable;
 
-public final class HttpClientTestOptions {
-
+@AutoValue
+public abstract class HttpClientTestOptions {
   public static final Set<AttributeKey<?>> DEFAULT_HTTP_ATTRIBUTES =
       Collections.unmodifiableSet(
           new HashSet<>(
@@ -32,149 +34,162 @@ public final class HttpClientTestOptions {
   public static final BiFunction<URI, String, String> DEFAULT_EXPECTED_CLIENT_SPAN_NAME_MAPPER =
       (uri, method) -> method != null ? "HTTP " + method : "HTTP request";
 
-  Function<URI, Set<AttributeKey<?>>> httpAttributes = unused -> DEFAULT_HTTP_ATTRIBUTES;
+  public abstract Function<URI, Set<AttributeKey<?>>> getHttpAttributes();
 
-  BiFunction<URI, String, String> expectedClientSpanNameMapper =
-      DEFAULT_EXPECTED_CLIENT_SPAN_NAME_MAPPER;
+  @Nullable
+  public abstract Integer getResponseCodeOnRedirectError();
 
-  Integer responseCodeOnRedirectError = null;
-  String userAgent = null;
+  @Nullable
+  public abstract String getUserAgent();
 
-  BiFunction<URI, Throwable, Throwable> clientSpanErrorMapper = (uri, exception) -> exception;
+  public abstract BiFunction<URI, Throwable, Throwable> getClientSpanErrorMapper();
 
-  BiFunction<String, Integer, SingleConnection> singleConnectionFactory = (host, port) -> null;
+  public abstract BiFunction<String, Integer, SingleConnection> getSingleConnectionFactory();
 
-  boolean testWithClientParent = true;
-  boolean testRedirects = true;
-  boolean testCircularRedirects = true;
-  int maxRedirects = 2;
-  boolean testReusedRequest = true;
-  boolean testConnectionFailure = true;
-  boolean testReadTimeout = false;
-  boolean testRemoteConnection = true;
-  boolean testHttps = true;
-  boolean testCallback = true;
-  boolean testCallbackWithParent = true;
-  boolean testCallbackWithImplicitParent = false;
-  boolean testErrorWithCallback = true;
+  public abstract BiFunction<URI, String, String> getExpectedClientSpanNameMapper();
 
-  HttpClientTestOptions() {}
+  public abstract boolean getTestWithClientParent();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions setHttpAttributes(
-      Function<URI, Set<AttributeKey<?>>> httpAttributes) {
-    this.httpAttributes = httpAttributes;
-    return this;
-  }
+  public abstract boolean getTestRedirects();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions setExpectedClientSpanNameMapper(
-      BiFunction<URI, String, String> expectedClientSpanNameMapper) {
-    this.expectedClientSpanNameMapper = expectedClientSpanNameMapper;
-    return this;
-  }
+  public abstract boolean getTestCircularRedirects();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions setResponseCodeOnRedirectError(int responseCodeOnRedirectError) {
-    this.responseCodeOnRedirectError = responseCodeOnRedirectError;
-    return this;
-  }
+  public abstract int getMaxRedirects();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions setUserAgent(String userAgent) {
-    this.userAgent = userAgent;
-    return this;
-  }
+  public abstract boolean getTestReusedRequest();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions setClientSpanErrorMapper(
-      BiFunction<URI, Throwable, Throwable> clientSpanErrorMapper) {
-    this.clientSpanErrorMapper = clientSpanErrorMapper;
-    return this;
-  }
+  public abstract boolean getTestConnectionFailure();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions setSingleConnectionFactory(
-      BiFunction<String, Integer, SingleConnection> singleConnectionFactory) {
-    this.singleConnectionFactory = singleConnectionFactory;
-    return this;
-  }
+  public abstract boolean getTestReadTimeout();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions setMaxRedirects(int maxRedirects) {
-    this.maxRedirects = maxRedirects;
-    return this;
-  }
+  public abstract boolean getTestRemoteConnection();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestWithClientParent() {
-    testWithClientParent = false;
-    return this;
-  }
+  public abstract boolean getTestHttps();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestRedirects() {
-    testRedirects = false;
-    return this;
-  }
+  public abstract boolean getTestCallback();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestCircularRedirects() {
-    testCircularRedirects = false;
-    return this;
-  }
+  public abstract boolean getTestCallbackWithParent();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestReusedRequest() {
-    testReusedRequest = false;
-    return this;
-  }
+  public abstract boolean getTestCallbackWithImplicitParent();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestConnectionFailure() {
-    testConnectionFailure = false;
-    return this;
-  }
+  public abstract boolean getTestErrorWithCallback();
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions enableTestReadTimeout() {
-    testReadTimeout = true;
-    return this;
+  static Builder builder() {
+    return new AutoValue_HttpClientTestOptions.Builder().withDefaults();
   }
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestRemoteConnection() {
-    testRemoteConnection = false;
-    return this;
-  }
+  @AutoValue.Builder
+  public interface Builder {
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestHttps() {
-    testHttps = false;
-    return this;
-  }
+    @CanIgnoreReturnValue
+    default Builder withDefaults() {
+      return setHttpAttributes(x -> DEFAULT_HTTP_ATTRIBUTES)
+          .setResponseCodeOnRedirectError(null)
+          .setUserAgent(null)
+          .setClientSpanErrorMapper((uri, exception) -> exception)
+          .setSingleConnectionFactory((host, port) -> null)
+          .setExpectedClientSpanNameMapper(DEFAULT_EXPECTED_CLIENT_SPAN_NAME_MAPPER)
+          .setTestWithClientParent(true)
+          .setTestRedirects(true)
+          .setTestCircularRedirects(true)
+          .setMaxRedirects(2)
+          .setTestReusedRequest(true)
+          .setTestConnectionFailure(true)
+          .setTestReadTimeout(false)
+          .setTestRemoteConnection(true)
+          .setTestHttps(true)
+          .setTestCallback(true)
+          .setTestCallbackWithParent(true)
+          .setTestCallbackWithImplicitParent(false)
+          .setTestErrorWithCallback(true);
+    }
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestCallback() {
-    testCallback = false;
-    return this;
-  }
+    Builder setHttpAttributes(Function<URI, Set<AttributeKey<?>>> value);
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestCallbackWithParent() {
-    testCallbackWithParent = false;
-    return this;
-  }
+    Builder setResponseCodeOnRedirectError(Integer value);
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions enableTestCallbackWithImplicitParent() {
-    testCallbackWithImplicitParent = true;
-    return this;
-  }
+    Builder setUserAgent(String value);
+
+    Builder setClientSpanErrorMapper(BiFunction<URI, Throwable, Throwable> value);
+
+    Builder setSingleConnectionFactory(BiFunction<String, Integer, SingleConnection> value);
+
+    Builder setExpectedClientSpanNameMapper(BiFunction<URI, String, String> value);
+
+    Builder setTestWithClientParent(boolean value);
+
+    Builder setTestRedirects(boolean value);
+
+    Builder setTestCircularRedirects(boolean value);
+
+    Builder setMaxRedirects(int value);
+
+    Builder setTestReusedRequest(boolean value);
+
+    Builder setTestConnectionFailure(boolean value);
+
+    Builder setTestReadTimeout(boolean value);
+
+    Builder setTestRemoteConnection(boolean value);
+
+    Builder setTestHttps(boolean value);
+
+    Builder setTestCallback(boolean value);
+
+    Builder setTestCallbackWithParent(boolean value);
+
+    Builder setTestCallbackWithImplicitParent(boolean value);
+
+    Builder setTestErrorWithCallback(boolean value);
+
+    default Builder disableTestWithClientParent() {
+      return setTestWithClientParent(false);
+    }
+
+    default Builder disableTestRedirects() {
+      return setTestRedirects(false);
+    }
+
+    default Builder disableTestCircularRedirects() {
+      return setTestCircularRedirects(false);
+    }
+
+    default Builder disableTestReusedRequest() {
+      return setTestReusedRequest(false);
+    }
+
+    default Builder disableTestConnectionFailure() {
+      return setTestConnectionFailure(false);
+    }
+
+    default Builder enableTestReadTimeout() {
+      return setTestReadTimeout(true);
+    }
+
+    default Builder disableTestRemoteConnection() {
+      return setTestRemoteConnection(false);
+    }
+
+    default Builder disableTestHttps() {
+      return setTestHttps(false);
+    }
+
+    default Builder disableTestCallback() {
+      return setTestCallback(false);
+    }
+
+    default Builder disableTestCallbackWithParent() {
+      return setTestCallbackWithParent(false);
+    }
+
+    default Builder disableTestErrorWithCallback() {
+      return setTestErrorWithCallback(false);
+    }
+
+    default Builder enableTestCallbackWithImplicitParent() {
+      return setTestCallbackWithImplicitParent(true);
+    }
 
-  @CanIgnoreReturnValue
-  public HttpClientTestOptions disableTestErrorWithCallback() {
-    testErrorWithCallback = false;
-    return this;
+    HttpClientTestOptions build();
   }
 }