[chttp2] Backport-1.53: Fix some fuzzer found bugs (#33021)

<!--

If you know who should review your pull request, please assign it to
that
person, otherwise the pull request would get assigned randomly.

If your pull request is for a specific language, please add the
appropriate
lang label.

-->

---------

Co-authored-by: ctiller <ctiller@users.noreply.github.com>
Co-authored-by: Alisha Nanda <alishananda@google.com>
Co-authored-by: BrandonY <brandony@gmail.com>
diff --git a/BUILD b/BUILD
index 32f8527..632ba43 100644
--- a/BUILD
+++ b/BUILD
@@ -3551,6 +3551,7 @@
         "//src/core:decode_huff",
         "//src/core:error",
         "//src/core:hpack_constants",
+        "//src/core:random_early_detection",
         "//src/core:slice",
         "//src/core:slice_refcount",
         "//src/core:stats_data",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 501e248..a671710 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -785,7 +785,6 @@
     add_dependencies(buildtests_c fd_conservation_posix_test)
   endif()
   add_dependencies(buildtests_c goaway_server_test)
-  add_dependencies(buildtests_c inproc_callback_test)
   add_dependencies(buildtests_c invalid_call_argument_test)
   add_dependencies(buildtests_c multiple_server_queues_test)
   add_dependencies(buildtests_c no_server_test)
@@ -2149,6 +2148,7 @@
   src/core/lib/address_utils/parse_address.cc
   src/core/lib/address_utils/sockaddr_utils.cc
   src/core/lib/backoff/backoff.cc
+  src/core/lib/backoff/random_early_detection.cc
   src/core/lib/channel/channel_args.cc
   src/core/lib/channel/channel_args_preconditioning.cc
   src/core/lib/channel/channel_stack.cc
@@ -2836,6 +2836,7 @@
   src/core/lib/address_utils/parse_address.cc
   src/core/lib/address_utils/sockaddr_utils.cc
   src/core/lib/backoff/backoff.cc
+  src/core/lib/backoff/random_early_detection.cc
   src/core/lib/channel/channel_args.cc
   src/core/lib/channel/channel_args_preconditioning.cc
   src/core/lib/channel/channel_stack.cc
@@ -5206,36 +5207,6 @@
 endif()
 if(gRPC_BUILD_TESTS)
 
-add_executable(inproc_callback_test
-  test/core/end2end/fixtures/local_util.cc
-  test/core/end2end/inproc_callback_test.cc
-)
-target_compile_features(inproc_callback_test PUBLIC cxx_std_14)
-target_include_directories(inproc_callback_test
-  PRIVATE
-    ${CMAKE_CURRENT_SOURCE_DIR}
-    ${CMAKE_CURRENT_SOURCE_DIR}/include
-    ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
-    ${_gRPC_RE2_INCLUDE_DIR}
-    ${_gRPC_SSL_INCLUDE_DIR}
-    ${_gRPC_UPB_GENERATED_DIR}
-    ${_gRPC_UPB_GRPC_GENERATED_DIR}
-    ${_gRPC_UPB_INCLUDE_DIR}
-    ${_gRPC_XXHASH_INCLUDE_DIR}
-    ${_gRPC_ZLIB_INCLUDE_DIR}
-)
-
-target_link_libraries(inproc_callback_test
-  ${_gRPC_BASELIB_LIBRARIES}
-  ${_gRPC_ZLIB_LIBRARIES}
-  ${_gRPC_ALLTARGETS_LIBRARIES}
-  grpc_test_util
-)
-
-
-endif()
-if(gRPC_BUILD_TESTS)
-
 add_executable(invalid_call_argument_test
   test/core/end2end/cq_verifier.cc
   test/core/end2end/invalid_call_argument_test.cc
@@ -11438,6 +11409,7 @@
   src/core/ext/upb-generated/src/proto/grpc/gcp/transport_security_common.upb.c
   src/core/lib/address_utils/parse_address.cc
   src/core/lib/address_utils/sockaddr_utils.cc
+  src/core/lib/backoff/random_early_detection.cc
   src/core/lib/channel/channel_args.cc
   src/core/lib/channel/channel_args_preconditioning.cc
   src/core/lib/channel/channel_stack.cc
diff --git a/Makefile b/Makefile
index 3537661..d48bd0e 100644
--- a/Makefile
+++ b/Makefile
@@ -1403,6 +1403,7 @@
     src/core/lib/address_utils/parse_address.cc \
     src/core/lib/address_utils/sockaddr_utils.cc \
     src/core/lib/backoff/backoff.cc \
+    src/core/lib/backoff/random_early_detection.cc \
     src/core/lib/channel/channel_args.cc \
     src/core/lib/channel/channel_args_preconditioning.cc \
     src/core/lib/channel/channel_stack.cc \
@@ -1943,6 +1944,7 @@
     src/core/lib/address_utils/parse_address.cc \
     src/core/lib/address_utils/sockaddr_utils.cc \
     src/core/lib/backoff/backoff.cc \
+    src/core/lib/backoff/random_early_detection.cc \
     src/core/lib/channel/channel_args.cc \
     src/core/lib/channel/channel_args_preconditioning.cc \
     src/core/lib/channel/channel_stack.cc \
diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml
index 4827e92..9778c50 100644
--- a/build_autogenerated.yaml
+++ b/build_autogenerated.yaml
@@ -20,9 +20,16 @@
   - test/core/end2end/cq_verifier.h
   - test/core/end2end/data/ssl_test_data.h
   - test/core/end2end/end2end_tests.h
+  - test/core/end2end/fixtures/h2_oauth2_common.h
+  - test/core/end2end/fixtures/h2_ssl_cred_reload_fixture.h
+  - test/core/end2end/fixtures/h2_ssl_tls_common.h
+  - test/core/end2end/fixtures/h2_tls_common.h
   - test/core/end2end/fixtures/http_proxy_fixture.h
+  - test/core/end2end/fixtures/inproc_fixture.h
   - test/core/end2end/fixtures/local_util.h
   - test/core/end2end/fixtures/proxy.h
+  - test/core/end2end/fixtures/secure_fixture.h
+  - test/core/end2end/fixtures/sockpair_fixture.h
   - test/core/end2end/tests/cancel_test_helpers.h
   - test/core/util/test_lb_policies.h
   src:
@@ -759,6 +766,7 @@
   - src/core/lib/address_utils/sockaddr_utils.h
   - src/core/lib/avl/avl.h
   - src/core/lib/backoff/backoff.h
+  - src/core/lib/backoff/random_early_detection.h
   - src/core/lib/channel/call_finalization.h
   - src/core/lib/channel/call_tracer.h
   - src/core/lib/channel/channel_args.h
@@ -1542,6 +1550,7 @@
   - src/core/lib/address_utils/parse_address.cc
   - src/core/lib/address_utils/sockaddr_utils.cc
   - src/core/lib/backoff/backoff.cc
+  - src/core/lib/backoff/random_early_detection.cc
   - src/core/lib/channel/channel_args.cc
   - src/core/lib/channel/channel_args_preconditioning.cc
   - src/core/lib/channel/channel_stack.cc
@@ -2096,6 +2105,7 @@
   - src/core/lib/address_utils/sockaddr_utils.h
   - src/core/lib/avl/avl.h
   - src/core/lib/backoff/backoff.h
+  - src/core/lib/backoff/random_early_detection.h
   - src/core/lib/channel/call_finalization.h
   - src/core/lib/channel/call_tracer.h
   - src/core/lib/channel/channel_args.h
@@ -2493,6 +2503,7 @@
   - src/core/lib/address_utils/parse_address.cc
   - src/core/lib/address_utils/sockaddr_utils.cc
   - src/core/lib/backoff/backoff.cc
+  - src/core/lib/backoff/random_early_detection.cc
   - src/core/lib/channel/channel_args.cc
   - src/core/lib/channel/channel_args_preconditioning.cc
   - src/core/lib/channel/channel_stack.cc
@@ -4291,18 +4302,6 @@
   - test/core/end2end/goaway_server_test.cc
   deps:
   - grpc_test_util
-- name: inproc_callback_test
-  build: test
-  language: c
-  headers:
-  - test/core/end2end/end2end_tests.h
-  - test/core/end2end/fixtures/local_util.h
-  src:
-  - test/core/end2end/fixtures/local_util.cc
-  - test/core/end2end/inproc_callback_test.cc
-  deps:
-  - grpc_test_util
-  uses_polling: false
 - name: invalid_call_argument_test
   build: test
   language: c
@@ -7371,6 +7370,7 @@
   - src/core/lib/address_utils/parse_address.h
   - src/core/lib/address_utils/sockaddr_utils.h
   - src/core/lib/avl/avl.h
+  - src/core/lib/backoff/random_early_detection.h
   - src/core/lib/channel/call_finalization.h
   - src/core/lib/channel/call_tracer.h
   - src/core/lib/channel/channel_args.h
@@ -7630,6 +7630,7 @@
   - src/core/ext/upb-generated/src/proto/grpc/gcp/transport_security_common.upb.c
   - src/core/lib/address_utils/parse_address.cc
   - src/core/lib/address_utils/sockaddr_utils.cc
+  - src/core/lib/backoff/random_early_detection.cc
   - src/core/lib/channel/channel_args.cc
   - src/core/lib/channel/channel_args_preconditioning.cc
   - src/core/lib/channel/channel_stack.cc
diff --git a/config.m4 b/config.m4
index e8ca7ba..976c593 100644
--- a/config.m4
+++ b/config.m4
@@ -484,6 +484,7 @@
     src/core/lib/address_utils/parse_address.cc \
     src/core/lib/address_utils/sockaddr_utils.cc \
     src/core/lib/backoff/backoff.cc \
+    src/core/lib/backoff/random_early_detection.cc \
     src/core/lib/channel/channel_args.cc \
     src/core/lib/channel/channel_args_preconditioning.cc \
     src/core/lib/channel/channel_stack.cc \
diff --git a/config.w32 b/config.w32
index 12bf985..4bfe3b7 100644
--- a/config.w32
+++ b/config.w32
@@ -450,6 +450,7 @@
     "src\\core\\lib\\address_utils\\parse_address.cc " +
     "src\\core\\lib\\address_utils\\sockaddr_utils.cc " +
     "src\\core\\lib\\backoff\\backoff.cc " +
+    "src\\core\\lib\\backoff\\random_early_detection.cc " +
     "src\\core\\lib\\channel\\channel_args.cc " +
     "src\\core\\lib\\channel\\channel_args_preconditioning.cc " +
     "src\\core\\lib\\channel\\channel_stack.cc " +
diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec
index 52a2ca0..4f2d1c6 100644
--- a/gRPC-C++.podspec
+++ b/gRPC-C++.podspec
@@ -710,6 +710,7 @@
                       'src/core/lib/address_utils/sockaddr_utils.h',
                       'src/core/lib/avl/avl.h',
                       'src/core/lib/backoff/backoff.h',
+                      'src/core/lib/backoff/random_early_detection.h',
                       'src/core/lib/channel/call_finalization.h',
                       'src/core/lib/channel/call_tracer.h',
                       'src/core/lib/channel/channel_args.h',
@@ -1642,6 +1643,7 @@
                               'src/core/lib/address_utils/sockaddr_utils.h',
                               'src/core/lib/avl/avl.h',
                               'src/core/lib/backoff/backoff.h',
+                              'src/core/lib/backoff/random_early_detection.h',
                               'src/core/lib/channel/call_finalization.h',
                               'src/core/lib/channel/call_tracer.h',
                               'src/core/lib/channel/channel_args.h',
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index c544add..89779cd 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -1083,6 +1083,8 @@
                       'src/core/lib/avl/avl.h',
                       'src/core/lib/backoff/backoff.cc',
                       'src/core/lib/backoff/backoff.h',
+                      'src/core/lib/backoff/random_early_detection.cc',
+                      'src/core/lib/backoff/random_early_detection.h',
                       'src/core/lib/channel/call_finalization.h',
                       'src/core/lib/channel/call_tracer.h',
                       'src/core/lib/channel/channel_args.cc',
@@ -2327,6 +2329,7 @@
                               'src/core/lib/address_utils/sockaddr_utils.h',
                               'src/core/lib/avl/avl.h',
                               'src/core/lib/backoff/backoff.h',
+                              'src/core/lib/backoff/random_early_detection.h',
                               'src/core/lib/channel/call_finalization.h',
                               'src/core/lib/channel/call_tracer.h',
                               'src/core/lib/channel/channel_args.h',
@@ -2799,12 +2802,19 @@
                       'test/core/end2end/end2end_test_utils.cc',
                       'test/core/end2end/end2end_tests.cc',
                       'test/core/end2end/end2end_tests.h',
+                      'test/core/end2end/fixtures/h2_oauth2_common.h',
+                      'test/core/end2end/fixtures/h2_ssl_cred_reload_fixture.h',
+                      'test/core/end2end/fixtures/h2_ssl_tls_common.h',
+                      'test/core/end2end/fixtures/h2_tls_common.h',
                       'test/core/end2end/fixtures/http_proxy_fixture.cc',
                       'test/core/end2end/fixtures/http_proxy_fixture.h',
+                      'test/core/end2end/fixtures/inproc_fixture.h',
                       'test/core/end2end/fixtures/local_util.cc',
                       'test/core/end2end/fixtures/local_util.h',
                       'test/core/end2end/fixtures/proxy.cc',
                       'test/core/end2end/fixtures/proxy.h',
+                      'test/core/end2end/fixtures/secure_fixture.h',
+                      'test/core/end2end/fixtures/sockpair_fixture.h',
                       'test/core/end2end/tests/authority_not_supported.cc',
                       'test/core/end2end/tests/bad_hostname.cc',
                       'test/core/end2end/tests/bad_ping.cc',
diff --git a/grpc.gemspec b/grpc.gemspec
index 9adc835..31a541d 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -992,6 +992,8 @@
   s.files += %w( src/core/lib/avl/avl.h )
   s.files += %w( src/core/lib/backoff/backoff.cc )
   s.files += %w( src/core/lib/backoff/backoff.h )
+  s.files += %w( src/core/lib/backoff/random_early_detection.cc )
+  s.files += %w( src/core/lib/backoff/random_early_detection.h )
   s.files += %w( src/core/lib/channel/call_finalization.h )
   s.files += %w( src/core/lib/channel/call_tracer.h )
   s.files += %w( src/core/lib/channel/channel_args.cc )
diff --git a/grpc.gyp b/grpc.gyp
index dd13cf0..58d0b5a 100644
--- a/grpc.gyp
+++ b/grpc.gyp
@@ -816,6 +816,7 @@
         'src/core/lib/address_utils/parse_address.cc',
         'src/core/lib/address_utils/sockaddr_utils.cc',
         'src/core/lib/backoff/backoff.cc',
+        'src/core/lib/backoff/random_early_detection.cc',
         'src/core/lib/channel/channel_args.cc',
         'src/core/lib/channel/channel_args_preconditioning.cc',
         'src/core/lib/channel/channel_stack.cc',
@@ -1298,6 +1299,7 @@
         'src/core/lib/address_utils/parse_address.cc',
         'src/core/lib/address_utils/sockaddr_utils.cc',
         'src/core/lib/backoff/backoff.cc',
+        'src/core/lib/backoff/random_early_detection.cc',
         'src/core/lib/channel/channel_args.cc',
         'src/core/lib/channel/channel_args_preconditioning.cc',
         'src/core/lib/channel/channel_stack.cc',
diff --git a/include/grpc/impl/grpc_types.h b/include/grpc/impl/grpc_types.h
index 7206cca..e3a268d 100644
--- a/include/grpc/impl/grpc_types.h
+++ b/include/grpc/impl/grpc_types.h
@@ -293,9 +293,18 @@
  *  protector.
  */
 #define GRPC_ARG_TSI_MAX_FRAME_SIZE "grpc.tsi.max_frame_size"
-/** Maximum metadata size, in bytes. Note this limit applies to the max sum of
-    all metadata key-value entries in a batch of headers. */
+/** Maximum metadata size (soft limit), in bytes. Note this limit applies to the
+   max sum of all metadata key-value entries in a batch of headers. Some random
+   sample of requests between this limit and
+   `GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE` will be rejected. Defaults to maximum
+   of 8 KB and `GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE` * 0.8 (if set).
+ */
 #define GRPC_ARG_MAX_METADATA_SIZE "grpc.max_metadata_size"
+/** Maximum metadata size (hard limit), in bytes. Note this limit applies to the
+   max sum of all metadata key-value entries in a batch of headers. All requests
+   exceeding this limit will be rejected. Defaults to maximum of 16 KB and
+   `GRPC_ARG_MAX_METADATA_SIZE` * 1.25 (if set). */
+#define GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE "grpc.absolute_max_metadata_size"
 /** If non-zero, allow the use of SO_REUSEPORT if it's available (default 1) */
 #define GRPC_ARG_ALLOW_REUSEPORT "grpc.so_reuseport"
 /** If non-zero, a pointer to a buffer pool (a pointer of type
diff --git a/package.xml b/package.xml
index 6c566ab..758caff 100644
--- a/package.xml
+++ b/package.xml
@@ -974,6 +974,8 @@
     <file baseinstalldir="/" name="src/core/lib/avl/avl.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/backoff/backoff.cc" role="src" />
     <file baseinstalldir="/" name="src/core/lib/backoff/backoff.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/backoff/random_early_detection.cc" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/backoff/random_early_detection.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/channel/call_finalization.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/channel/call_tracer.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/channel/channel_args.cc" role="src" />
diff --git a/src/core/ext/filters/client_channel/http_proxy.cc b/src/core/ext/filters/client_channel/http_proxy.cc
index 113e5e6..67dbd3c 100644
--- a/src/core/ext/filters/client_channel/http_proxy.cc
+++ b/src/core/ext/filters/client_channel/http_proxy.cc
@@ -76,7 +76,7 @@
   if (!uri_str.has_value()) uri_str = GetEnv("https_proxy");
   if (!uri_str.has_value()) uri_str = GetEnv("http_proxy");
   if (!uri_str.has_value()) return absl::nullopt;
-  // an emtpy value means "don't use proxy"
+  // an empty value means "don't use proxy"
   if (uri_str->empty()) return absl::nullopt;
   uri = URI::Parse(*uri_str);
   if (!uri.ok() || uri->authority().empty()) {
diff --git a/src/core/ext/filters/client_channel/lb_policy/rls/rls.cc b/src/core/ext/filters/client_channel/lb_policy/rls/rls.cc
index cc3fcf8..2e33f62 100644
--- a/src/core/ext/filters/client_channel/lb_policy/rls/rls.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/rls/rls.cc
@@ -116,7 +116,7 @@
 const char kGrpc[] = "grpc";
 const char* kRlsRequestPath = "/grpc.lookup.v1.RouteLookupService/RouteLookup";
 const char* kFakeTargetFieldValue = "fake_target_field_value";
-const char* kRlsHeaderKey = "X-Google-RLS-Data";
+const char* kRlsHeaderKey = "x-google-rls-data";
 
 const Duration kDefaultLookupServiceTimeout = Duration::Seconds(10);
 const Duration kMaxMaxAge = Duration::Minutes(5);
diff --git a/src/core/ext/transport/chaotic_good/frame.cc b/src/core/ext/transport/chaotic_good/frame.cc
index ad26079..76b33e7 100644
--- a/src/core/ext/transport/chaotic_good/frame.cc
+++ b/src/core/ext/transport/chaotic_good/frame.cc
@@ -156,6 +156,7 @@
   Arena::PoolPtr<Metadata> metadata;
   parser->BeginFrame(
       metadata.get(), std::numeric_limits<uint32_t>::max(),
+      std::numeric_limits<uint32_t>::max(),
       is_header ? HPackParser::Boundary::EndOfHeaders
                 : HPackParser::Boundary::EndOfStream,
       HPackParser::Priority::None,
diff --git a/src/core/ext/transport/chttp2/transport/bin_encoder.cc b/src/core/ext/transport/chttp2/transport/bin_encoder.cc
index 272907b..033b4cb 100644
--- a/src/core/ext/transport/chttp2/transport/bin_encoder.cc
+++ b/src/core/ext/transport/chttp2/transport/bin_encoder.cc
@@ -149,7 +149,8 @@
   }
 }
 
-static void enc_add2(huff_out* out, uint8_t a, uint8_t b) {
+static void enc_add2(huff_out* out, uint8_t a, uint8_t b, uint32_t* wire_size) {
+  *wire_size += 2;
   b64_huff_sym sa = huff_alphabet[a];
   b64_huff_sym sb = huff_alphabet[b];
   out->temp = (out->temp << (sa.length + sb.length)) |
@@ -159,7 +160,8 @@
   enc_flush_some(out);
 }
 
-static void enc_add1(huff_out* out, uint8_t a) {
+static void enc_add1(huff_out* out, uint8_t a, uint32_t* wire_size) {
+  *wire_size += 1;
   b64_huff_sym sa = huff_alphabet[a];
   out->temp = (out->temp << sa.length) | sa.bits;
   out->temp_length += sa.length;
@@ -167,7 +169,7 @@
 }
 
 grpc_slice grpc_chttp2_base64_encode_and_huffman_compress(
-    const grpc_slice& input) {
+    const grpc_slice& input, uint32_t* wire_size) {
   size_t input_length = GRPC_SLICE_LENGTH(input);
   size_t input_triplets = input_length / 3;
   size_t tail_case = input_length % 3;
@@ -183,16 +185,17 @@
   out.temp = 0;
   out.temp_length = 0;
   out.out = start_out;
+  *wire_size = 0;
 
   // encode full triplets
   for (i = 0; i < input_triplets; i++) {
     const uint8_t low_to_high = static_cast<uint8_t>((in[0] & 0x3) << 4);
     const uint8_t high_to_low = in[1] >> 4;
-    enc_add2(&out, in[0] >> 2, low_to_high | high_to_low);
+    enc_add2(&out, in[0] >> 2, low_to_high | high_to_low, wire_size);
 
     const uint8_t a = static_cast<uint8_t>((in[1] & 0xf) << 2);
     const uint8_t b = (in[2] >> 6);
-    enc_add2(&out, a | b, in[2] & 0x3f);
+    enc_add2(&out, a | b, in[2] & 0x3f, wire_size);
     in += 3;
   }
 
@@ -201,14 +204,15 @@
     case 0:
       break;
     case 1:
-      enc_add2(&out, in[0] >> 2, static_cast<uint8_t>((in[0] & 0x3) << 4));
+      enc_add2(&out, in[0] >> 2, static_cast<uint8_t>((in[0] & 0x3) << 4),
+               wire_size);
       in += 1;
       break;
     case 2: {
       const uint8_t low_to_high = static_cast<uint8_t>((in[0] & 0x3) << 4);
       const uint8_t high_to_low = in[1] >> 4;
-      enc_add2(&out, in[0] >> 2, low_to_high | high_to_low);
-      enc_add1(&out, static_cast<uint8_t>((in[1] & 0xf) << 2));
+      enc_add2(&out, in[0] >> 2, low_to_high | high_to_low, wire_size);
+      enc_add1(&out, static_cast<uint8_t>((in[1] & 0xf) << 2), wire_size);
       in += 2;
       break;
     }
diff --git a/src/core/ext/transport/chttp2/transport/bin_encoder.h b/src/core/ext/transport/chttp2/transport/bin_encoder.h
index ec0ef0b..1b43d01 100644
--- a/src/core/ext/transport/chttp2/transport/bin_encoder.h
+++ b/src/core/ext/transport/chttp2/transport/bin_encoder.h
@@ -21,6 +21,8 @@
 
 #include <grpc/support/port_platform.h>
 
+#include <stdint.h>
+
 #include <grpc/slice.h>
 
 // base64 encode a slice. Returns a new slice, does not take ownership of the
@@ -36,7 +38,9 @@
 // grpc_slice y = grpc_chttp2_huffman_compress(x);
 // grpc_core::CSliceUnref( x);
 // return y;
+// *wire_size is the length of the base64 encoded string prior to huffman
+// compression (as is needed for hpack table math)
 grpc_slice grpc_chttp2_base64_encode_and_huffman_compress(
-    const grpc_slice& input);
+    const grpc_slice& input, uint32_t* wire_size);
 
 #endif  // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_BIN_ENCODER_H
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
index 9706bd9..403f0c5 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
@@ -96,7 +96,8 @@
 #define DEFAULT_CONNECTION_WINDOW_TARGET (1024 * 1024)
 #define MAX_WINDOW 0x7fffffffu
 #define MAX_WRITE_BUFFER_SIZE (64 * 1024 * 1024)
-#define DEFAULT_MAX_HEADER_LIST_SIZE (8 * 1024)
+#define DEFAULT_MAX_HEADER_LIST_SIZE (16 * 1024)
+#define DEFAULT_MAX_HEADER_LIST_SIZE_SOFT_LIMIT (8 * 1024)
 
 #define DEFAULT_CLIENT_KEEPALIVE_TIME_MS INT_MAX
 #define DEFAULT_CLIENT_KEEPALIVE_TIMEOUT_MS 20000  // 20 seconds
@@ -348,6 +349,21 @@
                 .GetObjectRef<grpc_core::channelz::SocketNode::Security>());
   }
 
+  const int soft_limit =
+      channel_args.GetInt(GRPC_ARG_MAX_METADATA_SIZE).value_or(-1);
+  if (soft_limit < 0) {
+    // Set soft limit to 0.8 * hard limit if this is larger than
+    // `DEFAULT_MAX_HEADER_LIST_SIZE_SOFT_LIMIT` and
+    // `GRPC_ARG_MAX_METADATA_SIZE` is not set.
+    t->max_header_list_size_soft_limit = std::max(
+        DEFAULT_MAX_HEADER_LIST_SIZE_SOFT_LIMIT,
+        static_cast<int>(
+            0.8 * channel_args.GetInt(GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE)
+                      .value_or(-1)));
+  } else {
+    t->max_header_list_size_soft_limit = soft_limit;
+  }
+
   static const struct {
     absl::string_view channel_arg_name;
     grpc_chttp2_setting_id setting_id;
@@ -367,7 +383,7 @@
                        0,
                        INT32_MAX,
                        {true, true}},
-                      {GRPC_ARG_MAX_METADATA_SIZE,
+                      {GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE,
                        GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE,
                        -1,
                        0,
@@ -400,6 +416,21 @@
       if (value >= 0) {
         queue_setting_update(t, setting.setting_id,
                              grpc_core::Clamp(value, setting.min, setting.max));
+      } else if (setting.setting_id ==
+                 GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE) {
+        // Set value to 1.25 * soft limit if this is larger than
+        // `DEFAULT_MAX_HEADER_LIST_SIZE` and
+        // `GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE` is not set.
+        const int soft_limit = channel_args.GetInt(GRPC_ARG_MAX_METADATA_SIZE)
+                                   .value_or(setting.default_value);
+        const int value = (soft_limit < (INT_MAX / 1.25))
+                              ? static_cast<int>(soft_limit * 1.25)
+                              : soft_limit;
+        if (value > DEFAULT_MAX_HEADER_LIST_SIZE) {
+          queue_setting_update(
+              t, setting.setting_id,
+              grpc_core::Clamp(value, setting.min, setting.max));
+        }
       }
     } else if (channel_args.Contains(setting.channel_arg_name)) {
       gpr_log(GPR_DEBUG, "%s is not available on %s",
diff --git a/src/core/ext/transport/chttp2/transport/hpack_encoder.cc b/src/core/ext/transport/chttp2/transport/hpack_encoder.cc
index 93c80a7..dc99bdc 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_encoder.cc
+++ b/src/core/ext/transport/chttp2/transport/hpack_encoder.cc
@@ -109,33 +109,56 @@
   }
 }
 
-void HPackCompressor::Encoder::EmitIndexed(uint32_t elem_index) {
-  VarintWriter<1> w(elem_index);
-  w.Write(0x80, output_.AddTiny(w.length()));
+void HPackCompressor::SetMaxUsableSize(uint32_t max_table_size) {
+  max_usable_size_ = max_table_size;
+  SetMaxTableSize(std::min(table_.max_size(), max_table_size));
 }
 
+void HPackCompressor::SetMaxTableSize(uint32_t max_table_size) {
+  if (table_.SetMaxSize(std::min(max_usable_size_, max_table_size))) {
+    advertise_table_size_change_ = true;
+    if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
+      gpr_log(GPR_INFO, "set max table size from encoder to %d",
+              max_table_size);
+    }
+  }
+}
+
+namespace {
 struct WireValue {
   WireValue(uint8_t huffman_prefix, bool insert_null_before_wire_value,
             Slice slice)
       : data(std::move(slice)),
         huffman_prefix(huffman_prefix),
         insert_null_before_wire_value(insert_null_before_wire_value),
-        length(data.length() + (insert_null_before_wire_value ? 1 : 0)) {}
+        length(data.length() + (insert_null_before_wire_value ? 1 : 0)),
+        hpack_length(length) {}
+  WireValue(uint8_t huffman_prefix, bool insert_null_before_wire_value,
+            Slice slice, size_t hpack_length)
+      : data(std::move(slice)),
+        huffman_prefix(huffman_prefix),
+        insert_null_before_wire_value(insert_null_before_wire_value),
+        length(data.length() + (insert_null_before_wire_value ? 1 : 0)),
+        hpack_length(hpack_length + (insert_null_before_wire_value ? 1 : 0)) {}
   Slice data;
   const uint8_t huffman_prefix;
   const bool insert_null_before_wire_value;
   const size_t length;
+  const size_t hpack_length;
 };
 
-static WireValue GetWireValue(Slice value, bool true_binary_enabled,
-                              bool is_bin_hdr) {
+// Construct a wire value from a slice.
+// true_binary_enabled => use the true binary system
+// is_bin_hdr => the header is -bin suffixed
+WireValue GetWireValue(Slice value, bool true_binary_enabled, bool is_bin_hdr) {
   if (is_bin_hdr) {
     if (true_binary_enabled) {
       return WireValue(0x00, true, std::move(value));
     } else {
-      return WireValue(0x80, false,
-                       Slice(grpc_chttp2_base64_encode_and_huffman_compress(
-                           value.c_slice())));
+      uint32_t hpack_length;
+      Slice output(grpc_chttp2_base64_encode_and_huffman_compress(
+          value.c_slice(), &hpack_length));
+      return WireValue(0x80, false, std::move(output), hpack_length);
     }
   } else {
     // TODO(ctiller): opportunistically compress non-binary headers
@@ -175,6 +198,8 @@
 
   Slice data() { return std::move(wire_value_.data); }
 
+  uint32_t hpack_length() { return wire_value_.hpack_length; }
+
  private:
   WireValue wire_value_;
   VarintWriter<1> len_val_;
@@ -214,19 +239,33 @@
   Slice key_;
   VarintWriter<1> len_key_;
 };
+}  // namespace
 
-void HPackCompressor::Encoder::EmitLitHdrWithNonBinaryStringKeyIncIdx(
-    Slice key_slice, Slice value_slice) {
+namespace hpack_encoder_detail {
+void Encoder::EmitIndexed(uint32_t elem_index) {
+  VarintWriter<1> w(elem_index);
+  w.Write(0x80, output_.AddTiny(w.length()));
+}
+
+uint32_t Encoder::EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
+                                                         Slice value_slice) {
+  auto key_len = key_slice.length();
+  auto value_len = value_slice.length();
   StringKey key(std::move(key_slice));
   key.WritePrefix(0x40, output_.AddTiny(key.prefix_length()));
   output_.Append(key.key());
   NonBinaryStringValue emit(std::move(value_slice));
   emit.WritePrefix(output_.AddTiny(emit.prefix_length()));
+  // Allocate an index in the hpack table for this newly emitted entry.
+  // (we do so here because we know the length of the key and value)
+  uint32_t index = compressor_->table_.AllocateIndex(
+      key_len + value_len + hpack_constants::kEntryOverhead);
   output_.Append(emit.data());
+  return index;
 }
 
-void HPackCompressor::Encoder::EmitLitHdrWithBinaryStringKeyNotIdx(
-    Slice key_slice, Slice value_slice) {
+void Encoder::EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice,
+                                                  Slice value_slice) {
   StringKey key(std::move(key_slice));
   key.WritePrefix(0x00, output_.AddTiny(key.prefix_length()));
   output_.Append(key.key());
@@ -235,18 +274,24 @@
   output_.Append(emit.data());
 }
 
-void HPackCompressor::Encoder::EmitLitHdrWithBinaryStringKeyIncIdx(
-    Slice key_slice, Slice value_slice) {
+uint32_t Encoder::EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,
+                                                      Slice value_slice) {
+  auto key_len = key_slice.length();
   StringKey key(std::move(key_slice));
   key.WritePrefix(0x40, output_.AddTiny(key.prefix_length()));
   output_.Append(key.key());
   BinaryStringValue emit(std::move(value_slice), use_true_binary_metadata_);
   emit.WritePrefix(output_.AddTiny(emit.prefix_length()));
+  // Allocate an index in the hpack table for this newly emitted entry.
+  // (we do so here because we know the length of the key and value)
+  uint32_t index = compressor_->table_.AllocateIndex(
+      key_len + emit.hpack_length() + hpack_constants::kEntryOverhead);
   output_.Append(emit.data());
+  return index;
 }
 
-void HPackCompressor::Encoder::EmitLitHdrWithBinaryStringKeyNotIdx(
-    uint32_t key_index, Slice value_slice) {
+void Encoder::EmitLitHdrWithBinaryStringKeyNotIdx(uint32_t key_index,
+                                                  Slice value_slice) {
   BinaryStringValue emit(std::move(value_slice), use_true_binary_metadata_);
   VarintWriter<4> key(key_index);
   uint8_t* data = output_.AddTiny(key.length() + emit.prefix_length());
@@ -255,8 +300,8 @@
   output_.Append(emit.data());
 }
 
-void HPackCompressor::Encoder::EmitLitHdrWithNonBinaryStringKeyNotIdx(
-    Slice key_slice, Slice value_slice) {
+void Encoder::EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice key_slice,
+                                                     Slice value_slice) {
   StringKey key(std::move(key_slice));
   key.WritePrefix(0x00, output_.AddTiny(key.prefix_length()));
   output_.Append(key.key());
@@ -265,14 +310,14 @@
   output_.Append(emit.data());
 }
 
-void HPackCompressor::Encoder::AdvertiseTableSizeChange() {
+void Encoder::AdvertiseTableSizeChange() {
   VarintWriter<3> w(compressor_->table_.max_size());
   w.Write(0x20, output_.AddTiny(w.length()));
 }
 
-void HPackCompressor::SliceIndex::EmitTo(absl::string_view key,
-                                         const Slice& value, Encoder* encoder) {
-  auto& table = encoder->compressor_->table_;
+void SliceIndex::EmitTo(absl::string_view key, const Slice& value,
+                        Encoder* encoder) {
+  auto& table = encoder->hpack_table();
   using It = std::vector<ValueIndex>::iterator;
   It prev = values_.end();
   size_t transport_length =
@@ -291,8 +336,7 @@
         encoder->EmitIndexed(table.DynamicIndex(it->index));
       } else {
         // Not current, emit a new literal and update the index.
-        it->index = table.AllocateIndex(transport_length);
-        encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
+        it->index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
             Slice::FromStaticString(key), value.Ref());
       }
       // Bubble this entry up if we can - ensures that the most used values end
@@ -310,13 +354,12 @@
     prev = it;
   }
   // No hit, emit a new literal and add it to the index.
-  uint32_t index = table.AllocateIndex(transport_length);
-  encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice::FromStaticString(key),
-                                                  value.Ref());
+  uint32_t index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
+      Slice::FromStaticString(key), value.Ref());
   values_.emplace_back(value.Ref(), index);
 }
 
-void HPackCompressor::Encoder::Encode(const Slice& key, const Slice& value) {
+void Encoder::Encode(const Slice& key, const Slice& value) {
   if (absl::EndsWith(key.as_string_view(), "-bin")) {
     EmitLitHdrWithBinaryStringKeyNotIdx(key.Ref(), value.Ref());
   } else {
@@ -324,43 +367,14 @@
   }
 }
 
-void HPackCompressor::Encoder::Encode(HttpPathMetadata, const Slice& value) {
-  compressor_->path_index_.EmitTo(HttpPathMetadata::key(), value, this);
-}
-
-void HPackCompressor::Encoder::Encode(HttpAuthorityMetadata,
-                                      const Slice& value) {
-  compressor_->authority_index_.EmitTo(HttpAuthorityMetadata::key(), value,
-                                       this);
-}
-
-void HPackCompressor::Encoder::Encode(TeMetadata, TeMetadata::ValueType value) {
-  GPR_ASSERT(value == TeMetadata::ValueType::kTrailers);
-  EncodeAlwaysIndexed(
-      &compressor_->te_index_, "te", Slice::FromStaticString("trailers"),
-      2 /* te */ + 8 /* trailers */ + hpack_constants::kEntryOverhead);
-}
-
-void HPackCompressor::Encoder::Encode(ContentTypeMetadata,
-                                      ContentTypeMetadata::ValueType value) {
-  if (value != ContentTypeMetadata::ValueType::kApplicationGrpc) {
-    gpr_log(GPR_ERROR, "Not encoding bad content-type header");
-    return;
-  }
-  EncodeAlwaysIndexed(&compressor_->content_type_index_, "content-type",
-                      Slice::FromStaticString("application/grpc"),
-                      12 /* content-type */ + 16 /* application/grpc */ +
-                          hpack_constants::kEntryOverhead);
-}
-
-void HPackCompressor::Encoder::Encode(HttpSchemeMetadata,
-                                      HttpSchemeMetadata::ValueType value) {
+void Compressor<HttpSchemeMetadata, HttpSchemeCompressor>::EncodeWith(
+    HttpSchemeMetadata, HttpSchemeMetadata::ValueType value, Encoder* encoder) {
   switch (value) {
     case HttpSchemeMetadata::ValueType::kHttp:
-      EmitIndexed(6);  // :scheme: http
+      encoder->EmitIndexed(6);  // :scheme: http
       break;
     case HttpSchemeMetadata::ValueType::kHttps:
-      EmitIndexed(7);  // :scheme: https
+      encoder->EmitIndexed(7);  // :scheme: https
       break;
     case HttpSchemeMetadata::ValueType::kInvalid:
       Crash("invalid http scheme encoding");
@@ -368,22 +382,10 @@
   }
 }
 
-void HPackCompressor::Encoder::Encode(GrpcTraceBinMetadata,
-                                      const Slice& slice) {
-  EncodeRepeatingSliceValue(GrpcTraceBinMetadata::key(), slice,
-                            &compressor_->grpc_trace_bin_index_,
-                            HPackEncoderTable::MaxEntrySize());
-}
-
-void HPackCompressor::Encoder::Encode(GrpcTagsBinMetadata, const Slice& slice) {
-  EncodeRepeatingSliceValue(GrpcTagsBinMetadata::key(), slice,
-                            &compressor_->grpc_tags_bin_index_,
-                            HPackEncoderTable::MaxEntrySize());
-}
-
-void HPackCompressor::Encoder::Encode(HttpStatusMetadata, uint32_t status) {
+void Compressor<HttpStatusMetadata, HttpStatusCompressor>::EncodeWith(
+    HttpStatusMetadata, uint32_t status, Encoder* encoder) {
   if (status == 200) {
-    EmitIndexed(8);  // :status: 200
+    encoder->EmitIndexed(8);  // :status: 200
     return;
   }
   uint8_t index = 0;
@@ -408,27 +410,28 @@
       break;
   }
   if (GPR_LIKELY(index != 0)) {
-    EmitIndexed(index);
+    encoder->EmitIndexed(index);
   } else {
-    EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice::FromStaticString(":status"),
-                                           Slice::FromInt64(status));
+    encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
+        Slice::FromStaticString(":status"), Slice::FromInt64(status));
   }
 }
 
-void HPackCompressor::Encoder::Encode(HttpMethodMetadata,
-                                      HttpMethodMetadata::ValueType method) {
+void Compressor<HttpMethodMetadata, HttpMethodCompressor>::EncodeWith(
+    HttpMethodMetadata, HttpMethodMetadata::ValueType method,
+    Encoder* encoder) {
   switch (method) {
     case HttpMethodMetadata::ValueType::kPost:
-      EmitIndexed(3);  // :method: POST
+      encoder->EmitIndexed(3);  // :method: POST
       break;
     case HttpMethodMetadata::ValueType::kGet:
-      EmitIndexed(2);  // :method: GET
+      encoder->EmitIndexed(2);  // :method: GET
       break;
     case HttpMethodMetadata::ValueType::kPut:
       // Right now, we only emit PUT as a method for testing purposes, so it's
       // fine to not index it.
-      EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice::FromStaticString(":method"),
-                                             Slice::FromStaticString("PUT"));
+      encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
+          Slice::FromStaticString(":method"), Slice::FromStaticString("PUT"));
       break;
     case HttpMethodMetadata::ValueType::kInvalid:
       Crash("invalid http method encoding");
@@ -436,35 +439,31 @@
   }
 }
 
-void HPackCompressor::Encoder::EncodeAlwaysIndexed(uint32_t* index,
-                                                   absl::string_view key,
-                                                   Slice value,
-                                                   size_t transport_length) {
+void Encoder::EncodeAlwaysIndexed(uint32_t* index, absl::string_view key,
+                                  Slice value, size_t) {
   if (compressor_->table_.ConvertableToDynamicIndex(*index)) {
     EmitIndexed(compressor_->table_.DynamicIndex(*index));
   } else {
-    *index = compressor_->table_.AllocateIndex(transport_length);
-    EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice::FromStaticString(key),
-                                           std::move(value));
+    *index = EmitLitHdrWithNonBinaryStringKeyIncIdx(
+        Slice::FromStaticString(key), std::move(value));
   }
 }
 
-void HPackCompressor::Encoder::EncodeIndexedKeyWithBinaryValue(
-    uint32_t* index, absl::string_view key, Slice value) {
+void Encoder::EncodeIndexedKeyWithBinaryValue(uint32_t* index,
+                                              absl::string_view key,
+                                              Slice value) {
   if (compressor_->table_.ConvertableToDynamicIndex(*index)) {
     EmitLitHdrWithBinaryStringKeyNotIdx(
         compressor_->table_.DynamicIndex(*index), std::move(value));
   } else {
-    *index = compressor_->table_.AllocateIndex(key.length() + value.length() +
-                                               hpack_constants::kEntryOverhead);
-    EmitLitHdrWithBinaryStringKeyIncIdx(Slice::FromStaticString(key),
-                                        std::move(value));
+    *index = EmitLitHdrWithBinaryStringKeyIncIdx(Slice::FromStaticString(key),
+                                                 std::move(value));
   }
 }
 
-void HPackCompressor::Encoder::EncodeRepeatingSliceValue(
-    const absl::string_view& key, const Slice& slice, uint32_t* index,
-    size_t max_compression_size) {
+void Encoder::EncodeRepeatingSliceValue(const absl::string_view& key,
+                                        const Slice& slice, uint32_t* index,
+                                        size_t max_compression_size) {
   if (hpack_constants::SizeForEntry(key.size(), slice.size()) >
       max_compression_size) {
     EmitLitHdrWithBinaryStringKeyNotIdx(Slice::FromStaticString(key),
@@ -474,141 +473,37 @@
   }
 }
 
-void HPackCompressor::Encoder::Encode(GrpcTimeoutMetadata, Timestamp deadline) {
+void TimeoutCompressorImpl::EncodeWith(absl::string_view key,
+                                       Timestamp deadline, Encoder* encoder) {
   Timeout timeout = Timeout::FromDuration(deadline - Timestamp::Now());
-  for (auto it = compressor_->previous_timeouts_.begin();
-       it != compressor_->previous_timeouts_.end(); ++it) {
+  auto& table = encoder->hpack_table();
+  for (auto it = previous_timeouts_.begin(); it != previous_timeouts_.end();
+       ++it) {
     double ratio = timeout.RatioVersus(it->timeout);
     // If the timeout we're sending is shorter than a previous timeout, but
     // within 3% of it, we'll consider sending it.
     if (ratio > -3 && ratio <= 0 &&
-        compressor_->table_.ConvertableToDynamicIndex(it->index)) {
-      EmitIndexed(compressor_->table_.DynamicIndex(it->index));
+        table.ConvertableToDynamicIndex(it->index)) {
+      encoder->EmitIndexed(table.DynamicIndex(it->index));
       // Put this timeout to the front of the queue - forces common timeouts to
       // be considered earlier.
-      std::swap(*it, *compressor_->previous_timeouts_.begin());
+      std::swap(*it, *previous_timeouts_.begin());
       return;
     }
   }
   // Clean out some expired timeouts.
-  while (!compressor_->previous_timeouts_.empty() &&
-         !compressor_->table_.ConvertableToDynamicIndex(
-             compressor_->previous_timeouts_.back().index)) {
-    compressor_->previous_timeouts_.pop_back();
+  while (!previous_timeouts_.empty() &&
+         !table.ConvertableToDynamicIndex(previous_timeouts_.back().index)) {
+    previous_timeouts_.pop_back();
   }
   Slice encoded = timeout.Encode();
-  uint32_t index = compressor_->table_.AllocateIndex(
-      GrpcTimeoutMetadata::key().length() + encoded.length() +
-      hpack_constants::kEntryOverhead);
-  compressor_->previous_timeouts_.push_back(PreviousTimeout{timeout, index});
-  EmitLitHdrWithNonBinaryStringKeyIncIdx(
-      Slice::FromStaticString(GrpcTimeoutMetadata::key()), std::move(encoded));
+  uint32_t index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
+      Slice::FromStaticString(key), std::move(encoded));
+  previous_timeouts_.push_back(PreviousTimeout{timeout, index});
 }
 
-void HPackCompressor::Encoder::Encode(UserAgentMetadata, const Slice& slice) {
-  if (hpack_constants::SizeForEntry(UserAgentMetadata::key().size(),
-                                    slice.size()) >
-      HPackEncoderTable::MaxEntrySize()) {
-    EmitLitHdrWithNonBinaryStringKeyNotIdx(
-        Slice::FromStaticString(UserAgentMetadata::key()), slice.Ref());
-    return;
-  }
-  if (!slice.is_equivalent(compressor_->user_agent_)) {
-    compressor_->user_agent_ = slice.Ref();
-    compressor_->user_agent_index_ = 0;
-  }
-  EncodeAlwaysIndexed(&compressor_->user_agent_index_, UserAgentMetadata::key(),
-                      slice.Ref(),
-                      hpack_constants::SizeForEntry(
-                          UserAgentMetadata::key().size(), slice.size()));
-}
-
-void HPackCompressor::Encoder::Encode(GrpcStatusMetadata,
-                                      grpc_status_code status) {
-  const uint32_t code = static_cast<uint32_t>(status);
-  uint32_t* index = nullptr;
-  if (code < kNumCachedGrpcStatusValues) {
-    index = &compressor_->cached_grpc_status_[code];
-    if (compressor_->table_.ConvertableToDynamicIndex(*index)) {
-      EmitIndexed(compressor_->table_.DynamicIndex(*index));
-      return;
-    }
-  }
-  Slice key = Slice::FromStaticString(GrpcStatusMetadata::key());
-  Slice value = Slice::FromInt64(code);
-  const size_t transport_length =
-      key.length() + value.length() + hpack_constants::kEntryOverhead;
-  if (index != nullptr) {
-    *index = compressor_->table_.AllocateIndex(transport_length);
-    EmitLitHdrWithNonBinaryStringKeyIncIdx(std::move(key), std::move(value));
-  } else {
-    EmitLitHdrWithNonBinaryStringKeyNotIdx(std::move(key), std::move(value));
-  }
-}
-
-void HPackCompressor::Encoder::Encode(GrpcEncodingMetadata,
-                                      grpc_compression_algorithm value) {
-  uint32_t* index = nullptr;
-  if (value < GRPC_COMPRESS_ALGORITHMS_COUNT) {
-    index = &compressor_->cached_grpc_encoding_[static_cast<uint32_t>(value)];
-    if (compressor_->table_.ConvertableToDynamicIndex(*index)) {
-      EmitIndexed(compressor_->table_.DynamicIndex(*index));
-      return;
-    }
-  }
-  auto key = Slice::FromStaticString(GrpcEncodingMetadata::key());
-  auto encoded_value = GrpcEncodingMetadata::Encode(value);
-  size_t transport_length =
-      key.length() + encoded_value.length() + hpack_constants::kEntryOverhead;
-  if (index != nullptr) {
-    *index = compressor_->table_.AllocateIndex(transport_length);
-    EmitLitHdrWithNonBinaryStringKeyIncIdx(std::move(key),
-                                           std::move(encoded_value));
-  } else {
-    EmitLitHdrWithNonBinaryStringKeyNotIdx(std::move(key),
-                                           std::move(encoded_value));
-  }
-}
-
-void HPackCompressor::Encoder::Encode(GrpcAcceptEncodingMetadata,
-                                      CompressionAlgorithmSet value) {
-  if (compressor_->grpc_accept_encoding_index_ != 0 &&
-      value == compressor_->grpc_accept_encoding_ &&
-      compressor_->table_.ConvertableToDynamicIndex(
-          compressor_->grpc_accept_encoding_index_)) {
-    EmitIndexed(compressor_->table_.DynamicIndex(
-        compressor_->grpc_accept_encoding_index_));
-    return;
-  }
-  auto key = Slice::FromStaticString(GrpcAcceptEncodingMetadata::key());
-  auto encoded_value = GrpcAcceptEncodingMetadata::Encode(value);
-  size_t transport_length =
-      key.length() + encoded_value.length() + hpack_constants::kEntryOverhead;
-  compressor_->grpc_accept_encoding_index_ =
-      compressor_->table_.AllocateIndex(transport_length);
-  compressor_->grpc_accept_encoding_ = value;
-  EmitLitHdrWithNonBinaryStringKeyIncIdx(std::move(key),
-                                         std::move(encoded_value));
-}
-
-void HPackCompressor::SetMaxUsableSize(uint32_t max_table_size) {
-  max_usable_size_ = max_table_size;
-  SetMaxTableSize(std::min(table_.max_size(), max_table_size));
-}
-
-void HPackCompressor::SetMaxTableSize(uint32_t max_table_size) {
-  if (table_.SetMaxSize(std::min(max_usable_size_, max_table_size))) {
-    advertise_table_size_change_ = true;
-    if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) {
-      gpr_log(GPR_INFO, "set max table size from encoder to %d",
-              max_table_size);
-    }
-  }
-}
-
-HPackCompressor::Encoder::Encoder(HPackCompressor* compressor,
-                                  bool use_true_binary_metadata,
-                                  SliceBuffer& output)
+Encoder::Encoder(HPackCompressor* compressor, bool use_true_binary_metadata,
+                 SliceBuffer& output)
     : use_true_binary_metadata_(use_true_binary_metadata),
       compressor_(compressor),
       output_(output) {
@@ -617,4 +512,5 @@
   }
 }
 
+}  // namespace hpack_encoder_detail
 }  // namespace grpc_core
diff --git a/src/core/ext/transport/chttp2/transport/hpack_encoder.h b/src/core/ext/transport/chttp2/transport/hpack_encoder.h
index 6f8e136..c22c8d7 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_encoder.h
+++ b/src/core/ext/transport/chttp2/transport/hpack_encoder.h
@@ -28,15 +28,14 @@
 #include <vector>
 
 #include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 
-#include <grpc/impl/compression_types.h>
 #include <grpc/slice.h>
-#include <grpc/status.h>
+#include <grpc/support/log.h>
 
 #include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
 #include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h"
-#include "src/core/lib/compression/compression_internal.h"
 #include "src/core/lib/gprpp/time.h"
 #include "src/core/lib/slice/slice.h"
 #include "src/core/lib/slice/slice_buffer.h"
@@ -46,6 +45,280 @@
 
 namespace grpc_core {
 
+// Forward decl for encoder
+class HPackCompressor;
+
+namespace hpack_encoder_detail {
+
+class Encoder {
+ public:
+  Encoder(HPackCompressor* compressor, bool use_true_binary_metadata,
+          SliceBuffer& output);
+
+  void Encode(const Slice& key, const Slice& value);
+  template <typename MetadataTrait>
+  void Encode(MetadataTrait, const typename MetadataTrait::ValueType& value);
+
+  void AdvertiseTableSizeChange();
+  void EmitIndexed(uint32_t index);
+  GRPC_MUST_USE_RESULT
+  uint32_t EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
+                                                  Slice value_slice);
+  GRPC_MUST_USE_RESULT
+  uint32_t EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,
+                                               Slice value_slice);
+  void EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice, Slice value_slice);
+  void EmitLitHdrWithBinaryStringKeyNotIdx(uint32_t key_index,
+                                           Slice value_slice);
+  void EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice key_slice,
+                                              Slice value_slice);
+
+  void EncodeAlwaysIndexed(uint32_t* index, absl::string_view key, Slice value,
+                           size_t transport_length);
+  void EncodeIndexedKeyWithBinaryValue(uint32_t* index, absl::string_view key,
+                                       Slice value);
+
+  void EncodeRepeatingSliceValue(const absl::string_view& key,
+                                 const Slice& slice, uint32_t* index,
+                                 size_t max_compression_size);
+
+  HPackEncoderTable& hpack_table();
+
+ private:
+  const bool use_true_binary_metadata_;
+  HPackCompressor* const compressor_;
+  SliceBuffer& output_;
+};
+
+// Compressor is partially specialized on CompressionTraits, but leaves
+// MetadataTrait as variable.
+// Via MetadataMap::StatefulCompressor it builds compression state for
+// HPackCompressor.
+// Each trait compressor gets to have some persistent state across the channel
+// (declared as Compressor member variables).
+// The compressors expose a single method:
+// void EncodeWith(MetadataTrait, const MetadataTrait::ValueType, Encoder*);
+// This method figures out how to encode the value, and then delegates to
+// Encoder to perform the encoding.
+template <typename MetadataTrait, typename CompressonTraits>
+class Compressor;
+
+// No compression encoder: just emit the key and value as literals.
+template <typename MetadataTrait>
+class Compressor<MetadataTrait, NoCompressionCompressor> {
+ public:
+  void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
+                  Encoder* encoder) {
+    const Slice& slice = MetadataValueAsSlice<MetadataTrait>(value);
+    if (absl::EndsWith(MetadataTrait::key(), "-bin")) {
+      encoder->EmitLitHdrWithBinaryStringKeyNotIdx(
+          Slice::FromStaticString(MetadataTrait::key()), slice.Ref());
+    } else {
+      encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
+          Slice::FromStaticString(MetadataTrait::key()), slice.Ref());
+    }
+  }
+};
+
+// Frequent key with no value compression encoder
+template <typename MetadataTrait>
+class Compressor<MetadataTrait, FrequentKeyWithNoValueCompressionCompressor> {
+ public:
+  void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
+                  Encoder* encoder) {
+    const Slice& slice = MetadataValueAsSlice<MetadataTrait>(value);
+    encoder->EncodeRepeatingSliceValue(MetadataTrait::key(), slice,
+                                       &some_sent_value_,
+                                       HPackEncoderTable::MaxEntrySize());
+  }
+
+ private:
+  // Some previously sent value with this tag.
+  uint32_t some_sent_value_ = 0;
+};
+
+// Helper to determine if two objects have the same identity.
+// Equivalent here => equality, but equality does not imply equivalency.
+// For example, two slices with the same contents are equal, but not
+// equivalent.
+// Used as a much faster check for equality than the full equality check,
+// since many metadatum that are stable have the same root object in metadata
+// maps.
+template <typename T>
+static bool IsEquivalent(T a, T b) {
+  return a == b;
+}
+
+template <typename T>
+static bool IsEquivalent(const Slice& a, const Slice& b) {
+  return a.is_equivalent(b);
+}
+
+template <typename T>
+static void SaveCopyTo(const T& value, T& copy) {
+  copy = value;
+}
+
+static inline void SaveCopyTo(const Slice& value, Slice& copy) {
+  copy = value.Ref();
+}
+
+template <typename MetadataTrait>
+class Compressor<MetadataTrait, StableValueCompressor> {
+ public:
+  void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
+                  Encoder* encoder) {
+    auto& table = encoder->hpack_table();
+    if (previously_sent_value_ == value &&
+        table.ConvertableToDynamicIndex(previously_sent_index_)) {
+      encoder->EmitIndexed(table.DynamicIndex(previously_sent_index_));
+      return;
+    }
+    previously_sent_index_ = 0;
+    auto key = MetadataTrait::key();
+    const Slice& value_slice = MetadataValueAsSlice<MetadataTrait>(value);
+    if (hpack_constants::SizeForEntry(key.size(), value_slice.size()) >
+        HPackEncoderTable::MaxEntrySize()) {
+      encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
+          Slice::FromStaticString(key), value_slice.Ref());
+      return;
+    }
+    encoder->EncodeAlwaysIndexed(
+        &previously_sent_index_, key, value_slice.Ref(),
+        hpack_constants::SizeForEntry(key.size(), value_slice.size()));
+    SaveCopyTo(value, previously_sent_value_);
+  }
+
+ private:
+  // Previously sent value
+  typename MetadataTrait::ValueType previously_sent_value_{};
+  // And its index in the table
+  uint32_t previously_sent_index_ = 0;
+};
+
+template <typename MetadataTrait, typename MetadataTrait::ValueType known_value>
+class Compressor<
+    MetadataTrait,
+    KnownValueCompressor<typename MetadataTrait::ValueType, known_value>> {
+ public:
+  void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
+                  Encoder* encoder) {
+    if (value != known_value) {
+      gpr_log(GPR_ERROR, "%s",
+              absl::StrCat("Not encoding bad ", MetadataTrait::key(), " header")
+                  .c_str());
+      return;
+    }
+    Slice encoded(MetadataTrait::Encode(known_value));
+    const auto encoded_length = encoded.length();
+    encoder->EncodeAlwaysIndexed(&previously_sent_index_, MetadataTrait::key(),
+                                 std::move(encoded),
+                                 MetadataTrait::key().size() + encoded_length +
+                                     hpack_constants::kEntryOverhead);
+  }
+
+ private:
+  uint32_t previously_sent_index_ = 0;
+};
+template <typename MetadataTrait, size_t N>
+class Compressor<MetadataTrait, SmallIntegralValuesCompressor<N>> {
+ public:
+  void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
+                  Encoder* encoder) {
+    uint32_t* index = nullptr;
+    auto& table = encoder->hpack_table();
+    if (static_cast<size_t>(value) < N) {
+      index = &previously_sent_[static_cast<uint32_t>(value)];
+      if (table.ConvertableToDynamicIndex(*index)) {
+        encoder->EmitIndexed(table.DynamicIndex(*index));
+        return;
+      }
+    }
+    auto key = Slice::FromStaticString(MetadataTrait::key());
+    auto encoded_value = MetadataTrait::Encode(value);
+    if (index != nullptr) {
+      *index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
+          std::move(key), std::move(encoded_value));
+    } else {
+      encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(std::move(key),
+                                                      std::move(encoded_value));
+    }
+  }
+
+ private:
+  uint32_t previously_sent_[N] = {};
+};
+
+class SliceIndex {
+ public:
+  void EmitTo(absl::string_view key, const Slice& value, Encoder* encoder);
+
+ private:
+  struct ValueIndex {
+    ValueIndex(Slice value, uint32_t index)
+        : value(std::move(value)), index(index) {}
+    Slice value;
+    uint32_t index;
+  };
+  std::vector<ValueIndex> values_;
+};
+
+template <typename MetadataTrait>
+class Compressor<MetadataTrait, SmallSetOfValuesCompressor> {
+ public:
+  void EncodeWith(MetadataTrait, const Slice& value, Encoder* encoder) {
+    index_.EmitTo(MetadataTrait::key(), value, encoder);
+  }
+
+ private:
+  SliceIndex index_;
+};
+
+struct PreviousTimeout {
+  Timeout timeout;
+  uint32_t index;
+};
+
+class TimeoutCompressorImpl {
+ public:
+  void EncodeWith(absl::string_view key, Timestamp deadline, Encoder* encoder);
+
+ private:
+  std::vector<PreviousTimeout> previous_timeouts_;
+};
+
+template <typename MetadataTrait>
+class Compressor<MetadataTrait, TimeoutCompressor>
+    : public TimeoutCompressorImpl {
+ public:
+  void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
+                  Encoder* encoder) {
+    TimeoutCompressorImpl::EncodeWith(MetadataTrait::key(), value, encoder);
+  }
+};
+
+template <>
+class Compressor<HttpStatusMetadata, HttpStatusCompressor> {
+ public:
+  void EncodeWith(HttpStatusMetadata, uint32_t status, Encoder* encoder);
+};
+
+template <>
+class Compressor<HttpMethodMetadata, HttpMethodCompressor> {
+ public:
+  void EncodeWith(HttpMethodMetadata, HttpMethodMetadata::ValueType method,
+                  Encoder* encoder);
+};
+
+template <>
+class Compressor<HttpSchemeMetadata, HttpSchemeCompressor> {
+ public:
+  void EncodeWith(HttpSchemeMetadata, HttpSchemeMetadata::ValueType value,
+                  Encoder* encoder);
+};
+
+}  // namespace hpack_encoder_detail
+
 class HPackCompressor {
   class SliceIndex;
 
@@ -75,87 +348,22 @@
   void EncodeHeaders(const EncodeHeaderOptions& options,
                      const HeaderSet& headers, grpc_slice_buffer* output) {
     SliceBuffer raw;
-    Encoder encoder(this, options.use_true_binary_metadata, raw);
+    hpack_encoder_detail::Encoder encoder(
+        this, options.use_true_binary_metadata, raw);
     headers.Encode(&encoder);
     Frame(options, raw, output);
   }
 
   template <typename HeaderSet>
   void EncodeRawHeaders(const HeaderSet& headers, SliceBuffer& output) {
-    Encoder encoder(this, true, output);
+    hpack_encoder_detail::Encoder encoder(this, true, output);
     headers.Encode(&encoder);
   }
 
  private:
-  class Encoder {
-   public:
-    Encoder(HPackCompressor* compressor, bool use_true_binary_metadata,
-            SliceBuffer& output);
-
-    void Encode(const Slice& key, const Slice& value);
-    void Encode(HttpPathMetadata, const Slice& value);
-    void Encode(HttpAuthorityMetadata, const Slice& value);
-    void Encode(HttpStatusMetadata, uint32_t status);
-    void Encode(GrpcTimeoutMetadata, Timestamp deadline);
-    void Encode(TeMetadata, TeMetadata::ValueType value);
-    void Encode(ContentTypeMetadata, ContentTypeMetadata::ValueType value);
-    void Encode(HttpSchemeMetadata, HttpSchemeMetadata::ValueType value);
-    void Encode(HttpMethodMetadata, HttpMethodMetadata::ValueType method);
-    void Encode(UserAgentMetadata, const Slice& slice);
-    void Encode(GrpcStatusMetadata, grpc_status_code status);
-    void Encode(GrpcEncodingMetadata, grpc_compression_algorithm value);
-    void Encode(GrpcAcceptEncodingMetadata, CompressionAlgorithmSet value);
-    void Encode(GrpcTagsBinMetadata, const Slice& slice);
-    void Encode(GrpcTraceBinMetadata, const Slice& slice);
-    void Encode(GrpcMessageMetadata, const Slice& slice) {
-      if (slice.empty()) return;
-      EmitLitHdrWithNonBinaryStringKeyNotIdx(
-          Slice::FromStaticString("grpc-message"), slice.Ref());
-    }
-    template <typename Which>
-    void Encode(Which, const typename Which::ValueType& value) {
-      const Slice& slice = MetadataValueAsSlice<Which>(value);
-      if (absl::EndsWith(Which::key(), "-bin")) {
-        EmitLitHdrWithBinaryStringKeyNotIdx(
-            Slice::FromStaticString(Which::key()), slice.Ref());
-      } else {
-        EmitLitHdrWithNonBinaryStringKeyNotIdx(
-            Slice::FromStaticString(Which::key()), slice.Ref());
-      }
-    }
-
-   private:
-    friend class SliceIndex;
-
-    void AdvertiseTableSizeChange();
-    void EmitIndexed(uint32_t index);
-    void EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
-                                                Slice value_slice);
-    void EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,
-                                             Slice value_slice);
-    void EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice,
-                                             Slice value_slice);
-    void EmitLitHdrWithBinaryStringKeyNotIdx(uint32_t key_index,
-                                             Slice value_slice);
-    void EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice key_slice,
-                                                Slice value_slice);
-
-    void EncodeAlwaysIndexed(uint32_t* index, absl::string_view key,
-                             Slice value, size_t transport_length);
-    void EncodeIndexedKeyWithBinaryValue(uint32_t* index, absl::string_view key,
-                                         Slice value);
-
-    void EncodeRepeatingSliceValue(const absl::string_view& key,
-                                   const Slice& slice, uint32_t* index,
-                                   size_t max_compression_size);
-
-    const bool use_true_binary_metadata_;
-    HPackCompressor* const compressor_;
-    SliceBuffer& output_;
-  };
-
   static constexpr size_t kNumFilterValues = 64;
   static constexpr uint32_t kNumCachedGrpcStatusValues = 16;
+  friend class hpack_encoder_detail::Encoder;
 
   void Frame(const EncodeHeaderOptions& options, SliceBuffer& raw,
              grpc_slice_buffer* output);
@@ -168,50 +376,24 @@
   bool advertise_table_size_change_ = false;
   HPackEncoderTable table_;
 
-  class SliceIndex {
-   public:
-    void EmitTo(absl::string_view key, const Slice& value, Encoder* encoder);
-
-   private:
-    struct ValueIndex {
-      ValueIndex(Slice value, uint32_t index)
-          : value(std::move(value)), index(index) {}
-      Slice value;
-      uint32_t index;
-    };
-    std::vector<ValueIndex> values_;
-  };
-
-  struct PreviousTimeout {
-    Timeout timeout;
-    uint32_t index;
-  };
-
-  // Index into table_ for the te:trailers metadata element
-  uint32_t te_index_ = 0;
-  // Index into table_ for the content-type metadata element
-  uint32_t content_type_index_ = 0;
-  // Index into table_ for the user-agent metadata element
-  uint32_t user_agent_index_ = 0;
-  // Cached grpc-status values
-  uint32_t cached_grpc_status_[kNumCachedGrpcStatusValues] = {};
-  // Cached grpc-encoding values
-  uint32_t cached_grpc_encoding_[GRPC_COMPRESS_ALGORITHMS_COUNT] = {};
-  // Cached grpc-accept-encoding value
-  uint32_t grpc_accept_encoding_index_ = 0;
-  // The grpc-accept-encoding string referred to by grpc_accept_encoding_index_
-  CompressionAlgorithmSet grpc_accept_encoding_;
-  // Index of something that was sent with grpc-tags-bin
-  uint32_t grpc_tags_bin_index_ = 0;
-  // Index of something that was sent with grpc-trace-bin
-  uint32_t grpc_trace_bin_index_ = 0;
-  // The user-agent string referred to by user_agent_index_
-  Slice user_agent_;
-  SliceIndex path_index_;
-  SliceIndex authority_index_;
-  std::vector<PreviousTimeout> previous_timeouts_;
+  grpc_metadata_batch::StatefulCompressor<hpack_encoder_detail::Compressor>
+      compression_state_;
 };
 
+namespace hpack_encoder_detail {
+
+template <typename MetadataTrait>
+void Encoder::Encode(MetadataTrait,
+                     const typename MetadataTrait::ValueType& value) {
+  compressor_->compression_state_
+      .Compressor<MetadataTrait, typename MetadataTrait::CompressionTraits>::
+          EncodeWith(MetadataTrait(), value, this);
+}
+
+inline HPackEncoderTable& Encoder::hpack_table() { return compressor_->table_; }
+
+}  // namespace hpack_encoder_detail
+
 }  // namespace grpc_core
 
 #endif  // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_H
diff --git a/src/core/ext/transport/chttp2/transport/hpack_encoder_table.cc b/src/core/ext/transport/chttp2/transport/hpack_encoder_table.cc
index 209a4ee..55d2e44 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_encoder_table.cc
+++ b/src/core/ext/transport/chttp2/transport/hpack_encoder_table.cc
@@ -23,6 +23,8 @@
 namespace grpc_core {
 
 uint32_t HPackEncoderTable::AllocateIndex(size_t element_size) {
+  GPR_DEBUG_ASSERT(element_size >= 32);
+
   uint32_t new_index = tail_remote_index_ + table_elems_ + 1;
   GPR_DEBUG_ASSERT(element_size <= MaxEntrySize());
 
diff --git a/src/core/ext/transport/chttp2/transport/hpack_encoder_table.h b/src/core/ext/transport/chttp2/transport/hpack_encoder_table.h
index 6fc4482..623e50b 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_encoder_table.h
+++ b/src/core/ext/transport/chttp2/transport/hpack_encoder_table.h
@@ -50,6 +50,8 @@
   uint32_t max_size() const { return max_table_size_; }
   // Get the current table size
   uint32_t test_only_table_size() const { return table_size_; }
+  // Get the number of entries in the table
+  uint32_t test_only_table_elems() const { return table_elems_; }
 
   // Convert an element index into a dynamic index
   uint32_t DynamicIndex(uint32_t index) const {
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser.cc b/src/core/ext/transport/chttp2/transport/hpack_parser.cc
index c849f39..2e20c77 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser.cc
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser.cc
@@ -38,7 +38,6 @@
 #include "absl/types/span.h"
 #include "absl/types/variant.h"
 
-#include <grpc/status.h>
 #include <grpc/support/log.h>
 
 #include "src/core/ext/transport/chttp2/transport/decode_huff.h"
@@ -46,9 +45,11 @@
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/debug/stats_data.h"
 #include "src/core/lib/debug/trace.h"
+#include "src/core/lib/gprpp/crash.h"
 #include "src/core/lib/gprpp/status_helper.h"
 #include "src/core/lib/slice/slice.h"
 #include "src/core/lib/slice/slice_refcount.h"
+#include "src/core/lib/surface/validate_metadata.h"
 #include "src/core/lib/transport/parsed_metadata.h"
 
 // IWYU pragma: no_include <type_traits>
@@ -80,6 +81,40 @@
 };
 
 constexpr Base64InverseTable kBase64InverseTable;
+
+absl::Status EnsureStreamError(absl::Status error) {
+  if (error.ok()) return error;
+  return grpc_error_set_int(std::move(error), StatusIntProperty::kStreamId, 0);
+}
+
+bool IsStreamError(const absl::Status& status) {
+  intptr_t stream_id;
+  return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
+}
+
+class MetadataSizeLimitExceededEncoder {
+ public:
+  explicit MetadataSizeLimitExceededEncoder(std::string& summary)
+      : summary_(summary) {}
+
+  void Encode(const Slice& key, const Slice& value) {
+    AddToSummary(key.as_string_view(), value.size());
+  }
+
+  template <typename Key, typename Value>
+  void Encode(Key, const Value& value) {
+    AddToSummary(Key::key(), EncodedSizeOfKey(Key(), value));
+  }
+
+ private:
+  void AddToSummary(absl::string_view key,
+                    size_t value_length) GPR_ATTRIBUTE_NOINLINE {
+    absl::StrAppend(&summary_, " ", key, ":",
+                    hpack_constants::SizeForEntry(key.size(), value_length),
+                    "B");
+  }
+  std::string& summary_;
+};
 }  // namespace
 
 // Input tracks the current byte through the input data and provides it
@@ -121,7 +156,8 @@
   // of stream
   absl::optional<uint8_t> Next() {
     if (end_of_stream()) {
-      return UnexpectedEOF(absl::optional<uint8_t>());
+      UnexpectedEOF();
+      return absl::optional<uint8_t>();
     }
     return *begin_++;
   }
@@ -187,7 +223,10 @@
   // Parse a string prefix
   absl::optional<StringPrefix> ParseStringPrefix() {
     auto cur = Next();
-    if (!cur.has_value()) return {};
+    if (!cur.has_value()) {
+      GPR_DEBUG_ASSERT(eof_error());
+      return {};
+    }
     // Huffman if the top bit is 1
     const bool huff = (*cur & 0x80) != 0;
     // String length
@@ -195,14 +234,19 @@
     if (strlen == 0x7f) {
       // all ones ==> varint string length
       auto v = ParseVarint(0x7f);
-      if (!v.has_value()) return {};
+      if (!v.has_value()) {
+        GPR_DEBUG_ASSERT(eof_error());
+        return {};
+      }
       strlen = *v;
     }
     return StringPrefix{strlen, huff};
   }
 
   // Check if we saw an EOF.. must be verified before looking at TakeError
-  bool eof_error() const { return eof_error_; }
+  bool eof_error() const {
+    return eof_error_ || (!error_.ok() && !IsStreamError(error_));
+  }
 
   // Extract the parse error, leaving the current error as NONE.
   grpc_error_handle TakeError() {
@@ -211,34 +255,33 @@
     return out;
   }
 
-  // Set the current error - allows the rest of the code not to need to pass
-  // around StatusOr<> which would be prohibitive here.
-  GPR_ATTRIBUTE_NOINLINE void SetError(grpc_error_handle error) {
-    if (!error_.ok() || eof_error_) {
-      return;
-    }
-    error_ = error;
+  bool has_error() const { return !error_.ok(); }
+
+  // Set the current error - tweaks the error to include a stream id so that
+  // chttp2 does not close the connection.
+  // Intended for errors that are specific to a stream and recoverable.
+  // Callers should ensure that any hpack table updates happen.
+  GPR_ATTRIBUTE_NOINLINE void SetErrorAndContinueParsing(
+      grpc_error_handle error) {
+    GPR_ASSERT(!error.ok());
+    // StreamId is used as a signal to skip this stream but keep the connection
+    // alive
+    SetError(EnsureStreamError(std::move(error)));
+  }
+
+  // Set the current error, and skip past remaining bytes.
+  // Intended for unrecoverable errors, with the expectation that they will
+  // close the connection on return to chttp2.
+  GPR_ATTRIBUTE_NOINLINE void SetErrorAndStopParsing(grpc_error_handle error) {
+    GPR_ASSERT(!error.ok());
+    SetError(std::move(error));
     begin_ = end_;
   }
 
-  // If no error is set, set it to the value produced by error_factory.
-  // Return return_value unchanged.
-  template <typename F, typename T>
-  GPR_ATTRIBUTE_NOINLINE T MaybeSetErrorAndReturn(F error_factory,
-                                                  T return_value) {
-    if (!error_.ok() || eof_error_) return return_value;
-    error_ = error_factory();
-    begin_ = end_;
-    return return_value;
-  }
-
-  // Set the error to an unexpected eof, and return result (code golfed as this
-  // is a common case)
-  template <typename T>
-  T UnexpectedEOF(T return_value) {
-    if (!error_.ok()) return return_value;
+  // Set the error to an unexpected eof
+  void UnexpectedEOF() {
+    if (!error_.ok() && !IsStreamError(error_)) return;
     eof_error_ = true;
-    return return_value;
   }
 
   // Update the frontier - signifies we've successfully parsed another element
@@ -251,14 +294,24 @@
   // Helper to set the error to out of range for ParseVarint
   absl::optional<uint32_t> ParseVarintOutOfRange(uint32_t value,
                                                  uint8_t last_byte) {
-    return MaybeSetErrorAndReturn(
-        [value, last_byte] {
-          return GRPC_ERROR_CREATE(absl::StrFormat(
-              "integer overflow in hpack integer decoding: have 0x%08x, "
-              "got byte 0x%02x on byte 5",
-              value, last_byte));
-        },
-        absl::optional<uint32_t>());
+    SetErrorAndStopParsing(absl::InternalError(absl::StrFormat(
+        "integer overflow in hpack integer decoding: have 0x%08x, "
+        "got byte 0x%02x on byte 5",
+        value, last_byte)));
+    return absl::optional<uint32_t>();
+  }
+
+  // If no error is set, set it to the given error (i.e. first error wins)
+  // Do not use this directly, instead use SetErrorAndContinueParsing or
+  // SetErrorAndStopParsing.
+  void SetError(grpc_error_handle error) {
+    if (!error_.ok() || eof_error_) {
+      if (!IsStreamError(error) && IsStreamError(error_)) {
+        error_ = std::move(error);  // connection errors dominate
+      }
+      return;
+    }
+    error_ = std::move(error);
   }
 
   // Refcount if we are backed by a slice
@@ -279,6 +332,21 @@
 // management characteristics
 class HPackParser::String {
  public:
+  // ParseResult carries both a ParseStatus and the parsed string
+  struct ParseResult;
+  // Result of parsing a string
+  enum class ParseStatus {
+    // Parsed OK
+    kOk,
+    // Parse reached end of the current frame
+    kEof,
+    // Parse failed due to a huffman decode error
+    kParseHuffFailed,
+    // Parse failed due to a base64 decode error
+    kUnbase64Failed,
+  };
+
+  String() : value_(absl::Span<const uint8_t>()) {}
   String(const String&) = delete;
   String& operator=(const String&) = delete;
   String(String&& other) noexcept : value_(std::move(other.value_)) {
@@ -308,72 +376,10 @@
   }
 
   // Parse a non-binary string
-  static absl::optional<String> Parse(Input* input) {
-    auto pfx = input->ParseStringPrefix();
-    if (!pfx.has_value()) return {};
-    if (pfx->huff) {
-      // Huffman coded
-      std::vector<uint8_t> output;
-      auto v = ParseHuff(input, pfx->length,
-                         [&output](uint8_t c) { output.push_back(c); });
-      if (!v) return {};
-      return String(std::move(output));
-    }
-    return ParseUncompressed(input, pfx->length);
-  }
+  static ParseResult Parse(Input* input);
 
   // Parse a binary string
-  static absl::optional<String> ParseBinary(Input* input) {
-    auto pfx = input->ParseStringPrefix();
-    if (!pfx.has_value()) return {};
-    if (!pfx->huff) {
-      if (pfx->length > 0 && input->peek() == 0) {
-        // 'true-binary'
-        input->Advance(1);
-        return ParseUncompressed(input, pfx->length - 1);
-      }
-      // Base64 encoded... pull out the string, then unbase64 it
-      auto base64 = ParseUncompressed(input, pfx->length);
-      if (!base64.has_value()) return {};
-      return Unbase64(input, std::move(*base64));
-    } else {
-      // Huffman encoded...
-      std::vector<uint8_t> decompressed;
-      // State here says either we don't know if it's base64 or binary, or we do
-      // and what is it.
-      enum class State { kUnsure, kBinary, kBase64 };
-      State state = State::kUnsure;
-      auto decompressed_ok =
-          ParseHuff(input, pfx->length, [&state, &decompressed](uint8_t c) {
-            if (state == State::kUnsure) {
-              // First byte... if it's zero it's binary
-              if (c == 0) {
-                // Save the type, and skip the zero
-                state = State::kBinary;
-                return;
-              } else {
-                // Flag base64, store this value
-                state = State::kBase64;
-              }
-            }
-            // Non-first byte, or base64 first byte
-            decompressed.push_back(c);
-          });
-      if (!decompressed_ok) return {};
-      switch (state) {
-        case State::kUnsure:
-          // No bytes, empty span
-          return String(absl::Span<const uint8_t>());
-        case State::kBinary:
-          // Binary, we're done
-          return String(std::move(decompressed));
-        case State::kBase64:
-          // Base64 - unpack it
-          return Unbase64(input, String(std::move(decompressed)));
-      }
-      GPR_UNREACHABLE_CODE(abort(););
-    }
-  }
+  static ParseResult ParseBinary(Input* input);
 
  private:
   void AppendBytes(const uint8_t* data, size_t length);
@@ -385,54 +391,27 @@
   // Parse some huffman encoded bytes, using output(uint8_t b) to emit each
   // decoded byte.
   template <typename Out>
-  static bool ParseHuff(Input* input, uint32_t length, Out output) {
+  static ParseStatus ParseHuff(Input* input, uint32_t length, Out output) {
     // If there's insufficient bytes remaining, return now.
     if (input->remaining() < length) {
-      return input->UnexpectedEOF(false);
+      input->UnexpectedEOF();
+      GPR_DEBUG_ASSERT(input->eof_error());
+      return ParseStatus::kEof;
     }
     // Grab the byte range, and iterate through it.
     const uint8_t* p = input->cur_ptr();
     input->Advance(length);
-    return HuffDecoder<Out>(output, p, p + length).Run();
+    return HuffDecoder<Out>(output, p, p + length).Run()
+               ? ParseStatus::kOk
+               : ParseStatus::kParseHuffFailed;
   }
 
   // Parse some uncompressed string bytes.
-  static absl::optional<String> ParseUncompressed(Input* input,
-                                                  uint32_t length) {
-    // Check there's enough bytes
-    if (input->remaining() < length) {
-      return input->UnexpectedEOF(absl::optional<String>());
-    }
-    auto* refcount = input->slice_refcount();
-    auto* p = input->cur_ptr();
-    input->Advance(length);
-    if (refcount != nullptr) {
-      return String(refcount, p, p + length);
-    } else {
-      return String(absl::Span<const uint8_t>(p, length));
-    }
-  }
+  static ParseResult ParseUncompressed(Input* input, uint32_t length,
+                                       uint32_t wire_size);
 
   // Turn base64 encoded bytes into not base64 encoded bytes.
-  // Only takes input to set an error on failure.
-  static absl::optional<String> Unbase64(Input* input, String s) {
-    absl::optional<std::vector<uint8_t>> result;
-    if (auto* p = absl::get_if<Slice>(&s.value_)) {
-      result = Unbase64Loop(p->begin(), p->end());
-    }
-    if (auto* p = absl::get_if<absl::Span<const uint8_t>>(&s.value_)) {
-      result = Unbase64Loop(p->begin(), p->end());
-    }
-    if (auto* p = absl::get_if<std::vector<uint8_t>>(&s.value_)) {
-      result = Unbase64Loop(p->data(), p->data() + p->size());
-    }
-    if (!result.has_value()) {
-      return input->MaybeSetErrorAndReturn(
-          [] { return GRPC_ERROR_CREATE("illegal base64 encoding"); },
-          absl::optional<String>());
-    }
-    return String(std::move(*result));
-  }
+  static ParseResult Unbase64(String s);
 
   // Main loop for Unbase64
   static absl::optional<std::vector<uint8_t>> Unbase64Loop(const uint8_t* cur,
@@ -519,24 +498,154 @@
   absl::variant<Slice, absl::Span<const uint8_t>, std::vector<uint8_t>> value_;
 };
 
+struct HPackParser::String::ParseResult {
+  ParseResult() = delete;
+  ParseResult(ParseStatus status, size_t wire_size, String value)
+      : status(status), wire_size(wire_size), value(std::move(value)) {}
+  ParseStatus status;
+  size_t wire_size;
+  String value;
+};
+
+HPackParser::String::ParseResult HPackParser::String::ParseUncompressed(
+    Input* input, uint32_t length, uint32_t wire_size) {
+  // Check there's enough bytes
+  if (input->remaining() < length) {
+    input->UnexpectedEOF();
+    GPR_DEBUG_ASSERT(input->eof_error());
+    return ParseResult{ParseStatus::kEof, wire_size, String{}};
+  }
+  auto* refcount = input->slice_refcount();
+  auto* p = input->cur_ptr();
+  input->Advance(length);
+  if (refcount != nullptr) {
+    return ParseResult{ParseStatus::kOk, wire_size,
+                       String(refcount, p, p + length)};
+  } else {
+    return ParseResult{ParseStatus::kOk, wire_size,
+                       String(absl::Span<const uint8_t>(p, length))};
+  }
+}
+
+HPackParser::String::ParseResult HPackParser::String::Unbase64(String s) {
+  absl::optional<std::vector<uint8_t>> result;
+  if (auto* p = absl::get_if<Slice>(&s.value_)) {
+    result = Unbase64Loop(p->begin(), p->end());
+  }
+  if (auto* p = absl::get_if<absl::Span<const uint8_t>>(&s.value_)) {
+    result = Unbase64Loop(p->begin(), p->end());
+  }
+  if (auto* p = absl::get_if<std::vector<uint8_t>>(&s.value_)) {
+    result = Unbase64Loop(p->data(), p->data() + p->size());
+  }
+  if (!result.has_value()) {
+    return ParseResult{ParseStatus::kUnbase64Failed, s.string_view().length(),
+                       String{}};
+  }
+  return ParseResult{ParseStatus::kOk, s.string_view().length(),
+                     String(std::move(*result))};
+}
+
+HPackParser::String::ParseResult HPackParser::String::Parse(Input* input) {
+  auto pfx = input->ParseStringPrefix();
+  if (!pfx.has_value()) {
+    GPR_DEBUG_ASSERT(input->eof_error());
+    return ParseResult{ParseStatus::kEof, 0, String{}};
+  }
+  if (pfx->huff) {
+    // Huffman coded
+    std::vector<uint8_t> output;
+    ParseStatus sts = ParseHuff(input, pfx->length,
+                                [&output](uint8_t c) { output.push_back(c); });
+    size_t wire_len = output.size();
+    return ParseResult{sts, wire_len, String(std::move(output))};
+  }
+  return ParseUncompressed(input, pfx->length, pfx->length);
+}
+
+HPackParser::String::ParseResult HPackParser::String::ParseBinary(
+    Input* input) {
+  auto pfx = input->ParseStringPrefix();
+  if (!pfx.has_value()) {
+    GPR_DEBUG_ASSERT(input->eof_error());
+    return ParseResult{ParseStatus::kEof, 0, String{}};
+  }
+  if (!pfx->huff) {
+    if (pfx->length > 0 && input->peek() == 0) {
+      // 'true-binary'
+      input->Advance(1);
+      return ParseUncompressed(input, pfx->length - 1, pfx->length);
+    }
+    // Base64 encoded... pull out the string, then unbase64 it
+    auto base64 = ParseUncompressed(input, pfx->length, pfx->length);
+    if (base64.status != ParseStatus::kOk) return base64;
+    return Unbase64(std::move(base64.value));
+  } else {
+    // Huffman encoded...
+    std::vector<uint8_t> decompressed;
+    // State here says either we don't know if it's base64 or binary, or we do
+    // and what is it.
+    enum class State { kUnsure, kBinary, kBase64 };
+    State state = State::kUnsure;
+    auto sts =
+        ParseHuff(input, pfx->length, [&state, &decompressed](uint8_t c) {
+          if (state == State::kUnsure) {
+            // First byte... if it's zero it's binary
+            if (c == 0) {
+              // Save the type, and skip the zero
+              state = State::kBinary;
+              return;
+            } else {
+              // Flag base64, store this value
+              state = State::kBase64;
+            }
+          }
+          // Non-first byte, or base64 first byte
+          decompressed.push_back(c);
+        });
+    if (sts != ParseStatus::kOk) {
+      return ParseResult{sts, 0, String{}};
+    }
+    switch (state) {
+      case State::kUnsure:
+        // No bytes, empty span
+        return ParseResult{ParseStatus::kOk, 0,
+                           String(absl::Span<const uint8_t>())};
+      case State::kBinary:
+        // Binary, we're done
+        {
+          size_t wire_len = decompressed.size();
+          return ParseResult{ParseStatus::kOk, wire_len,
+                             String(std::move(decompressed))};
+        }
+      case State::kBase64:
+        // Base64 - unpack it
+        return Unbase64(String(std::move(decompressed)));
+    }
+    GPR_UNREACHABLE_CODE(abort(););
+  }
+}
+
 // Parser parses one key/value pair from a byte stream.
 class HPackParser::Parser {
  public:
-  Parser(Input* input, grpc_metadata_batch* metadata_buffer,
-         uint32_t metadata_size_limit, HPackTable* table,
+  Parser(Input* input, grpc_metadata_batch* metadata_buffer, HPackTable* table,
          uint8_t* dynamic_table_updates_allowed, uint32_t* frame_length,
-         LogInfo log_info)
+         RandomEarlyDetection* metadata_early_detection, LogInfo log_info)
       : input_(input),
         metadata_buffer_(metadata_buffer),
         table_(table),
         dynamic_table_updates_allowed_(dynamic_table_updates_allowed),
         frame_length_(frame_length),
-        metadata_size_limit_(metadata_size_limit),
+        metadata_early_detection_(metadata_early_detection),
         log_info_(log_info) {}
 
   // Skip any priority bits, or return false on failure
   bool SkipPriority() {
-    if (input_->remaining() < 5) return input_->UnexpectedEOF(false);
+    if (input_->remaining() < 5) {
+      input_->UnexpectedEOF();
+      return false;
+    }
     input_->Advance(5);
     return true;
   }
@@ -609,8 +718,9 @@
       case 8:
         if (cur == 0x80) {
           // illegal value.
-          return input_->MaybeSetErrorAndReturn(
-              [] { return GRPC_ERROR_CREATE("Illegal hpack op code"); }, false);
+          input_->SetErrorAndStopParsing(
+              absl::InternalError("Illegal hpack op code"));
+          return false;
         }
         ABSL_FALLTHROUGH_INTENDED;
       case 9:
@@ -647,20 +757,31 @@
         type = "???";
         break;
     }
-    gpr_log(GPR_DEBUG, "HTTP:%d:%s:%s: %s", log_info_.stream_id, type,
-            log_info_.is_client ? "CLI" : "SVR", memento.DebugString().c_str());
+    gpr_log(GPR_DEBUG, "HTTP:%d:%s:%s: %s%s", log_info_.stream_id, type,
+            log_info_.is_client ? "CLI" : "SVR",
+            memento.md.DebugString().c_str(),
+            memento.parse_status.ok()
+                ? ""
+                : absl::StrCat(
+                      " (parse error: ", memento.parse_status.ToString(), ")")
+                      .c_str());
   }
 
-  bool EmitHeader(const HPackTable::Memento& md) {
+  void EmitHeader(const HPackTable::Memento& md) {
     // Pass up to the transport
-    if (GPR_UNLIKELY(metadata_buffer_ == nullptr)) return true;
-    *frame_length_ += md.transport_size();
-    if (GPR_UNLIKELY(*frame_length_ > metadata_size_limit_)) {
-      return HandleMetadataSizeLimitExceeded(md);
+    *frame_length_ += md.md.transport_size();
+    if (!input_->has_error() &&
+        metadata_early_detection_->MustReject(*frame_length_)) {
+      // Reject any requests above hard metadata limit.
+      HandleMetadataHardSizeLimitExceeded(md);
     }
-
-    metadata_buffer_->Set(md);
-    return true;
+    if (!md.parse_status.ok()) {
+      // Reject any requests with invalid metadata.
+      HandleMetadataParseError(md.parse_status);
+    }
+    if (GPR_LIKELY(metadata_buffer_ != nullptr)) {
+      metadata_buffer_->Set(md.md);
+    }
   }
 
   bool FinishHeaderAndAddToTable(absl::optional<HPackTable::Memento> md) {
@@ -671,73 +792,149 @@
       LogHeader(*md);
     }
     // Emit whilst we own the metadata.
-    auto r = EmitHeader(*md);
+    EmitHeader(*md);
     // Add to the hpack table
     grpc_error_handle err = table_->Add(std::move(*md));
     if (GPR_UNLIKELY(!err.ok())) {
-      input_->SetError(err);
+      input_->SetErrorAndStopParsing(std::move(err));
       return false;
     };
-    return r;
+    return true;
   }
 
   bool FinishHeaderOmitFromTable(absl::optional<HPackTable::Memento> md) {
     // Allow higher code to just pass in failures ... simplifies things a bit.
     if (!md.has_value()) return false;
-    return FinishHeaderOmitFromTable(*md);
+    FinishHeaderOmitFromTable(*md);
+    return true;
   }
 
-  bool FinishHeaderOmitFromTable(const HPackTable::Memento& md) {
+  void FinishHeaderOmitFromTable(const HPackTable::Memento& md) {
     // Log if desired
     if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_chttp2_hpack_parser)) {
       LogHeader(md);
     }
-    return EmitHeader(md);
+    EmitHeader(md);
   }
 
+  // Helper type to build a memento from a key & value, and to consolidate some
+  // tricky error path code.
+  class MementoBuilder {
+   public:
+    explicit MementoBuilder(Input* input, absl::string_view key_string,
+                            absl::Status status = absl::OkStatus())
+        : input_(input), key_string_(key_string), status_(std::move(status)) {}
+
+    auto ErrorHandler() {
+      return [this](absl::string_view error, const Slice&) {
+        auto message =
+            absl::StrCat("Error parsing '", key_string_,
+                         "' metadata: error=", error, " key=", key_string_);
+        gpr_log(GPR_ERROR, "%s", message.c_str());
+        if (status_.ok()) {
+          status_ = absl::InternalError(message);
+        }
+      };
+    }
+
+    HPackTable::Memento Build(ParsedMetadata<grpc_metadata_batch> memento) {
+      return HPackTable::Memento{std::move(memento), std::move(status_)};
+    }
+
+    // Handle the result of parsing a value.
+    // Returns true if parsing should continue, false if it should stop.
+    // Stores an error on the input if necessary.
+    bool HandleParseResult(String::ParseStatus status) {
+      auto continuable = [this](absl::string_view error) {
+        auto this_error = absl::InternalError(absl::StrCat(
+            "Error parsing '", key_string_, "' metadata: error=", error));
+        if (status_.ok()) status_ = this_error;
+        input_->SetErrorAndContinueParsing(std::move(this_error));
+      };
+      switch (status) {
+        case String::ParseStatus::kOk:
+          return true;
+        case String::ParseStatus::kParseHuffFailed:
+          input_->SetErrorAndStopParsing(
+              absl::InternalError("Huffman decoding failed"));
+          return false;
+        case String::ParseStatus::kUnbase64Failed:
+          continuable("illegal base64 encoding");
+          return true;
+        case String::ParseStatus::kEof:
+          GPR_DEBUG_ASSERT(input_->eof_error());
+          return false;
+      }
+      GPR_UNREACHABLE_CODE(return false);
+    }
+
+   private:
+    Input* input_;
+    absl::string_view key_string_;
+    absl::Status status_;
+  };
+
   // Parse a string encoded key and a string encoded value
   absl::optional<HPackTable::Memento> ParseLiteralKey() {
     auto key = String::Parse(input_);
-    if (!key.has_value()) return {};
-    auto value = ParseValueString(absl::EndsWith(key->string_view(), "-bin"));
-    if (GPR_UNLIKELY(!value.has_value())) {
-      return {};
+    switch (key.status) {
+      case String::ParseStatus::kOk:
+        break;
+      case String::ParseStatus::kParseHuffFailed:
+        input_->SetErrorAndStopParsing(
+            absl::InternalError("Huffman decoding failed"));
+        return absl::nullopt;
+      case String::ParseStatus::kUnbase64Failed:
+        Crash("unreachable");
+      case String::ParseStatus::kEof:
+        GPR_DEBUG_ASSERT(input_->eof_error());
+        return absl::nullopt;
     }
-    auto key_string = key->string_view();
-    auto value_slice = value->Take();
-    const auto transport_size = key_string.size() + value_slice.size() +
-                                hpack_constants::kEntryOverhead;
-    return grpc_metadata_batch::Parse(
-        key->string_view(), std::move(value_slice), transport_size,
-        [key_string](absl::string_view error, const Slice& value) {
-          ReportMetadataParseError(key_string, error, value.as_string_view());
-        });
+    auto key_string = key.value.string_view();
+    auto value = ParseValueString(absl::EndsWith(key_string, "-bin"));
+    MementoBuilder builder(input_, key_string,
+                           EnsureStreamError(ValidateKey(key_string)));
+    if (!builder.HandleParseResult(value.status)) return absl::nullopt;
+    auto value_slice = value.value.Take();
+    const auto transport_size =
+        key_string.size() + value.wire_size + hpack_constants::kEntryOverhead;
+    return builder.Build(
+        grpc_metadata_batch::Parse(key_string, std::move(value_slice),
+                                   transport_size, builder.ErrorHandler()));
+  }
+
+  absl::Status ValidateKey(absl::string_view key) {
+    if (key == HttpSchemeMetadata::key() || key == HttpMethodMetadata::key() ||
+        key == HttpAuthorityMetadata::key() || key == HttpPathMetadata::key() ||
+        key == HttpStatusMetadata::key()) {
+      return absl::OkStatus();
+    }
+    return ValidateHeaderKeyIsLegal(key);
   }
 
   // Parse an index encoded key and a string encoded value
   absl::optional<HPackTable::Memento> ParseIdxKey(uint32_t index) {
     const auto* elem = table_->Lookup(index);
     if (GPR_UNLIKELY(elem == nullptr)) {
-      return InvalidHPackIndexError(index,
-                                    absl::optional<HPackTable::Memento>());
+      InvalidHPackIndexError(index);
+      return absl::optional<HPackTable::Memento>();
     }
-    auto value = ParseValueString(elem->is_binary_header());
-    if (GPR_UNLIKELY(!value.has_value())) return {};
-    return elem->WithNewValue(
-        value->Take(), [=](absl::string_view error, const Slice& value) {
-          ReportMetadataParseError(elem->key(), error, value.as_string_view());
-        });
-  }
+    MementoBuilder builder(input_, elem->md.key(), elem->parse_status);
+    auto value = ParseValueString(elem->md.is_binary_header());
+    if (!builder.HandleParseResult(value.status)) return absl::nullopt;
+    return builder.Build(elem->md.WithNewValue(
+        value.value.Take(), value.wire_size, builder.ErrorHandler()));
+  };
 
   // Parse a varint index encoded key and a string encoded value
   absl::optional<HPackTable::Memento> ParseVarIdxKey(uint32_t offset) {
     auto index = input_->ParseVarint(offset);
-    if (GPR_UNLIKELY(!index.has_value())) return {};
+    if (GPR_UNLIKELY(!index.has_value())) return absl::nullopt;
     return ParseIdxKey(*index);
   }
 
   // Parse a string, figuring out if it's binary or not by the key name.
-  absl::optional<String> ParseValueString(bool is_binary) {
+  String::ParseResult ParseValueString(bool is_binary) {
     if (is_binary) {
       return String::ParseBinary(input_);
     } else {
@@ -751,26 +948,25 @@
     if (!index.has_value()) return false;
     const auto* elem = table_->Lookup(*index);
     if (GPR_UNLIKELY(elem == nullptr)) {
-      return InvalidHPackIndexError(*index, false);
+      InvalidHPackIndexError(*index);
+      return false;
     }
-    return FinishHeaderOmitFromTable(*elem);
+    FinishHeaderOmitFromTable(*elem);
+    return true;
   }
 
   // finish parsing a max table size change
   bool FinishMaxTableSize(absl::optional<uint32_t> size) {
     if (!size.has_value()) return false;
     if (*dynamic_table_updates_allowed_ == 0) {
-      return input_->MaybeSetErrorAndReturn(
-          [] {
-            return GRPC_ERROR_CREATE(
-                "More than two max table size changes in a single frame");
-          },
-          false);
+      input_->SetErrorAndStopParsing(absl::InternalError(
+          "More than two max table size changes in a single frame"));
+      return false;
     }
     (*dynamic_table_updates_allowed_)--;
     grpc_error_handle err = table_->SetCurrentTableSize(*size);
     if (!err.ok()) {
-      input_->SetError(err);
+      input_->SetErrorAndStopParsing(std::move(err));
       return false;
     }
     return true;
@@ -778,88 +974,52 @@
 
   // Set an invalid hpack index error if no error has been set. Returns result
   // unmodified.
-  template <typename R>
-  R InvalidHPackIndexError(uint32_t index, R result) {
-    return input_->MaybeSetErrorAndReturn(
-        [this, index] {
-          return grpc_error_set_int(
-              grpc_error_set_int(
-                  GRPC_ERROR_CREATE("Invalid HPACK index received"),
-                  StatusIntProperty::kIndex, static_cast<intptr_t>(index)),
-              StatusIntProperty::kSize,
-              static_cast<intptr_t>(this->table_->num_entries()));
-        },
-        std::move(result));
+  void InvalidHPackIndexError(uint32_t index) {
+    input_->SetErrorAndStopParsing(grpc_error_set_int(
+        grpc_error_set_int(absl::InternalError("Invalid HPACK index received"),
+                           StatusIntProperty::kIndex,
+                           static_cast<intptr_t>(index)),
+        StatusIntProperty::kSize,
+        static_cast<intptr_t>(this->table_->num_entries())));
   }
 
-  class MetadataSizeLimitExceededEncoder {
-   public:
-    explicit MetadataSizeLimitExceededEncoder(std::string& summary)
-        : summary_(summary) {}
-
-    void Encode(const Slice& key, const Slice& value) {
-      AddToSummary(key.as_string_view(), value.size());
+  GPR_ATTRIBUTE_NOINLINE
+  void HandleMetadataParseError(const absl::Status& status) {
+    if (metadata_buffer_ != nullptr) {
+      metadata_buffer_->Clear();
+      metadata_buffer_ = nullptr;
     }
-
-    template <typename Key, typename Value>
-    void Encode(Key, const Value& value) {
-      AddToSummary(Key::key(), EncodedSizeOfKey(Key(), value));
-    }
-
-   private:
-    void AddToSummary(absl::string_view key,
-                      size_t value_length) GPR_ATTRIBUTE_NOINLINE {
-      absl::StrAppend(&summary_, " ", key, ":",
-                      hpack_constants::SizeForEntry(key.size(), value_length),
-                      "B");
-    }
-    std::string& summary_;
-  };
+    // StreamId is used as a signal to skip this stream but keep the connection
+    // alive
+    input_->SetErrorAndContinueParsing(status);
+  }
 
   GPR_ATTRIBUTE_NOINLINE
-  bool HandleMetadataSizeLimitExceeded(const HPackTable::Memento& md) {
+  void HandleMetadataHardSizeLimitExceeded(const HPackTable::Memento& md) {
     // Collect a summary of sizes so far for debugging
     // Do not collect contents, for fear of exposing PII.
     std::string summary;
+    std::string error_message;
     if (metadata_buffer_ != nullptr) {
       MetadataSizeLimitExceededEncoder encoder(summary);
       metadata_buffer_->Encode(&encoder);
     }
-    summary =
-        absl::StrCat("; adding ", md.key(), " (length ", md.transport_size(),
-                     "B)", summary.empty() ? "" : " to ", summary);
-    if (metadata_buffer_ != nullptr) metadata_buffer_->Clear();
-    // StreamId is used as a signal to skip this stream but keep the connection
-    // alive
-    return input_->MaybeSetErrorAndReturn(
-        [this, summary = std::move(summary)] {
-          return grpc_error_set_int(
-              grpc_error_set_int(
-                  GRPC_ERROR_CREATE(absl::StrCat(
-                      "received initial metadata size exceeds limit (",
-                      *frame_length_, " vs. ", metadata_size_limit_, ")",
-                      summary)),
-                  StatusIntProperty::kRpcStatus,
-                  GRPC_STATUS_RESOURCE_EXHAUSTED),
-              StatusIntProperty::kStreamId, 0);
-        },
-        false);
-  }
-
-  static void ReportMetadataParseError(absl::string_view key,
-                                       absl::string_view error,
-                                       absl::string_view value) {
-    gpr_log(
-        GPR_ERROR, "Error parsing metadata: %s",
-        absl::StrCat("error=", error, " key=", key, " value=", value).c_str());
+    summary = absl::StrCat("; adding ", md.md.key(), " (length ",
+                           md.md.transport_size(), "B)",
+                           summary.empty() ? "" : " to ", summary);
+    error_message = absl::StrCat(
+        "received metadata size exceeds hard limit (", *frame_length_, " vs. ",
+        metadata_early_detection_->hard_limit(), ")", summary);
+    HandleMetadataParseError(absl::ResourceExhaustedError(error_message));
   }
 
   Input* const input_;
-  grpc_metadata_batch* const metadata_buffer_;
+  grpc_metadata_batch* metadata_buffer_;
   HPackTable* const table_;
   uint8_t* const dynamic_table_updates_allowed_;
   uint32_t* const frame_length_;
-  const uint32_t metadata_size_limit_;
+  // Random early detection of metadata size limits.
+  RandomEarlyDetection* metadata_early_detection_;
   const LogInfo log_info_;
 };
 
@@ -881,8 +1041,10 @@
 HPackParser::~HPackParser() = default;
 
 void HPackParser::BeginFrame(grpc_metadata_batch* metadata_buffer,
-                             uint32_t metadata_size_limit, Boundary boundary,
-                             Priority priority, LogInfo log_info) {
+                             uint32_t metadata_size_soft_limit,
+                             uint32_t metadata_size_hard_limit,
+                             Boundary boundary, Priority priority,
+                             LogInfo log_info) {
   metadata_buffer_ = metadata_buffer;
   if (metadata_buffer != nullptr) {
     metadata_buffer->Set(GrpcStatusFromWire(), true);
@@ -891,7 +1053,9 @@
   priority_ = priority;
   dynamic_table_updates_allowed_ = 2;
   frame_length_ = 0;
-  metadata_size_limit_ = metadata_size_limit;
+  metadata_early_detection_ = RandomEarlyDetection(
+      /*soft_limit=*/metadata_size_soft_limit,
+      /*hard_limit=*/metadata_size_hard_limit);
   log_info_ = log_info;
 }
 
@@ -909,43 +1073,72 @@
 }
 
 grpc_error_handle HPackParser::ParseInput(Input input, bool is_last) {
-  bool parsed_ok = ParseInputInner(&input);
-  if (is_last) global_stats().IncrementHttp2MetadataSize(frame_length_);
-  if (parsed_ok) return absl::OkStatus();
+  ParseInputInner(&input);
+  if (is_last) {
+    if (metadata_early_detection_.Reject(frame_length_)) {
+      HandleMetadataSoftSizeLimitExceeded(&input);
+    }
+    global_stats().IncrementHttp2MetadataSize(frame_length_);
+  }
   if (input.eof_error()) {
     if (GPR_UNLIKELY(is_last && is_boundary())) {
-      return GRPC_ERROR_CREATE(
+      auto err = input.TakeError();
+      if (!err.ok() && !IsStreamError(err)) return err;
+      return absl::InternalError(
           "Incomplete header at the end of a header/continuation sequence");
     }
     unparsed_bytes_ = std::vector<uint8_t>(input.frontier(), input.end_ptr());
-    return absl::OkStatus();
+    return input.TakeError();
   }
   return input.TakeError();
 }
 
-bool HPackParser::ParseInputInner(Input* input) {
+void HPackParser::ParseInputInner(Input* input) {
   switch (priority_) {
     case Priority::None:
       break;
     case Priority::Included: {
-      if (input->remaining() < 5) return input->UnexpectedEOF(false);
+      if (input->remaining() < 5) {
+        input->UnexpectedEOF();
+        return;
+      }
       input->Advance(5);
       input->UpdateFrontier();
       priority_ = Priority::None;
     }
   }
   while (!input->end_of_stream()) {
-    if (GPR_UNLIKELY(!Parser(input, metadata_buffer_, metadata_size_limit_,
-                             &table_, &dynamic_table_updates_allowed_,
-                             &frame_length_, log_info_)
+    if (GPR_UNLIKELY(!Parser(input, metadata_buffer_, &table_,
+                             &dynamic_table_updates_allowed_, &frame_length_,
+                             &metadata_early_detection_, log_info_)
                           .Parse())) {
-      return false;
+      return;
     }
     input->UpdateFrontier();
   }
-  return true;
 }
 
 void HPackParser::FinishFrame() { metadata_buffer_ = nullptr; }
 
+void HPackParser::HandleMetadataSoftSizeLimitExceeded(Input* input) {
+  // Collect a summary of sizes so far for debugging
+  // Do not collect contents, for fear of exposing PII.
+  std::string summary;
+  std::string error_message;
+  if (metadata_buffer_ != nullptr) {
+    MetadataSizeLimitExceededEncoder encoder(summary);
+    metadata_buffer_->Encode(&encoder);
+  }
+  error_message = absl::StrCat(
+      "received metadata size exceeds soft limit (", frame_length_, " vs. ",
+      metadata_early_detection_.soft_limit(),
+      "), rejecting requests with some random probability", summary);
+  if (metadata_buffer_ != nullptr) {
+    metadata_buffer_->Clear();
+    metadata_buffer_ = nullptr;
+  }
+  input->SetErrorAndContinueParsing(
+      absl::ResourceExhaustedError(error_message));
+}
+
 }  // namespace grpc_core
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser.h b/src/core/ext/transport/chttp2/transport/hpack_parser.h
index 0688c17..d286307 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser.h
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser.h
@@ -29,6 +29,7 @@
 
 #include "src/core/ext/transport/chttp2/transport/frame.h"
 #include "src/core/ext/transport/chttp2/transport/hpack_parser_table.h"
+#include "src/core/lib/backoff/random_early_detection.h"
 #include "src/core/lib/iomgr/error.h"
 #include "src/core/lib/transport/metadata_batch.h"
 
@@ -80,7 +81,8 @@
   // Begin parsing a new frame
   // Sink receives each parsed header,
   void BeginFrame(grpc_metadata_batch* metadata_buffer,
-                  uint32_t metadata_size_limit, Boundary boundary,
+                  uint32_t metadata_size_soft_limit,
+                  uint32_t metadata_size_hard_limit, Boundary boundary,
                   Priority priority, LogInfo log_info);
   // Start throwing away any received headers after parsing them.
   void StopBufferingFrame() { metadata_buffer_ = nullptr; }
@@ -103,7 +105,9 @@
   class String;
 
   grpc_error_handle ParseInput(Input input, bool is_last);
-  bool ParseInputInner(Input* input);
+  void ParseInputInner(Input* input);
+  GPR_ATTRIBUTE_NOINLINE
+  void HandleMetadataSoftSizeLimitExceeded(Input* input);
 
   // Target metadata buffer
   grpc_metadata_batch* metadata_buffer_ = nullptr;
@@ -121,7 +125,7 @@
   uint8_t dynamic_table_updates_allowed_;
   // Length of frame so far.
   uint32_t frame_length_;
-  uint32_t metadata_size_limit_;
+  RandomEarlyDetection metadata_early_detection_;
   // Information for logging
   LogInfo log_info_;
 
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser_table.cc b/src/core/ext/transport/chttp2/transport/hpack_parser_table.cc
index 1e582e2..3079093 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser_table.cc
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser_table.cc
@@ -82,8 +82,8 @@
 // Evict one element from the table
 void HPackTable::EvictOne() {
   auto first_entry = entries_.PopOne();
-  GPR_ASSERT(first_entry.transport_size() <= mem_used_);
-  mem_used_ -= first_entry.transport_size();
+  GPR_ASSERT(first_entry.md.transport_size() <= mem_used_);
+  mem_used_ -= first_entry.md.transport_size();
 }
 
 void HPackTable::SetMaxBytes(uint32_t max_bytes) {
@@ -104,7 +104,7 @@
     return absl::OkStatus();
   }
   if (bytes > max_bytes_) {
-    return GRPC_ERROR_CREATE(absl::StrFormat(
+    return absl::InternalError(absl::StrFormat(
         "Attempt to make hpack table %d bytes when max is %d bytes", bytes,
         max_bytes_));
   }
@@ -130,7 +130,7 @@
   }
 
   // we can't add elements bigger than the max table size
-  if (md.transport_size() > current_table_bytes_) {
+  if (md.md.transport_size() > current_table_bytes_) {
     // HPACK draft 10 section 4.4 states:
     // If the size of the new entry is less than or equal to the maximum
     // size, that entry is added to the table.  It is not an error to
@@ -145,13 +145,13 @@
   }
 
   // evict entries to ensure no overflow
-  while (md.transport_size() >
+  while (md.md.transport_size() >
          static_cast<size_t>(current_table_bytes_) - mem_used_) {
     EvictOne();
   }
 
   // copy the finalized entry in
-  mem_used_ += md.transport_size();
+  mem_used_ += md.md.transport_size();
   entries_.Put(std::move(md));
   return absl::OkStatus();
 }
@@ -228,12 +228,14 @@
 
 HPackTable::Memento MakeMemento(size_t i) {
   auto sm = kStaticTable[i];
-  return grpc_metadata_batch::Parse(
-      sm.key, Slice::FromStaticString(sm.value),
-      strlen(sm.key) + strlen(sm.value) + hpack_constants::kEntryOverhead,
-      [](absl::string_view, const Slice&) {
-        abort();  // not expecting to see this
-      });
+  return HPackTable::Memento{
+      grpc_metadata_batch::Parse(
+          sm.key, Slice::FromStaticString(sm.value),
+          strlen(sm.key) + strlen(sm.value) + hpack_constants::kEntryOverhead,
+          [](absl::string_view, const Slice&) {
+            abort();  // not expecting to see this
+          }),
+      absl::OkStatus()};
 }
 
 }  // namespace
diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser_table.h b/src/core/ext/transport/chttp2/transport/hpack_parser_table.h
index 086647f..7e29cb5 100644
--- a/src/core/ext/transport/chttp2/transport/hpack_parser_table.h
+++ b/src/core/ext/transport/chttp2/transport/hpack_parser_table.h
@@ -25,6 +25,8 @@
 
 #include <vector>
 
+#include "absl/status/status.h"
+
 #include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
 #include "src/core/lib/gprpp/no_destruct.h"
 #include "src/core/lib/iomgr/error.h"
@@ -45,7 +47,10 @@
   void SetMaxBytes(uint32_t max_bytes);
   grpc_error_handle SetCurrentTableSize(uint32_t bytes);
 
-  using Memento = ParsedMetadata<grpc_metadata_batch>;
+  struct Memento {
+    ParsedMetadata<grpc_metadata_batch> md;
+    absl::Status parse_status;
+  };
 
   // Lookup, but don't ref.
   const Memento* Lookup(uint32_t index) const {
@@ -68,6 +73,9 @@
   // Current entry count in the table.
   uint32_t num_entries() const { return entries_.num_entries(); }
 
+  // Current size of the table.
+  uint32_t test_only_table_size() const { return mem_used_; }
+
  private:
   struct StaticMementos {
     StaticMementos();
diff --git a/src/core/ext/transport/chttp2/transport/internal.h b/src/core/ext/transport/chttp2/transport/internal.h
index 8d5c468..df30764 100644
--- a/src/core/ext/transport/chttp2/transport/internal.h
+++ b/src/core/ext/transport/chttp2/transport/internal.h
@@ -457,6 +457,8 @@
   bool keepalive_ping_started = false;
   /// keep-alive state machine state
   grpc_chttp2_keepalive_state keepalive_state;
+  // Soft limit on max header size.
+  uint32_t max_header_list_size_soft_limit = 0;
   grpc_core::ContextList* cl = nullptr;
   grpc_core::RefCountedPtr<grpc_core::channelz::SocketNode> channelz_socket;
   uint32_t num_messages_in_next_write = 0;
diff --git a/src/core/ext/transport/chttp2/transport/parsing.cc b/src/core/ext/transport/chttp2/transport/parsing.cc
index 2b9b024..1e160d5 100644
--- a/src/core/ext/transport/chttp2/transport/parsing.cc
+++ b/src/core/ext/transport/chttp2/transport/parsing.cc
@@ -475,6 +475,9 @@
       "header", grpc_chttp2_header_parser_parse, &t->hpack_parser};
   t->hpack_parser.BeginFrame(
       nullptr,
+      /*metadata_size_soft_limit=*/
+      t->max_header_list_size_soft_limit,
+      /*metadata_size_hard_limit=*/
       t->settings[GRPC_ACKED_SETTINGS]
                  [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE],
       hpack_boundary_type(t, is_eoh), priority_type,
@@ -691,6 +694,9 @@
   }
   t->hpack_parser.BeginFrame(
       incoming_metadata_buffer,
+      /*metadata_size_soft_limit=*/
+      t->max_header_list_size_soft_limit,
+      /*metadata_size_hard_limit=*/
       t->settings[GRPC_ACKED_SETTINGS]
                  [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE],
       hpack_boundary_type(t, is_eoh), priority_type,
diff --git a/src/core/lib/backoff/random_early_detection.h b/src/core/lib/backoff/random_early_detection.h
index 3c88d9f..4347236 100644
--- a/src/core/lib/backoff/random_early_detection.h
+++ b/src/core/lib/backoff/random_early_detection.h
@@ -17,6 +17,8 @@
 
 #include <grpc/support/port_platform.h>
 
+#include <limits.h>
+
 #include <cstdint>
 
 #include "absl/random/random.h"
@@ -27,6 +29,7 @@
 // or accepted based upon their size.
 class RandomEarlyDetection {
  public:
+  RandomEarlyDetection() : soft_limit_(INT_MAX), hard_limit_(INT_MAX) {}
   RandomEarlyDetection(uint64_t soft_limit, uint64_t hard_limit)
       : soft_limit_(soft_limit), hard_limit_(hard_limit) {}
 
diff --git a/src/core/lib/iomgr/endpoint_pair.h b/src/core/lib/iomgr/endpoint_pair.h
index 7a8b930..586f4fc 100644
--- a/src/core/lib/iomgr/endpoint_pair.h
+++ b/src/core/lib/iomgr/endpoint_pair.h
@@ -28,7 +28,7 @@
   grpc_endpoint* server;
 };
 
-grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(const char* name,
-                                                   grpc_channel_args* args);
+grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(
+    const char* name, const grpc_channel_args* args);
 
 #endif  // GRPC_SRC_CORE_LIB_IOMGR_ENDPOINT_PAIR_H
diff --git a/src/core/lib/iomgr/endpoint_pair_posix.cc b/src/core/lib/iomgr/endpoint_pair_posix.cc
index 754bd35..b977c0d 100644
--- a/src/core/lib/iomgr/endpoint_pair_posix.cc
+++ b/src/core/lib/iomgr/endpoint_pair_posix.cc
@@ -55,8 +55,8 @@
   GPR_ASSERT(grpc_set_socket_no_sigpipe_if_possible(sv[1]) == absl::OkStatus());
 }
 
-grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(const char* name,
-                                                   grpc_channel_args* args) {
+grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(
+    const char* name, const grpc_channel_args* args) {
   int sv[2];
   grpc_endpoint_pair p;
   create_sockets(sv);
diff --git a/src/core/lib/iomgr/endpoint_pair_windows.cc b/src/core/lib/iomgr/endpoint_pair_windows.cc
index 64265e4..97e4641 100644
--- a/src/core/lib/iomgr/endpoint_pair_windows.cc
+++ b/src/core/lib/iomgr/endpoint_pair_windows.cc
@@ -80,7 +80,7 @@
 }
 
 grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(
-    const char*, grpc_channel_args* channel_args) {
+    const char*, const grpc_channel_args* /* channel_args */) {
   SOCKET sv[2];
   grpc_endpoint_pair p;
   create_sockets(sv);
diff --git a/src/core/lib/surface/validate_metadata.cc b/src/core/lib/surface/validate_metadata.cc
index 3fef478..4e38d6b 100644
--- a/src/core/lib/surface/validate_metadata.cc
+++ b/src/core/lib/surface/validate_metadata.cc
@@ -21,46 +21,20 @@
 #include "src/core/lib/surface/validate_metadata.h"
 
 #include "absl/status/status.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 
 #include <grpc/grpc.h>
 
-#include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/bitset.h"
-#include "src/core/lib/gprpp/memory.h"
-#include "src/core/lib/gprpp/status_helper.h"
 #include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/slice/slice_internal.h"
 
-static grpc_error_handle conforms_to(const grpc_slice& slice,
-                                     const grpc_core::BitSet<256>& legal_bits,
-                                     const char* err_desc) {
-  const uint8_t* p = GRPC_SLICE_START_PTR(slice);
-  const uint8_t* e = GRPC_SLICE_END_PTR(slice);
-  for (; p != e; p++) {
-    if (!legal_bits.is_set(*p)) {
-      size_t len;
-      grpc_core::UniquePtr<char> ptr(gpr_dump_return_len(
-          reinterpret_cast<const char*> GRPC_SLICE_START_PTR(slice),
-          GRPC_SLICE_LENGTH(slice), GPR_DUMP_HEX | GPR_DUMP_ASCII, &len));
-      grpc_error_handle error = grpc_error_set_str(
-          grpc_error_set_int(GRPC_ERROR_CREATE(err_desc),
-                             grpc_core::StatusIntProperty::kOffset,
-                             p - GRPC_SLICE_START_PTR(slice)),
-          grpc_core::StatusStrProperty::kRawBytes,
-          absl::string_view(ptr.get(), len));
-      return error;
-    }
-  }
-  return absl::OkStatus();
-}
-
-static int error2int(grpc_error_handle error) {
-  int r = (error.ok());
-  return r;
-}
+namespace grpc_core {
 
 namespace {
-class LegalHeaderKeyBits : public grpc_core::BitSet<256> {
+class LegalHeaderKeyBits : public BitSet<256> {
  public:
   constexpr LegalHeaderKeyBits() {
     for (int i = 'a'; i <= 'z'; i++) set(i);
@@ -71,19 +45,45 @@
   }
 };
 constexpr LegalHeaderKeyBits g_legal_header_key_bits;
+
+GPR_ATTRIBUTE_NOINLINE
+absl::Status DoesNotConformTo(absl::string_view x, const char* err_desc) {
+  return absl::InternalError(absl::StrCat(err_desc, ": ", x, " (hex ",
+                                          absl::BytesToHexString(x), ")"));
+}
+
+absl::Status ConformsTo(absl::string_view x, const BitSet<256>& legal_bits,
+                        const char* err_desc) {
+  for (uint8_t c : x) {
+    if (!legal_bits.is_set(c)) {
+      return DoesNotConformTo(x, err_desc);
+    }
+  }
+  return absl::OkStatus();
+}
 }  // namespace
 
+absl::Status ValidateHeaderKeyIsLegal(absl::string_view key) {
+  if (key.empty()) {
+    return absl::InternalError("Metadata keys cannot be zero length");
+  }
+  if (key.size() > UINT32_MAX) {
+    return absl::InternalError(
+        "Metadata keys cannot be larger than UINT32_MAX");
+  }
+  return ConformsTo(key, g_legal_header_key_bits, "Illegal header key");
+}
+
+}  // namespace grpc_core
+
+static int error2int(grpc_error_handle error) {
+  int r = (error.ok());
+  return r;
+}
+
 grpc_error_handle grpc_validate_header_key_is_legal(const grpc_slice& slice) {
-  if (GRPC_SLICE_LENGTH(slice) == 0) {
-    return GRPC_ERROR_CREATE("Metadata keys cannot be zero length");
-  }
-  if (GRPC_SLICE_LENGTH(slice) > UINT32_MAX) {
-    return GRPC_ERROR_CREATE("Metadata keys cannot be larger than UINT32_MAX");
-  }
-  if (GRPC_SLICE_START_PTR(slice)[0] == ':') {
-    return GRPC_ERROR_CREATE("Metadata keys cannot start with :");
-  }
-  return conforms_to(slice, g_legal_header_key_bits, "Illegal header key");
+  return grpc_core::ValidateHeaderKeyIsLegal(
+      grpc_core::StringViewFromSlice(slice));
 }
 
 int grpc_header_key_is_legal(grpc_slice slice) {
@@ -104,8 +104,9 @@
 
 grpc_error_handle grpc_validate_header_nonbin_value_is_legal(
     const grpc_slice& slice) {
-  return conforms_to(slice, g_legal_header_non_bin_value_bits,
-                     "Illegal header value");
+  return grpc_core::ConformsTo(grpc_core::StringViewFromSlice(slice),
+                               g_legal_header_non_bin_value_bits,
+                               "Illegal header value");
 }
 
 int grpc_header_nonbin_value_is_legal(grpc_slice slice) {
diff --git a/src/core/lib/surface/validate_metadata.h b/src/core/lib/surface/validate_metadata.h
index 3e6fdf4..a6e99ca 100644
--- a/src/core/lib/surface/validate_metadata.h
+++ b/src/core/lib/surface/validate_metadata.h
@@ -25,11 +25,20 @@
 
 #include <cstring>
 
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+
 #include <grpc/slice.h>
 #include <grpc/support/log.h>
 
 #include "src/core/lib/iomgr/error.h"
 
+namespace grpc_core {
+
+absl::Status ValidateHeaderKeyIsLegal(absl::string_view key);
+
+}
+
 grpc_error_handle grpc_validate_header_key_is_legal(const grpc_slice& slice);
 grpc_error_handle grpc_validate_header_nonbin_value_is_legal(
     const grpc_slice& slice);
diff --git a/src/core/lib/transport/metadata_batch.cc b/src/core/lib/transport/metadata_batch.cc
index c2b53ff..b8432f1 100644
--- a/src/core/lib/transport/metadata_batch.cc
+++ b/src/core/lib/transport/metadata_batch.cc
@@ -93,7 +93,7 @@
       return StaticSlice::FromStaticString("unrepresentable value"));
 }
 
-const char* ContentTypeMetadata::DisplayValue(MementoType content_type) {
+const char* ContentTypeMetadata::DisplayValue(ValueType content_type) {
   switch (content_type) {
     case ValueType::kApplicationGrpc:
       return "application/grpc";
@@ -137,7 +137,7 @@
   return out;
 }
 
-const char* TeMetadata::DisplayValue(MementoType te) {
+const char* TeMetadata::DisplayValue(ValueType te) {
   switch (te) {
     case ValueType::kTrailers:
       return "trailers";
@@ -222,7 +222,7 @@
   }
 }
 
-const char* HttpMethodMetadata::DisplayValue(MementoType content_type) {
+const char* HttpMethodMetadata::DisplayValue(ValueType content_type) {
   switch (content_type) {
     case kPost:
       return "POST";
@@ -264,7 +264,7 @@
   return Slice(std::move(slice));
 }
 
-std::string LbCostBinMetadata::DisplayValue(MementoType x) {
+std::string LbCostBinMetadata::DisplayValue(ValueType x) {
   return absl::StrCat(x.name, ":", x.cost);
 }
 
diff --git a/src/core/lib/transport/metadata_batch.h b/src/core/lib/transport/metadata_batch.h
index b7743f4..9616e67 100644
--- a/src/core/lib/transport/metadata_batch.h
+++ b/src/core/lib/transport/metadata_batch.h
@@ -49,6 +49,50 @@
 
 namespace grpc_core {
 
+///////////////////////////////////////////////////////////////////////////////
+// Compression traits.
+//
+// Each metadata trait exposes exactly one compression trait.
+// This type directs how transports might choose to compress the metadata.
+// Adding a value here typically involves editing all transports to support the
+// trait, and so should not be done lightly.
+
+// No compression.
+struct NoCompressionCompressor {};
+
+// Expect a single value for this metadata key, but we don't know apriori its
+// value.
+// It's ok if it changes over time, but it should be mostly stable.
+// This is used for things like user-agent, which is expected to be the same
+// for all requests.
+struct StableValueCompressor {};
+
+// Expect a single value for this metadata key, and we know apriori its value.
+template <typename T, T value>
+struct KnownValueCompressor {};
+
+// Values are uncompressible, but expect the key to be in most requests and try
+// and compress that.
+struct FrequentKeyWithNoValueCompressionCompressor {};
+
+// Expect a small set of values for this metadata key.
+struct SmallSetOfValuesCompressor {};
+
+// Expect integral values up to N for this metadata key.
+template <size_t N>
+struct SmallIntegralValuesCompressor {};
+
+// Specialty compressor for grpc-timeout metadata.
+struct TimeoutCompressor {};
+
+// Specialty compressors for HTTP/2 psuedo headers.
+struct HttpSchemeCompressor {};
+struct HttpMethodCompressor {};
+struct HttpStatusCompressor {};
+
+///////////////////////////////////////////////////////////////////////////////
+// Metadata traits
+
 // Given a metadata key and a value, return the encoded size.
 // Defaults to calling the key's Encode() method and then calculating the size
 // of that, but can be overridden for specific keys if there's a better way of
@@ -70,11 +114,13 @@
   static constexpr bool kRepeatable = false;
   using ValueType = Timestamp;
   using MementoType = Duration;
+  using CompressionTraits = TimeoutCompressor;
   static absl::string_view key() { return "grpc-timeout"; }
   static MementoType ParseMemento(Slice value, MetadataParseErrorFn on_error);
   static ValueType MementoToValue(MementoType timeout);
   static Slice Encode(ValueType x);
-  static std::string DisplayValue(MementoType x) { return x.ToString(); }
+  static std::string DisplayValue(ValueType x) { return x.ToString(); }
+  static std::string DisplayMemento(MementoType x) { return x.ToString(); }
 };
 
 // TE metadata trait.
@@ -88,6 +134,7 @@
     kInvalid,
   };
   using MementoType = ValueType;
+  using CompressionTraits = KnownValueCompressor<ValueType, kTrailers>;
   static absl::string_view key() { return "te"; }
   static MementoType ParseMemento(Slice value, MetadataParseErrorFn on_error);
   static ValueType MementoToValue(MementoType te) { return te; }
@@ -95,7 +142,8 @@
     GPR_ASSERT(x == kTrailers);
     return StaticSlice::FromStaticString("trailers");
   }
-  static const char* DisplayValue(MementoType te);
+  static const char* DisplayValue(ValueType te);
+  static const char* DisplayMemento(MementoType te) { return DisplayValue(te); }
 };
 
 inline size_t EncodedSizeOfKey(TeMetadata, TeMetadata::ValueType x) {
@@ -114,6 +162,7 @@
     kInvalid,
   };
   using MementoType = ValueType;
+  using CompressionTraits = KnownValueCompressor<ValueType, kApplicationGrpc>;
   static absl::string_view key() { return "content-type"; }
   static MementoType ParseMemento(Slice value, MetadataParseErrorFn on_error);
   static ValueType MementoToValue(MementoType content_type) {
@@ -121,7 +170,10 @@
   }
 
   static StaticSlice Encode(ValueType x);
-  static const char* DisplayValue(MementoType content_type);
+  static const char* DisplayValue(ValueType content_type);
+  static const char* DisplayMemento(ValueType content_type) {
+    return DisplayValue(content_type);
+  }
 };
 
 // scheme metadata trait.
@@ -133,6 +185,7 @@
     kInvalid,
   };
   using MementoType = ValueType;
+  using CompressionTraits = HttpSchemeCompressor;
   static absl::string_view key() { return ":scheme"; }
   static MementoType ParseMemento(Slice value, MetadataParseErrorFn on_error) {
     return Parse(value.as_string_view(), on_error);
@@ -143,7 +196,10 @@
     return content_type;
   }
   static StaticSlice Encode(ValueType x);
-  static const char* DisplayValue(MementoType content_type);
+  static const char* DisplayValue(ValueType content_type);
+  static const char* DisplayMemento(MementoType content_type) {
+    return DisplayValue(content_type);
+  }
 };
 
 size_t EncodedSizeOfKey(HttpSchemeMetadata, HttpSchemeMetadata::ValueType x);
@@ -158,13 +214,17 @@
     kInvalid,
   };
   using MementoType = ValueType;
+  using CompressionTraits = HttpMethodCompressor;
   static absl::string_view key() { return ":method"; }
   static MementoType ParseMemento(Slice value, MetadataParseErrorFn on_error);
   static ValueType MementoToValue(MementoType content_type) {
     return content_type;
   }
   static StaticSlice Encode(ValueType x);
-  static const char* DisplayValue(MementoType content_type);
+  static const char* DisplayValue(ValueType content_type);
+  static const char* DisplayMemento(MementoType content_type) {
+    return DisplayValue(content_type);
+  }
 };
 
 // Base type for metadata pertaining to a single compression algorithm
@@ -178,24 +238,28 @@
     GPR_ASSERT(x != GRPC_COMPRESS_ALGORITHMS_COUNT);
     return Slice::FromStaticString(CompressionAlgorithmAsString(x));
   }
-  static const char* DisplayValue(MementoType x) {
+  static const char* DisplayValue(ValueType x) {
     if (const char* p = CompressionAlgorithmAsString(x)) {
       return p;
     } else {
       return "<discarded-invalid-value>";
     }
   }
+  static const char* DisplayMemento(MementoType x) { return DisplayValue(x); }
 };
 
 // grpc-encoding metadata trait.
 struct GrpcEncodingMetadata : public CompressionAlgorithmBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits =
+      SmallIntegralValuesCompressor<GRPC_COMPRESS_ALGORITHMS_COUNT>;
   static absl::string_view key() { return "grpc-encoding"; }
 };
 
 // grpc-internal-encoding-request metadata trait.
 struct GrpcInternalEncodingRequest : public CompressionAlgorithmBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = NoCompressionCompressor;
   static absl::string_view key() { return "grpc-internal-encoding-request"; }
 };
 
@@ -205,12 +269,16 @@
   static absl::string_view key() { return "grpc-accept-encoding"; }
   using ValueType = CompressionAlgorithmSet;
   using MementoType = ValueType;
+  using CompressionTraits = StableValueCompressor;
   static MementoType ParseMemento(Slice value, MetadataParseErrorFn) {
     return CompressionAlgorithmSet::FromString(value.as_string_view());
   }
   static ValueType MementoToValue(MementoType x) { return x; }
   static Slice Encode(ValueType x) { return x.ToSlice(); }
-  static absl::string_view DisplayValue(MementoType x) { return x.ToString(); }
+  static absl::string_view DisplayValue(ValueType x) { return x.ToString(); }
+  static absl::string_view DisplayMemento(MementoType x) {
+    return DisplayValue(x);
+  }
 };
 
 struct SimpleSliceBasedMetadata {
@@ -221,7 +289,10 @@
   }
   static ValueType MementoToValue(MementoType value) { return value; }
   static Slice Encode(const ValueType& x) { return x.Ref(); }
-  static absl::string_view DisplayValue(const MementoType& value) {
+  static absl::string_view DisplayValue(const ValueType& value) {
+    return value.as_string_view();
+  }
+  static absl::string_view DisplayMemento(const MementoType& value) {
     return value.as_string_view();
   }
 };
@@ -229,54 +300,63 @@
 // user-agent metadata trait.
 struct UserAgentMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = StableValueCompressor;
   static absl::string_view key() { return "user-agent"; }
 };
 
 // grpc-message metadata trait.
 struct GrpcMessageMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = NoCompressionCompressor;
   static absl::string_view key() { return "grpc-message"; }
 };
 
 // host metadata trait.
 struct HostMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = NoCompressionCompressor;
   static absl::string_view key() { return "host"; }
 };
 
 // endpoint-load-metrics-bin metadata trait.
 struct EndpointLoadMetricsBinMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = NoCompressionCompressor;
   static absl::string_view key() { return "endpoint-load-metrics-bin"; }
 };
 
 // grpc-server-stats-bin metadata trait.
 struct GrpcServerStatsBinMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = NoCompressionCompressor;
   static absl::string_view key() { return "grpc-server-stats-bin"; }
 };
 
 // grpc-trace-bin metadata trait.
 struct GrpcTraceBinMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = FrequentKeyWithNoValueCompressionCompressor;
   static absl::string_view key() { return "grpc-trace-bin"; }
 };
 
 // grpc-tags-bin metadata trait.
 struct GrpcTagsBinMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = FrequentKeyWithNoValueCompressionCompressor;
   static absl::string_view key() { return "grpc-tags-bin"; }
 };
 
 // :authority metadata trait.
 struct HttpAuthorityMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = SmallSetOfValuesCompressor;
   static absl::string_view key() { return ":authority"; }
 };
 
 // :path metadata trait.
 struct HttpPathMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = SmallSetOfValuesCompressor;
   static absl::string_view key() { return ":path"; }
 };
 
@@ -289,7 +369,8 @@
   using MementoType = Int;
   static ValueType MementoToValue(MementoType value) { return value; }
   static Slice Encode(ValueType x) { return Slice::FromInt64(x); }
-  static Int DisplayValue(MementoType x) { return x; }
+  static Int DisplayValue(ValueType x) { return x; }
+  static Int DisplayMemento(MementoType x) { return x; }
 };
 
 template <typename Int, Int kInvalidValue>
@@ -309,6 +390,7 @@
 struct GrpcStatusMetadata
     : public SimpleIntBasedMetadata<grpc_status_code, GRPC_STATUS_UNKNOWN> {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = SmallIntegralValuesCompressor<16>;
   static absl::string_view key() { return "grpc-status"; }
 };
 
@@ -316,6 +398,7 @@
 struct GrpcPreviousRpcAttemptsMetadata
     : public SimpleIntBasedMetadata<uint32_t, 0> {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = NoCompressionCompressor;
   static absl::string_view key() { return "grpc-previous-rpc-attempts"; }
 };
 
@@ -325,9 +408,11 @@
   static absl::string_view key() { return "grpc-retry-pushback-ms"; }
   using ValueType = Duration;
   using MementoType = Duration;
+  using CompressionTraits = NoCompressionCompressor;
   static ValueType MementoToValue(MementoType x) { return x; }
   static Slice Encode(Duration x) { return Slice::FromInt64(x.millis()); }
   static int64_t DisplayValue(Duration x) { return x.millis(); }
+  static int64_t DisplayMemento(Duration x) { return DisplayValue(x); }
   static Duration ParseMemento(Slice value, MetadataParseErrorFn on_error);
 };
 
@@ -335,6 +420,7 @@
 // TODO(ctiller): consider moving to uint16_t
 struct HttpStatusMetadata : public SimpleIntBasedMetadata<uint32_t, 0> {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = HttpStatusCompressor;
   static absl::string_view key() { return ":status"; }
 };
 
@@ -347,9 +433,13 @@
   static absl::string_view key() { return "grpclb_client_stats"; }
   using ValueType = GrpcLbClientStats*;
   using MementoType = ValueType;
+  using CompressionTraits = NoCompressionCompressor;
   static ValueType MementoToValue(MementoType value) { return value; }
   static Slice Encode(ValueType) { abort(); }
-  static const char* DisplayValue(MementoType) { return "<internal-lb-stats>"; }
+  static const char* DisplayValue(ValueType) { return "<internal-lb-stats>"; }
+  static const char* DisplayMemento(MementoType) {
+    return "<internal-lb-stats>";
+  }
   static MementoType ParseMemento(Slice, MetadataParseErrorFn) {
     return nullptr;
   }
@@ -363,6 +453,7 @@
 // lb-token metadata
 struct LbTokenMetadata : public SimpleSliceBasedMetadata {
   static constexpr bool kRepeatable = false;
+  using CompressionTraits = NoCompressionCompressor;
   static absl::string_view key() { return "lb-token"; }
 };
 
@@ -375,9 +466,11 @@
     std::string name;
   };
   using MementoType = ValueType;
+  using CompressionTraits = NoCompressionCompressor;
   static ValueType MementoToValue(MementoType value) { return value; }
   static Slice Encode(const ValueType& x);
-  static std::string DisplayValue(MementoType x);
+  static std::string DisplayValue(ValueType x);
+  static std::string DisplayMemento(MementoType x) { return DisplayValue(x); }
   static MementoType ParseMemento(Slice value, MetadataParseErrorFn on_error);
 };
 
@@ -542,8 +635,9 @@
 
   GPR_ATTRIBUTE_NOINLINE ParsedMetadata<Container> NotFound(
       absl::string_view key) {
-    return ParsedMetadata<Container>(Slice::FromCopiedString(key),
-                                     std::move(value_));
+    return ParsedMetadata<Container>(
+        typename ParsedMetadata<Container>::FromSlicePair{},
+        Slice::FromCopiedString(key), std::move(value_), transport_size_);
   }
 
  private:
@@ -699,6 +793,11 @@
 };
 
 template <>
+struct AdaptDisplayValueToLog<const char*> {
+  static std::string ToString(const char* value) { return std::string(value); }
+};
+
+template <>
 struct AdaptDisplayValueToLog<StaticSlice> {
   static absl::string_view ToString(StaticSlice value) {
     return value.as_string_view();
@@ -739,7 +838,7 @@
     return EncodeTo(encoder);
   }
   void LogTo(LogFn log_fn) const {
-    LogKeyValueTo(Which::key(), value, Which::Encode, log_fn);
+    LogKeyValueTo(Which::key(), value, Which::DisplayValue, log_fn);
   }
   using StorageType = typename Which::ValueType;
   GPR_NO_UNIQUE_ADDRESS StorageType value;
@@ -954,6 +1053,35 @@
   ChunkedVector<std::pair<Slice, Slice>, 10> unknown_;
 };
 
+// Given a factory template Factory, construct a type that derives from
+// Factory<MetadataTrait, MetadataTrait::CompressionTraits> for all
+// MetadataTraits. Useful for transports in defining the stateful parts of their
+// compression algorithm.
+template <template <typename, typename> class Factory,
+          typename... MetadataTraits>
+struct StatefulCompressor;
+
+template <template <typename, typename> class Factory, typename MetadataTrait,
+          bool kEncodable = IsEncodableTrait<MetadataTrait>::value>
+struct SpecificStatefulCompressor;
+
+template <template <typename, typename> class Factory, typename MetadataTrait>
+struct SpecificStatefulCompressor<Factory, MetadataTrait, true>
+    : public Factory<MetadataTrait, typename MetadataTrait::CompressionTraits> {
+};
+
+template <template <typename, typename> class Factory, typename MetadataTrait>
+struct SpecificStatefulCompressor<Factory, MetadataTrait, false> {};
+
+template <template <typename, typename> class Factory, typename MetadataTrait,
+          typename... MetadataTraits>
+struct StatefulCompressor<Factory, MetadataTrait, MetadataTraits...>
+    : public SpecificStatefulCompressor<Factory, MetadataTrait>,
+      public StatefulCompressor<Factory, MetadataTraits...> {};
+
+template <template <typename, typename> class Factory>
+struct StatefulCompressor<Factory> {};
+
 }  // namespace metadata_detail
 
 // Helper function for encoders
@@ -1025,7 +1153,8 @@
 //   // Convert a value to something that can be passed to StrCat and
 //   displayed
 //   // for debugging
-//   static SomeStrCatableType DisplayValue(MementoType value) { ... }
+//   static SomeStrCatableType DisplayValue(ValueType value) { ... }
+//   static SomeStrCatableType DisplayMemento(MementoType value) { ... }
 // };
 //
 // Non-encodable traits are determined by missing the key() method, and have
@@ -1071,6 +1200,15 @@
   explicit MetadataMap(Arena* arena);
   ~MetadataMap();
 
+  // Given a compressor factory - template taking <MetadataTrait,
+  // CompressionTrait>, StatefulCompressor<Factory> provides a type
+  // derived from all Encodable traits in this MetadataMap.
+  // This can be used by transports to delegate compression to the appropriate
+  // compression algorithm.
+  template <template <typename, typename> class Factory>
+  using StatefulCompressor =
+      metadata_detail::StatefulCompressor<Factory, Traits...>;
+
   MetadataMap(const MetadataMap&) = delete;
   MetadataMap& operator=(const MetadataMap&) = delete;
   MetadataMap(MetadataMap&&) noexcept;
diff --git a/src/core/lib/transport/parsed_metadata.h b/src/core/lib/transport/parsed_metadata.h
index f69ff96..d8fb245 100644
--- a/src/core/lib/transport/parsed_metadata.h
+++ b/src/core/lib/transport/parsed_metadata.h
@@ -26,6 +26,7 @@
 
 #include "absl/functional/function_ref.h"
 #include "absl/meta/type_traits.h"
+#include "absl/strings/escaping.h"
 #include "absl/strings/match.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
@@ -151,9 +152,14 @@
     value_.slice = value.TakeCSlice();
   }
   // Construct metadata from a string key, slice value pair.
-  ParsedMetadata(Slice key, Slice value)
+  // FromSlicePair() is used to adjust the overload set so that we don't
+  // inadvertently match against any of the previous overloads.
+  // TODO(ctiller): re-evaluate the overload functions here so and maybe
+  // introduce some factory functions?
+  struct FromSlicePair {};
+  ParsedMetadata(FromSlicePair, Slice key, Slice value, uint32_t transport_size)
       : vtable_(ParsedMetadata::KeyValueVTable(key.as_string_view())),
-        transport_size_(static_cast<uint32_t>(key.size() + value.size() + 32)) {
+        transport_size_(transport_size) {
     value_.pointer =
         new std::pair<Slice, Slice>(std::move(key), std::move(value));
   }
@@ -187,14 +193,13 @@
   // HTTP2 defined storage size of this metadatum.
   uint32_t transport_size() const { return transport_size_; }
   // Create a new parsed metadata with the same key but a different value.
-  ParsedMetadata WithNewValue(Slice value,
+  ParsedMetadata WithNewValue(Slice value, uint32_t value_wire_size,
                               MetadataParseErrorFn on_error) const {
     ParsedMetadata result;
     result.vtable_ = vtable_;
     result.value_ = value_;
     result.transport_size_ =
-        TransportSize(static_cast<uint32_t>(key().length()),
-                      static_cast<uint32_t>(value.length()));
+        TransportSize(static_cast<uint32_t>(key().length()), value_wire_size);
     vtable_->with_new_value(&value, on_error, &result);
     return result;
   }
@@ -300,7 +305,7 @@
         return metadata_detail::MakeDebugStringPipeline(
             Which::key(), value,
             metadata_detail::FieldFromTrivial<typename Which::MementoType>,
-            Which::DisplayValue);
+            Which::DisplayMemento);
       },
       // key
       Which::key(),
@@ -334,7 +339,7 @@
         return metadata_detail::MakeDebugStringPipeline(
             Which::key(), value,
             metadata_detail::FieldFromPointer<typename Which::MementoType>,
-            Which::DisplayValue);
+            Which::DisplayMemento);
       },
       // key
       Which::key(),
@@ -362,7 +367,7 @@
       [](const Buffer& value) {
         return metadata_detail::MakeDebugStringPipeline(
             Which::key(), value, metadata_detail::SliceFromBuffer,
-            Which::DisplayValue);
+            Which::DisplayMemento);
       },
       // key
       Which::key(),
@@ -395,12 +400,17 @@
     return absl::StrCat(p->first.as_string_view(), ": ",
                         p->second.as_string_view());
   };
+  static const auto binary_debug_string = [](const Buffer& value) {
+    auto* p = static_cast<KV*>(value.pointer);
+    return absl::StrCat(p->first.as_string_view(), ": \"",
+                        absl::CEscape(p->second.as_string_view()), "\"");
+  };
   static const auto key_fn = [](const Buffer& value) {
     return static_cast<KV*>(value.pointer)->first.as_string_view();
   };
   static const VTable vtable[2] = {
       {false, destroy, set, with_new_value, debug_string, "", key_fn},
-      {true, destroy, set, with_new_value, debug_string, "", key_fn},
+      {true, destroy, set, with_new_value, binary_debug_string, "", key_fn},
   };
   return &vtable[absl::EndsWith(key, "-bin")];
 }
diff --git a/src/objective-c/tests/CronetTests/CoreCronetEnd2EndTests.mm b/src/objective-c/tests/CronetTests/CoreCronetEnd2EndTests.mm
index b34aac5..2b37358 100644
--- a/src/objective-c/tests/CronetTests/CoreCronetEnd2EndTests.mm
+++ b/src/objective-c/tests/CronetTests/CoreCronetEnd2EndTests.mm
@@ -44,6 +44,7 @@
 #include "src/core/lib/security/credentials/credentials.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/data/ssl_test_data.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
@@ -52,100 +53,44 @@
 
 #import "../ConfigureCronet.h"
 
-struct fullstack_secure_fixture_data {
-  std::string localaddr;
-};
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
-    const grpc_channel_args *client_args, const grpc_channel_args *server_args) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data *ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-
-  ffd->localaddr = grpc_core::JoinHostPort("127.0.0.1", port);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(NULL);
-
-  return f;
-}
-
 static void process_auth_failure(void *state, grpc_auth_context *ctx, const grpc_metadata *md,
                                  size_t md_count, grpc_process_auth_metadata_done_cb cb,
                                  void *user_data) {
-  GPR_ASSERT(state == NULL);
-  cb(user_data, NULL, 0, NULL, 0, GRPC_STATUS_UNAUTHENTICATED, NULL);
+  GPR_ASSERT(state == nullptr);
+  cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
 }
 
-static void cronet_init_client_secure_fullstack(grpc_end2end_test_fixture *f,
-                                                const grpc_channel_args *client_args,
-                                                stream_engine *cronetEngine) {
-  fullstack_secure_fixture_data *ffd = (fullstack_secure_fixture_data *)f->fixture_data;
-  f->client =
-      grpc_cronet_secure_channel_create(cronetEngine, ffd->localaddr.c_str(), client_args, NULL);
-  GPR_ASSERT(f->client != NULL);
-}
-
-static void chttp2_init_server_secure_fullstack(grpc_end2end_test_fixture *f,
-                                                const grpc_channel_args *server_args,
-                                                grpc_server_credentials *server_creds) {
-  fullstack_secure_fixture_data *ffd = (fullstack_secure_fixture_data *)f->fixture_data;
-  if (f->server) {
-    grpc_server_destroy(f->server);
+class CronetFixture final : public SecureFixture {
+ private:
+  grpc_channel_credentials *MakeClientCreds(const grpc_core::ChannelArgs &args) override {
+    grpc_core::Crash("unreachable");
   }
-  f->server = grpc_server_create(server_args, NULL);
-  grpc_server_register_completion_queue(f->server, f->cq, NULL);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(), server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-static void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture *f) {
-  fullstack_secure_fixture_data *ffd = (fullstack_secure_fixture_data *)f->fixture_data;
-  delete ffd;
-}
-
-static void cronet_init_client_simple_ssl_secure_fullstack(grpc_end2end_test_fixture *f,
-                                                           const grpc_channel_args *client_args) {
-  grpc_core::ExecCtx exec_ctx;
-  stream_engine *cronetEngine = [Cronet getGlobalEngine];
-
-  const grpc_channel_args *new_client_args = grpc_channel_args_copy(client_args);
-  cronet_init_client_secure_fullstack(f, new_client_args, cronetEngine);
-  grpc_channel_args_destroy(new_client_args);
-}
-
-static int fail_server_auth_check(const grpc_channel_args *server_args) {
-  size_t i;
-  if (server_args == NULL) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) == 0) {
-      return 1;
+  grpc_server_credentials *MakeServerCreds(const grpc_core::ChannelArgs &args) override {
+    grpc_ssl_pem_key_cert_pair pem_cert_key_pair = {test_server1_key, test_server1_cert};
+    grpc_server_credentials *ssl_creds =
+        grpc_ssl_server_credentials_create(nullptr, &pem_cert_key_pair, 1, 0, nullptr);
+    if (args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)) {
+      grpc_auth_metadata_processor processor = {process_auth_failure, nullptr, nullptr};
+      grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
     }
+    return ssl_creds;
   }
-  return 0;
-}
-
-static void chttp2_init_server_simple_ssl_secure_fullstack(grpc_end2end_test_fixture *f,
-                                                           const grpc_channel_args *server_args) {
-  grpc_ssl_pem_key_cert_pair pem_cert_key_pair = {test_server1_key, test_server1_cert};
-  grpc_server_credentials *ssl_creds =
-      grpc_ssl_server_credentials_create(NULL, &pem_cert_key_pair, 1, 0, NULL);
-  if (fail_server_auth_check(server_args)) {
-    grpc_auth_metadata_processor processor = {process_auth_failure, NULL, NULL};
-    grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
+  grpc_channel *MakeClient(const grpc_core::ChannelArgs &args) override {
+    stream_engine *cronetEngine = [Cronet getGlobalEngine];
+    return grpc_cronet_secure_channel_create(cronetEngine, localaddr().c_str(), args.ToC().get(),
+                                             nullptr);
   }
-  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
-}
+};
 
 /* All test configurations */
 
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/simple_ssl_fullstack",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION | FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS, nullptr,
-     chttp2_create_fixture_secure_fullstack, cronet_init_client_simple_ssl_secure_fullstack,
-     chttp2_init_server_simple_ssl_secure_fullstack, chttp2_tear_down_secure_fullstack},
+     [](const grpc_core::ChannelArgs & /*client_args*/,
+        const grpc_core::ChannelArgs & /*server_args*/) {
+       return std::make_unique<CronetFixture>();
+     }},
 };
 
 static char *roots_filename;
@@ -170,8 +115,8 @@
 
   /* Set the SSL roots env var. */
   roots_file = gpr_tmpfile("chttp2_simple_ssl_fullstack_test", &roots_filename);
-  GPR_ASSERT(roots_filename != NULL);
-  GPR_ASSERT(roots_file != NULL);
+  GPR_ASSERT(roots_filename != nullptr);
+  GPR_ASSERT(roots_file != nullptr);
   GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
   fclose(roots_file);
   GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, roots_filename);
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index 6f474ac..b9f377e 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -459,6 +459,7 @@
     'src/core/lib/address_utils/parse_address.cc',
     'src/core/lib/address_utils/sockaddr_utils.cc',
     'src/core/lib/backoff/backoff.cc',
+    'src/core/lib/backoff/random_early_detection.cc',
     'src/core/lib/channel/channel_args.cc',
     'src/core/lib/channel/channel_args_preconditioning.cc',
     'src/core/lib/channel/channel_stack.cc',
diff --git a/templates/test/core/end2end/end2end_defs.include b/templates/test/core/end2end/end2end_defs.include
index 9bbe9aa..81ea76c 100644
--- a/templates/test/core/end2end/end2end_defs.include
+++ b/templates/test/core/end2end/end2end_defs.include
@@ -34,7 +34,7 @@
 static bool g_pre_init_called = false;
 
 % for test in tests:
-extern void ${test}(grpc_end2end_test_config config);
+extern void ${test}(const CoreTestConfiguration& config);
 extern void ${test}_pre_init(void);
 % endfor
 
@@ -48,7 +48,7 @@
 
 // NOLINTNEXTLINE(readability-function-size)
 void grpc_end2end_tests(int argc, char **argv,
-                        grpc_end2end_test_config config) {
+                    const CoreTestConfiguration& config) {
   int i;
 
   GPR_ASSERT(g_pre_init_called);
diff --git a/test/core/bad_client/bad_client.cc b/test/core/bad_client/bad_client.cc
index 2693d1a..0f26d91 100644
--- a/test/core/bad_client/bad_client.cc
+++ b/test/core/bad_client/bad_client.cc
@@ -319,8 +319,6 @@
   return success;
 }
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 void server_verifier_request_call(grpc_server* server,
                                   grpc_completion_queue* cq,
                                   void* /*registered_method*/) {
@@ -334,9 +332,10 @@
   grpc_metadata_array_init(&request_metadata_recv);
 
   error = grpc_server_request_call(server, &s, &call_details,
-                                   &request_metadata_recv, cq, cq, tag(101));
+                                   &request_metadata_recv, cq, cq,
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.host, "localhost"));
diff --git a/test/core/bad_client/tests/duplicate_header.cc b/test/core/bad_client/tests/duplicate_header.cc
index 2f78b44..2ee3b81 100644
--- a/test/core/bad_client/tests/duplicate_header.cc
+++ b/test/core/bad_client/tests/duplicate_header.cc
@@ -16,7 +16,6 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
 #include <grpc/grpc.h>
@@ -52,8 +51,6 @@
   "\x00\x00\x20\x00\x00\x00\x00\x00\x01" \
   "\x00\x00\x00\x00"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 static void verifier(grpc_server* server, grpc_completion_queue* cq,
                      void* /*registered_method*/) {
   grpc_call_error error;
@@ -70,9 +67,10 @@
   grpc_metadata_array_init(&request_metadata_recv);
 
   error = grpc_server_request_call(server, &s, &call_details,
-                                   &request_metadata_recv, cq, cq, tag(101));
+                                   &request_metadata_recv, cq, cq,
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.host, "localhost"));
@@ -90,11 +88,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), grpc_core::CqVerifier::AnyStatus());
+  cqv.Expect(grpc_core::CqVerifier::tag(102),
+             grpc_core::CqVerifier::AnyStatus());
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -112,11 +111,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
   cqv.Verify();
 
   grpc_metadata_array_destroy(&request_metadata_recv);
diff --git a/test/core/bad_client/tests/head_of_line_blocking.cc b/test/core/bad_client/tests/head_of_line_blocking.cc
index 2884160..7360d8e 100644
--- a/test/core/bad_client/tests/head_of_line_blocking.cc
+++ b/test/core/bad_client/tests/head_of_line_blocking.cc
@@ -71,8 +71,6 @@
     "\x01\x00\x00\x27\x10"
     "";
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 static void verifier(grpc_server* server, grpc_completion_queue* cq,
                      void* registered_method) {
   grpc_call_error error;
@@ -84,11 +82,11 @@
 
   grpc_metadata_array_init(&request_metadata_recv);
 
-  error = grpc_server_request_registered_call(server, registered_method, &s,
-                                              &deadline, &request_metadata_recv,
-                                              &payload, cq, cq, tag(101));
+  error = grpc_server_request_registered_call(
+      server, registered_method, &s, &deadline, &request_metadata_recv,
+      &payload, cq, cq, grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   GPR_ASSERT(payload != nullptr);
diff --git a/test/core/bad_client/tests/server_registered_method.cc b/test/core/bad_client/tests/server_registered_method.cc
index f9c9e67..2b9059b 100644
--- a/test/core/bad_client/tests/server_registered_method.cc
+++ b/test/core/bad_client/tests/server_registered_method.cc
@@ -16,8 +16,6 @@
 //
 //
 
-#include <stdint.h>
-
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
@@ -43,8 +41,6 @@
   "\x10\x02te\x08trailers"                                    \
   "\x10\x0auser-agent\"bad-client grpc-c/0.12.0.0 (linux)"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 static void verifier_succeeds(grpc_server* server, grpc_completion_queue* cq,
                               void* registered_method) {
   grpc_call_error error;
@@ -56,11 +52,11 @@
 
   grpc_metadata_array_init(&request_metadata_recv);
 
-  error = grpc_server_request_registered_call(server, registered_method, &s,
-                                              &deadline, &request_metadata_recv,
-                                              &payload, cq, cq, tag(101));
+  error = grpc_server_request_registered_call(
+      server, registered_method, &s, &deadline, &request_metadata_recv,
+      &payload, cq, cq, grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   GPR_ASSERT(payload != nullptr);
diff --git a/test/core/bad_client/tests/simple_request.cc b/test/core/bad_client/tests/simple_request.cc
index f5f6dca..628b9d5 100644
--- a/test/core/bad_client/tests/simple_request.cc
+++ b/test/core/bad_client/tests/simple_request.cc
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <stdint.h>
-
 #include <grpc/grpc.h>
 #include <grpc/slice.h>
 #include <grpc/support/log.h>
@@ -83,8 +81,6 @@
   "\x10\x0cgrpc-timeout\x02"                                                \
   "5S"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 static void verifier(grpc_server* server, grpc_completion_queue* cq,
                      void* /*registered_method*/) {
   grpc_call_error error;
@@ -97,9 +93,10 @@
   grpc_metadata_array_init(&request_metadata_recv);
 
   error = grpc_server_request_call(server, &s, &call_details,
-                                   &request_metadata_recv, cq, cq, tag(101));
+                                   &request_metadata_recv, cq, cq,
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.host, "localhost"));
diff --git a/test/core/bad_ssl/bad_ssl_test.cc b/test/core/bad_ssl/bad_ssl_test.cc
index 0f86a89..db4f7fc 100644
--- a/test/core/bad_ssl/bad_ssl_test.cc
+++ b/test/core/bad_ssl/bad_ssl_test.cc
@@ -16,7 +16,6 @@
 //
 //
 
-#include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -40,8 +39,6 @@
 #include "test/core/util/subprocess.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 static void run_test(const char* target, size_t nops) {
   grpc_channel_credentials* ssl_creds =
       grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
@@ -101,10 +98,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, nops, tag(1), nullptr);
+  error = grpc_call_start_batch(c, ops, nops, grpc_core::CqVerifier::tag(1),
+                                nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status != GRPC_STATUS_OK);
diff --git a/test/core/end2end/BUILD b/test/core/end2end/BUILD
index a1f0cb2..e72edec 100644
--- a/test/core/end2end/BUILD
+++ b/test/core/end2end/BUILD
@@ -100,16 +100,44 @@
 )
 
 grpc_cc_library(
-    name = "local_util",
+    name = "fixture_support",
     srcs = ["fixtures/local_util.cc"],
     hdrs = [
         "end2end_tests.h",
+        "fixtures/h2_oauth2_common.h",
+        "fixtures/h2_ssl_cred_reload_fixture.h",
+        "fixtures/h2_ssl_tls_common.h",
+        "fixtures/h2_tls_common.h",
+        "fixtures/inproc_fixture.h",
         "fixtures/local_util.h",
+        "fixtures/secure_fixture.h",
+        "fixtures/sockpair_fixture.h",
+    ],
+    external_deps = [
+        "absl/status",
+        "absl/status:statusor",
+        "absl/strings",
     ],
     language = "C++",
     deps = [
+        "//:config",
+        "//:exec_ctx",
         "//:gpr",
+        "//:grpc",
         "//:grpc_public_hdrs",
+        "//:grpc_security_base",
+        "//:grpc_transport_chttp2",
+        "//:ref_counted_ptr",
+        "//src/core:channel_args",
+        "//src/core:channel_args_preconditioning",
+        "//src/core:channel_stack_type",
+        "//src/core:error",
+        "//src/core:grpc_ssl_credentials",
+        "//src/core:grpc_tls_credentials",
+        "//src/core:grpc_transport_inproc",
+        "//src/core:slice",
+        "//src/core:transport_fwd",
+        "//test/core/util:grpc_test_util",
     ],
 )
 
@@ -201,22 +229,6 @@
 )
 
 grpc_cc_test(
-    name = "inproc_callback_test",
-    srcs = ["inproc_callback_test.cc"],
-    external_deps = ["absl/strings:str_format"],
-    language = "C++",
-    uses_event_engine = False,
-    uses_polling = False,
-    deps = [
-        "local_util",
-        "//:gpr",
-        "//:grpc_public_hdrs",
-        "//src/core:grpc_transport_inproc",
-        "//test/core/util:grpc_test_util",
-    ],
-)
-
-grpc_cc_test(
     name = "invalid_call_argument_test",
     srcs = ["invalid_call_argument_test.cc"],
     language = "C++",
diff --git a/test/core/end2end/connection_refused_test.cc b/test/core/end2end/connection_refused_test.cc
index 4e217ea..c7f5236 100644
--- a/test/core/end2end/connection_refused_test.cc
+++ b/test/core/end2end/connection_refused_test.cc
@@ -16,7 +16,6 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
 #include <string>
@@ -36,8 +35,6 @@
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 static void run_test(bool wait_for_ready, bool use_service_config) {
   grpc_channel* chan;
   grpc_call* call;
@@ -107,11 +104,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(call, ops,
-                                                   (size_t)(op - ops), tag(1),
-                                                   nullptr));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(call, ops, (size_t)(op - ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   // verify that all tags get completed
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   if (wait_for_ready) {
diff --git a/test/core/end2end/cq_verifier.h b/test/core/end2end/cq_verifier.h
index 5c01007..d92e6de 100644
--- a/test/core/end2end/cq_verifier.h
+++ b/test/core/end2end/cq_verifier.h
@@ -19,6 +19,8 @@
 #ifndef GRPC_TEST_CORE_END2END_CQ_VERIFIER_H
 #define GRPC_TEST_CORE_END2END_CQ_VERIFIER_H
 
+#include <stdint.h>
+
 #include <string>
 #include <vector>
 
@@ -73,6 +75,8 @@
 
   std::string ToString() const;
 
+  static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
+
  private:
   struct Expectation {
     SourceLocation location;
diff --git a/test/core/end2end/dualstack_socket_test.cc b/test/core/end2end/dualstack_socket_test.cc
index 39ca046..16d7df2 100644
--- a/test/core/end2end/dualstack_socket_test.cc
+++ b/test/core/end2end/dualstack_socket_test.cc
@@ -16,8 +16,6 @@
 //
 //
 
-#include <stdint.h>
-
 #include <algorithm>
 #include <initializer_list>
 #include <memory>
@@ -62,8 +60,6 @@
 
 // This test exercises IPv4, IPv6, and dualstack sockets in various ways.
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 static void drain_cq(grpc_completion_queue* cq) {
   grpc_event ev;
   do {
@@ -200,16 +196,17 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   if (expect_ok) {
     // Check for a successful request.
     error = grpc_server_request_call(server, &s, &call_details,
-                                     &request_metadata_recv, cq, cq, tag(101));
+                                     &request_metadata_recv, cq, cq,
+                                     grpc_core::CqVerifier::tag(101));
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv.Expect(tag(101), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(101), true);
     cqv.Verify();
 
     memset(ops, 0, sizeof(ops));
@@ -230,11 +227,11 @@
     op->flags = 0;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
+                                  grpc_core::CqVerifier::tag(102), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
-    cqv.Expect(tag(102), true);
-    cqv.Expect(tag(1), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(1), true);
     cqv.Verify();
 
     peer = grpc_call_get_peer(c);
@@ -251,7 +248,7 @@
     grpc_call_unref(s);
   } else {
     // Check for a failed connection.
-    cqv.Expect(tag(1), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(1), true);
     cqv.Verify();
 
     gpr_log(GPR_INFO, "status: %d (expected: %d)", status,
@@ -265,12 +262,13 @@
   grpc_channel_destroy(client);
 
   // Destroy server.
-  grpc_server_shutdown_and_notify(server, cq, tag(1000));
+  grpc_server_shutdown_and_notify(server, cq, grpc_core::CqVerifier::tag(1000));
   grpc_event ev;
   do {
     ev = grpc_completion_queue_next(cq, grpc_timeout_seconds_to_deadline(5),
                                     nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
+  } while (ev.type != GRPC_OP_COMPLETE ||
+           ev.tag != grpc_core::CqVerifier::tag(1000));
 
   grpc_server_destroy(server);
   grpc_completion_queue_shutdown(cq);
diff --git a/test/core/end2end/end2end_test_utils.cc b/test/core/end2end/end2end_test_utils.cc
index d169225..a3c6b41 100644
--- a/test/core/end2end/end2end_test_utils.cc
+++ b/test/core/end2end/end2end_test_utils.cc
@@ -22,7 +22,7 @@
 #include "test/core/end2end/end2end_tests.h"
 
 const char* get_host_override_string(const char* str,
-                                     grpc_end2end_test_config config) {
+                                     const CoreTestConfiguration& config) {
   if (config.feature_mask & FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER) {
     return str;
   } else {
@@ -31,7 +31,7 @@
 }
 
 const grpc_slice* get_host_override_slice(const char* str,
-                                          grpc_end2end_test_config config) {
+                                          const CoreTestConfiguration& config) {
   const char* r = get_host_override_string(str, config);
   if (r != nullptr) {
     static grpc_slice ret;
@@ -42,7 +42,7 @@
 }
 
 void validate_host_override_string(const char* pattern, grpc_slice str,
-                                   grpc_end2end_test_config config) {
+                                   const CoreTestConfiguration& config) {
   if (config.feature_mask & FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER) {
     GPR_ASSERT(0 == grpc_slice_str_cmp(str, pattern));
   }
diff --git a/test/core/end2end/end2end_tests.cc b/test/core/end2end/end2end_tests.cc
index d95b7ab..da3bf9e 100644
--- a/test/core/end2end/end2end_tests.cc
+++ b/test/core/end2end/end2end_tests.cc
@@ -32,191 +32,191 @@
 
 static bool g_pre_init_called = false;
 
-extern void authority_not_supported(grpc_end2end_test_config config);
+extern void authority_not_supported(const CoreTestConfiguration& config);
 extern void authority_not_supported_pre_init(void);
-extern void bad_hostname(grpc_end2end_test_config config);
+extern void bad_hostname(const CoreTestConfiguration& config);
 extern void bad_hostname_pre_init(void);
-extern void bad_ping(grpc_end2end_test_config config);
+extern void bad_ping(const CoreTestConfiguration& config);
 extern void bad_ping_pre_init(void);
-extern void binary_metadata(grpc_end2end_test_config config);
+extern void binary_metadata(const CoreTestConfiguration& config);
 extern void binary_metadata_pre_init(void);
-extern void call_creds(grpc_end2end_test_config config);
+extern void call_creds(const CoreTestConfiguration& config);
 extern void call_creds_pre_init(void);
-extern void call_host_override(grpc_end2end_test_config config);
+extern void call_host_override(const CoreTestConfiguration& config);
 extern void call_host_override_pre_init(void);
-extern void cancel_after_accept(grpc_end2end_test_config config);
+extern void cancel_after_accept(const CoreTestConfiguration& config);
 extern void cancel_after_accept_pre_init(void);
-extern void cancel_after_client_done(grpc_end2end_test_config config);
+extern void cancel_after_client_done(const CoreTestConfiguration& config);
 extern void cancel_after_client_done_pre_init(void);
-extern void cancel_after_invoke(grpc_end2end_test_config config);
+extern void cancel_after_invoke(const CoreTestConfiguration& config);
 extern void cancel_after_invoke_pre_init(void);
-extern void cancel_after_round_trip(grpc_end2end_test_config config);
+extern void cancel_after_round_trip(const CoreTestConfiguration& config);
 extern void cancel_after_round_trip_pre_init(void);
-extern void cancel_before_invoke(grpc_end2end_test_config config);
+extern void cancel_before_invoke(const CoreTestConfiguration& config);
 extern void cancel_before_invoke_pre_init(void);
-extern void cancel_in_a_vacuum(grpc_end2end_test_config config);
+extern void cancel_in_a_vacuum(const CoreTestConfiguration& config);
 extern void cancel_in_a_vacuum_pre_init(void);
-extern void cancel_with_status(grpc_end2end_test_config config);
+extern void cancel_with_status(const CoreTestConfiguration& config);
 extern void cancel_with_status_pre_init(void);
-extern void channelz(grpc_end2end_test_config config);
+extern void channelz(const CoreTestConfiguration& config);
 extern void channelz_pre_init(void);
-extern void client_streaming(grpc_end2end_test_config config);
+extern void client_streaming(const CoreTestConfiguration& config);
 extern void client_streaming_pre_init(void);
-extern void compressed_payload(grpc_end2end_test_config config);
+extern void compressed_payload(const CoreTestConfiguration& config);
 extern void compressed_payload_pre_init(void);
-extern void connectivity(grpc_end2end_test_config config);
+extern void connectivity(const CoreTestConfiguration& config);
 extern void connectivity_pre_init(void);
-extern void default_host(grpc_end2end_test_config config);
+extern void default_host(const CoreTestConfiguration& config);
 extern void default_host_pre_init(void);
-extern void disappearing_server(grpc_end2end_test_config config);
+extern void disappearing_server(const CoreTestConfiguration& config);
 extern void disappearing_server_pre_init(void);
-extern void empty_batch(grpc_end2end_test_config config);
+extern void empty_batch(const CoreTestConfiguration& config);
 extern void empty_batch_pre_init(void);
-extern void filter_causes_close(grpc_end2end_test_config config);
+extern void filter_causes_close(const CoreTestConfiguration& config);
 extern void filter_causes_close_pre_init(void);
-extern void filter_context(grpc_end2end_test_config config);
+extern void filter_context(const CoreTestConfiguration& config);
 extern void filter_context_pre_init(void);
-extern void filter_init_fails(grpc_end2end_test_config config);
+extern void filter_init_fails(const CoreTestConfiguration& config);
 extern void filter_init_fails_pre_init(void);
-extern void filter_latency(grpc_end2end_test_config config);
+extern void filter_latency(const CoreTestConfiguration& config);
 extern void filter_latency_pre_init(void);
-extern void filter_status_code(grpc_end2end_test_config config);
+extern void filter_status_code(const CoreTestConfiguration& config);
 extern void filter_status_code_pre_init(void);
-extern void filtered_metadata(grpc_end2end_test_config config);
+extern void filtered_metadata(const CoreTestConfiguration& config);
 extern void filtered_metadata_pre_init(void);
-extern void graceful_server_shutdown(grpc_end2end_test_config config);
+extern void graceful_server_shutdown(const CoreTestConfiguration& config);
 extern void graceful_server_shutdown_pre_init(void);
-extern void grpc_authz(grpc_end2end_test_config config);
+extern void grpc_authz(const CoreTestConfiguration& config);
 extern void grpc_authz_pre_init(void);
-extern void high_initial_seqno(grpc_end2end_test_config config);
+extern void high_initial_seqno(const CoreTestConfiguration& config);
 extern void high_initial_seqno_pre_init(void);
-extern void hpack_size(grpc_end2end_test_config config);
+extern void hpack_size(const CoreTestConfiguration& config);
 extern void hpack_size_pre_init(void);
-extern void invoke_large_request(grpc_end2end_test_config config);
+extern void invoke_large_request(const CoreTestConfiguration& config);
 extern void invoke_large_request_pre_init(void);
-extern void keepalive_timeout(grpc_end2end_test_config config);
+extern void keepalive_timeout(const CoreTestConfiguration& config);
 extern void keepalive_timeout_pre_init(void);
-extern void large_metadata(grpc_end2end_test_config config);
+extern void large_metadata(const CoreTestConfiguration& config);
 extern void large_metadata_pre_init(void);
-extern void max_concurrent_streams(grpc_end2end_test_config config);
+extern void max_concurrent_streams(const CoreTestConfiguration& config);
 extern void max_concurrent_streams_pre_init(void);
-extern void max_connection_age(grpc_end2end_test_config config);
+extern void max_connection_age(const CoreTestConfiguration& config);
 extern void max_connection_age_pre_init(void);
-extern void max_connection_idle(grpc_end2end_test_config config);
+extern void max_connection_idle(const CoreTestConfiguration& config);
 extern void max_connection_idle_pre_init(void);
-extern void max_message_length(grpc_end2end_test_config config);
+extern void max_message_length(const CoreTestConfiguration& config);
 extern void max_message_length_pre_init(void);
-extern void negative_deadline(grpc_end2end_test_config config);
+extern void negative_deadline(const CoreTestConfiguration& config);
 extern void negative_deadline_pre_init(void);
-extern void no_logging(grpc_end2end_test_config config);
+extern void no_logging(const CoreTestConfiguration& config);
 extern void no_logging_pre_init(void);
-extern void no_op(grpc_end2end_test_config config);
+extern void no_op(const CoreTestConfiguration& config);
 extern void no_op_pre_init(void);
-extern void payload(grpc_end2end_test_config config);
+extern void payload(const CoreTestConfiguration& config);
 extern void payload_pre_init(void);
-extern void ping(grpc_end2end_test_config config);
+extern void ping(const CoreTestConfiguration& config);
 extern void ping_pre_init(void);
-extern void ping_pong_streaming(grpc_end2end_test_config config);
+extern void ping_pong_streaming(const CoreTestConfiguration& config);
 extern void ping_pong_streaming_pre_init(void);
-extern void proxy_auth(grpc_end2end_test_config config);
+extern void proxy_auth(const CoreTestConfiguration& config);
 extern void proxy_auth_pre_init(void);
-extern void registered_call(grpc_end2end_test_config config);
+extern void registered_call(const CoreTestConfiguration& config);
 extern void registered_call_pre_init(void);
-extern void request_with_flags(grpc_end2end_test_config config);
+extern void request_with_flags(const CoreTestConfiguration& config);
 extern void request_with_flags_pre_init(void);
-extern void request_with_payload(grpc_end2end_test_config config);
+extern void request_with_payload(const CoreTestConfiguration& config);
 extern void request_with_payload_pre_init(void);
-extern void resource_quota_server(grpc_end2end_test_config config);
+extern void resource_quota_server(const CoreTestConfiguration& config);
 extern void resource_quota_server_pre_init(void);
-extern void retry(grpc_end2end_test_config config);
+extern void retry(const CoreTestConfiguration& config);
 extern void retry_pre_init(void);
-extern void retry_cancel_after_first_attempt_starts(grpc_end2end_test_config config);
+extern void retry_cancel_after_first_attempt_starts(const CoreTestConfiguration& config);
 extern void retry_cancel_after_first_attempt_starts_pre_init(void);
-extern void retry_cancel_during_delay(grpc_end2end_test_config config);
+extern void retry_cancel_during_delay(const CoreTestConfiguration& config);
 extern void retry_cancel_during_delay_pre_init(void);
-extern void retry_cancel_with_multiple_send_batches(grpc_end2end_test_config config);
+extern void retry_cancel_with_multiple_send_batches(const CoreTestConfiguration& config);
 extern void retry_cancel_with_multiple_send_batches_pre_init(void);
-extern void retry_cancellation(grpc_end2end_test_config config);
+extern void retry_cancellation(const CoreTestConfiguration& config);
 extern void retry_cancellation_pre_init(void);
-extern void retry_disabled(grpc_end2end_test_config config);
+extern void retry_disabled(const CoreTestConfiguration& config);
 extern void retry_disabled_pre_init(void);
-extern void retry_exceeds_buffer_size_in_delay(grpc_end2end_test_config config);
+extern void retry_exceeds_buffer_size_in_delay(const CoreTestConfiguration& config);
 extern void retry_exceeds_buffer_size_in_delay_pre_init(void);
-extern void retry_exceeds_buffer_size_in_initial_batch(grpc_end2end_test_config config);
+extern void retry_exceeds_buffer_size_in_initial_batch(const CoreTestConfiguration& config);
 extern void retry_exceeds_buffer_size_in_initial_batch_pre_init(void);
-extern void retry_exceeds_buffer_size_in_subsequent_batch(grpc_end2end_test_config config);
+extern void retry_exceeds_buffer_size_in_subsequent_batch(const CoreTestConfiguration& config);
 extern void retry_exceeds_buffer_size_in_subsequent_batch_pre_init(void);
-extern void retry_lb_drop(grpc_end2end_test_config config);
+extern void retry_lb_drop(const CoreTestConfiguration& config);
 extern void retry_lb_drop_pre_init(void);
-extern void retry_lb_fail(grpc_end2end_test_config config);
+extern void retry_lb_fail(const CoreTestConfiguration& config);
 extern void retry_lb_fail_pre_init(void);
-extern void retry_non_retriable_status(grpc_end2end_test_config config);
+extern void retry_non_retriable_status(const CoreTestConfiguration& config);
 extern void retry_non_retriable_status_pre_init(void);
-extern void retry_non_retriable_status_before_recv_trailing_metadata_started(grpc_end2end_test_config config);
+extern void retry_non_retriable_status_before_recv_trailing_metadata_started(const CoreTestConfiguration& config);
 extern void retry_non_retriable_status_before_recv_trailing_metadata_started_pre_init(void);
-extern void retry_per_attempt_recv_timeout(grpc_end2end_test_config config);
+extern void retry_per_attempt_recv_timeout(const CoreTestConfiguration& config);
 extern void retry_per_attempt_recv_timeout_pre_init(void);
-extern void retry_per_attempt_recv_timeout_on_last_attempt(grpc_end2end_test_config config);
+extern void retry_per_attempt_recv_timeout_on_last_attempt(const CoreTestConfiguration& config);
 extern void retry_per_attempt_recv_timeout_on_last_attempt_pre_init(void);
-extern void retry_recv_initial_metadata(grpc_end2end_test_config config);
+extern void retry_recv_initial_metadata(const CoreTestConfiguration& config);
 extern void retry_recv_initial_metadata_pre_init(void);
-extern void retry_recv_message(grpc_end2end_test_config config);
+extern void retry_recv_message(const CoreTestConfiguration& config);
 extern void retry_recv_message_pre_init(void);
-extern void retry_recv_message_replay(grpc_end2end_test_config config);
+extern void retry_recv_message_replay(const CoreTestConfiguration& config);
 extern void retry_recv_message_replay_pre_init(void);
-extern void retry_recv_trailing_metadata_error(grpc_end2end_test_config config);
+extern void retry_recv_trailing_metadata_error(const CoreTestConfiguration& config);
 extern void retry_recv_trailing_metadata_error_pre_init(void);
-extern void retry_send_initial_metadata_refs(grpc_end2end_test_config config);
+extern void retry_send_initial_metadata_refs(const CoreTestConfiguration& config);
 extern void retry_send_initial_metadata_refs_pre_init(void);
-extern void retry_send_op_fails(grpc_end2end_test_config config);
+extern void retry_send_op_fails(const CoreTestConfiguration& config);
 extern void retry_send_op_fails_pre_init(void);
-extern void retry_send_recv_batch(grpc_end2end_test_config config);
+extern void retry_send_recv_batch(const CoreTestConfiguration& config);
 extern void retry_send_recv_batch_pre_init(void);
-extern void retry_server_pushback_delay(grpc_end2end_test_config config);
+extern void retry_server_pushback_delay(const CoreTestConfiguration& config);
 extern void retry_server_pushback_delay_pre_init(void);
-extern void retry_server_pushback_disabled(grpc_end2end_test_config config);
+extern void retry_server_pushback_disabled(const CoreTestConfiguration& config);
 extern void retry_server_pushback_disabled_pre_init(void);
-extern void retry_streaming(grpc_end2end_test_config config);
+extern void retry_streaming(const CoreTestConfiguration& config);
 extern void retry_streaming_pre_init(void);
-extern void retry_streaming_after_commit(grpc_end2end_test_config config);
+extern void retry_streaming_after_commit(const CoreTestConfiguration& config);
 extern void retry_streaming_after_commit_pre_init(void);
-extern void retry_streaming_succeeds_before_replay_finished(grpc_end2end_test_config config);
+extern void retry_streaming_succeeds_before_replay_finished(const CoreTestConfiguration& config);
 extern void retry_streaming_succeeds_before_replay_finished_pre_init(void);
-extern void retry_throttled(grpc_end2end_test_config config);
+extern void retry_throttled(const CoreTestConfiguration& config);
 extern void retry_throttled_pre_init(void);
-extern void retry_too_many_attempts(grpc_end2end_test_config config);
+extern void retry_too_many_attempts(const CoreTestConfiguration& config);
 extern void retry_too_many_attempts_pre_init(void);
-extern void retry_transparent_goaway(grpc_end2end_test_config config);
+extern void retry_transparent_goaway(const CoreTestConfiguration& config);
 extern void retry_transparent_goaway_pre_init(void);
-extern void retry_transparent_max_concurrent_streams(grpc_end2end_test_config config);
+extern void retry_transparent_max_concurrent_streams(const CoreTestConfiguration& config);
 extern void retry_transparent_max_concurrent_streams_pre_init(void);
-extern void retry_transparent_not_sent_on_wire(grpc_end2end_test_config config);
+extern void retry_transparent_not_sent_on_wire(const CoreTestConfiguration& config);
 extern void retry_transparent_not_sent_on_wire_pre_init(void);
-extern void retry_unref_before_finish(grpc_end2end_test_config config);
+extern void retry_unref_before_finish(const CoreTestConfiguration& config);
 extern void retry_unref_before_finish_pre_init(void);
-extern void retry_unref_before_recv(grpc_end2end_test_config config);
+extern void retry_unref_before_recv(const CoreTestConfiguration& config);
 extern void retry_unref_before_recv_pre_init(void);
-extern void server_finishes_request(grpc_end2end_test_config config);
+extern void server_finishes_request(const CoreTestConfiguration& config);
 extern void server_finishes_request_pre_init(void);
-extern void server_streaming(grpc_end2end_test_config config);
+extern void server_streaming(const CoreTestConfiguration& config);
 extern void server_streaming_pre_init(void);
-extern void shutdown_finishes_calls(grpc_end2end_test_config config);
+extern void shutdown_finishes_calls(const CoreTestConfiguration& config);
 extern void shutdown_finishes_calls_pre_init(void);
-extern void shutdown_finishes_tags(grpc_end2end_test_config config);
+extern void shutdown_finishes_tags(const CoreTestConfiguration& config);
 extern void shutdown_finishes_tags_pre_init(void);
-extern void simple_delayed_request(grpc_end2end_test_config config);
+extern void simple_delayed_request(const CoreTestConfiguration& config);
 extern void simple_delayed_request_pre_init(void);
-extern void simple_metadata(grpc_end2end_test_config config);
+extern void simple_metadata(const CoreTestConfiguration& config);
 extern void simple_metadata_pre_init(void);
-extern void simple_request(grpc_end2end_test_config config);
+extern void simple_request(const CoreTestConfiguration& config);
 extern void simple_request_pre_init(void);
-extern void streaming_error_response(grpc_end2end_test_config config);
+extern void streaming_error_response(const CoreTestConfiguration& config);
 extern void streaming_error_response_pre_init(void);
-extern void trailing_metadata(grpc_end2end_test_config config);
+extern void trailing_metadata(const CoreTestConfiguration& config);
 extern void trailing_metadata_pre_init(void);
-extern void write_buffering(grpc_end2end_test_config config);
+extern void write_buffering(const CoreTestConfiguration& config);
 extern void write_buffering_pre_init(void);
-extern void write_buffering_at_end(grpc_end2end_test_config config);
+extern void write_buffering_at_end(const CoreTestConfiguration& config);
 extern void write_buffering_at_end_pre_init(void);
 
 void grpc_end2end_tests_pre_init(void) {
@@ -319,7 +319,7 @@
 
 // NOLINTNEXTLINE(readability-function-size)
 void grpc_end2end_tests(int argc, char **argv,
-                        grpc_end2end_test_config config) {
+                    const CoreTestConfiguration& config) {
   int i;
 
   GPR_ASSERT(g_pre_init_called);
diff --git a/test/core/end2end/end2end_tests.h b/test/core/end2end/end2end_tests.h
index 388572c..d635fdd 100644
--- a/test/core/end2end/end2end_tests.h
+++ b/test/core/end2end/end2end_tests.h
@@ -21,11 +21,15 @@
 
 #include <stdint.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/slice.h>
+#include <grpc/support/log.h>
 
-typedef struct grpc_end2end_test_fixture grpc_end2end_test_fixture;
-typedef struct grpc_end2end_test_config grpc_end2end_test_config;
+#include "src/core/lib/channel/channel_args.h"
+#include "test/core/util/test_config.h"
 
 // Test feature flags.
 #define FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION 1
@@ -44,14 +48,76 @@
 
 #define FAIL_AUTH_CHECK_SERVER_ARG_NAME "fail_auth_check"
 
-struct grpc_end2end_test_fixture {
-  grpc_completion_queue* cq;
-  grpc_server* server;
-  grpc_channel* client;
-  void* fixture_data;
+class CoreTestFixture {
+ public:
+  virtual ~CoreTestFixture() {
+    ShutdownServer();
+    ShutdownClient();
+    grpc_completion_queue_shutdown(cq());
+    DrainCq();
+    grpc_completion_queue_destroy(cq());
+  }
+
+  grpc_completion_queue* cq() { return cq_; }
+  grpc_server* server() { return server_; }
+  grpc_channel* client() { return client_; }
+
+  void InitServer(const grpc_core::ChannelArgs& args) {
+    if (server_ != nullptr) ShutdownServer();
+    server_ = MakeServer(args);
+    GPR_ASSERT(server_ != nullptr);
+  }
+  void InitClient(const grpc_core::ChannelArgs& args) {
+    if (client_ != nullptr) ShutdownClient();
+    client_ = MakeClient(args);
+    GPR_ASSERT(client_ != nullptr);
+  }
+
+  void ShutdownServer() {
+    if (server_ == nullptr) return;
+    grpc_server_shutdown_and_notify(server_, cq_, server_);
+    grpc_event ev;
+    do {
+      ev = grpc_completion_queue_next(cq_, grpc_timeout_seconds_to_deadline(5),
+                                      nullptr);
+    } while (ev.type != GRPC_OP_COMPLETE || ev.tag != server_);
+    DestroyServer();
+  }
+
+  void DestroyServer() {
+    if (server_ == nullptr) return;
+    grpc_server_destroy(server_);
+    server_ = nullptr;
+  }
+
+  void ShutdownClient() {
+    if (client_ == nullptr) return;
+    grpc_channel_destroy(client_);
+    client_ = nullptr;
+  }
+
+ protected:
+  void SetServer(grpc_server* server);
+  void SetClient(grpc_channel* client);
+
+ private:
+  virtual grpc_server* MakeServer(const grpc_core::ChannelArgs& args) = 0;
+  virtual grpc_channel* MakeClient(const grpc_core::ChannelArgs& args) = 0;
+
+  void DrainCq() {
+    grpc_event ev;
+    do {
+      ev = grpc_completion_queue_next(cq_, grpc_timeout_seconds_to_deadline(5),
+                                      nullptr);
+    } while (ev.type != GRPC_QUEUE_SHUTDOWN);
+  }
+
+  grpc_completion_queue* cq_ = grpc_completion_queue_create_for_next(nullptr);
+  grpc_server* server_ = nullptr;
+  grpc_channel* client_ = nullptr;
 };
 
-struct grpc_end2end_test_config {
+struct CoreTestConfiguration {
   // A descriptive name for this test fixture.
   const char* name;
 
@@ -59,31 +125,28 @@
   uint32_t feature_mask;
 
   // If the call host is setup by the fixture (for example, via the
-  // GRPC_SSL_TARGET_NAME_OVERRIDE_ARG channel arg), which value should the test
-  // expect to find in call_details.host
+  // GRPC_SSL_TARGET_NAME_OVERRIDE_ARG channel arg), which value should the
+  // test expect to find in call_details.host
   const char* overridden_call_host;
 
-  grpc_end2end_test_fixture (*create_fixture)(
-      const grpc_channel_args* client_args,
-      const grpc_channel_args* server_args);
-  void (*init_client)(grpc_end2end_test_fixture* f,
-                      const grpc_channel_args* client_args);
-  void (*init_server)(grpc_end2end_test_fixture* f,
-                      const grpc_channel_args* server_args);
-  void (*tear_down_data)(grpc_end2end_test_fixture* f);
+  std::function<std::unique_ptr<CoreTestFixture>(
+      const grpc_core::ChannelArgs& client_args,
+      const grpc_core::ChannelArgs& server_args)>
+      create_fixture;
 };
 
 void grpc_end2end_tests_pre_init(void);
-void grpc_end2end_tests(int argc, char** argv, grpc_end2end_test_config config);
+void grpc_end2end_tests(int argc, char** argv,
+                        const CoreTestConfiguration& config);
 
 const char* get_host_override_string(const char* str,
-                                     grpc_end2end_test_config config);
+                                     const CoreTestConfiguration& config);
 // Returns a pointer to a statically allocated slice: future invocations
 // overwrite past invocations, not threadsafe, etc...
 const grpc_slice* get_host_override_slice(const char* str,
-                                          grpc_end2end_test_config config);
+                                          const CoreTestConfiguration& config);
 
 void validate_host_override_string(const char* pattern, grpc_slice str,
-                                   grpc_end2end_test_config config);
+                                   const CoreTestConfiguration& config);
 
 #endif  // GRPC_TEST_CORE_END2END_END2END_TESTS_H
diff --git a/test/core/end2end/fixtures/h2_census.cc b/test/core/end2end/fixtures/h2_census.cc
index 6bb97d4..fc8ec35 100644
--- a/test/core/end2end/fixtures/h2_census.cc
+++ b/test/core/end2end/fixtures/h2_census.cc
@@ -18,6 +18,8 @@
 
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include <grpc/grpc.h>
@@ -26,91 +28,46 @@
 
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/host_port.h"
-#include "src/core/lib/iomgr/exec_ctx.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_fixture_data {
-  std::string localaddr;
+class CensusFixture : public CoreTestFixture {
+ private:
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& args) override {
+    grpc_server_credentials* server_creds =
+        grpc_insecure_server_credentials_create();
+    auto* server = grpc_server_create(
+        args.Set(GRPC_ARG_ENABLE_CENSUS, true).ToC().get(), nullptr);
+    grpc_server_register_completion_queue(server, cq(), nullptr);
+    GPR_ASSERT(
+        grpc_server_add_http2_port(server, localaddr_.c_str(), server_creds));
+    grpc_server_credentials_release(server_creds);
+    grpc_server_start(server);
+    return server;
+  }
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& args) override {
+    auto* creds = grpc_insecure_credentials_create();
+    auto* client =
+        grpc_channel_create(localaddr_.c_str(), creds,
+                            args.Set(GRPC_ARG_ENABLE_CENSUS, true).ToC().get());
+    grpc_channel_credentials_release(creds);
+    return client;
+  }
+  const std::string localaddr_ =
+      grpc_core::JoinHostPort("localhost", grpc_pick_unused_port_or_die());
 };
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_fixture_data* ffd = new fullstack_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-static grpc_arg make_census_enable_arg(void) {
-  grpc_arg arg;
-  arg.type = GRPC_ARG_INTEGER;
-  arg.key = const_cast<char*>(GRPC_ARG_ENABLE_CENSUS);
-  arg.value.integer = 1;
-  return arg;
-}
-
-void chttp2_init_client_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* client_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_arg arg = make_census_enable_arg();
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  client_args = grpc_channel_args_copy_and_add(client_args, &arg, 1);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client);
-  {
-    grpc_core::ExecCtx exec_ctx;
-    grpc_channel_args_destroy(client_args);
-  }
-  grpc_channel_credentials_release(creds);
-}
-
-void chttp2_init_server_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* server_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_arg arg = make_census_enable_arg();
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  server_args = grpc_channel_args_copy_and_add(server_args, &arg, 1);
-  f->server = grpc_server_create(server_args, nullptr);
-  {
-    grpc_core::ExecCtx exec_ctx;
-    grpc_channel_args_destroy(server_args);
-  }
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack+census",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack, chttp2_init_client_fullstack,
-     chttp2_init_server_fullstack, chttp2_tear_down_fullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<CensusFixture>();
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_compress.cc b/test/core/end2end/fixtures/h2_compress.cc
index bd99930..8cf05e5 100644
--- a/test/core/end2end/fixtures/h2_compress.cc
+++ b/test/core/end2end/fixtures/h2_compress.cc
@@ -18,6 +18,8 @@
 
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include <grpc/compression.h>
@@ -27,86 +29,54 @@
 
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/host_port.h"
-#include "src/core/lib/iomgr/exec_ctx.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_compression_fixture_data {
-  std::string localaddr;
+class CompressionFixture : public CoreTestFixture {
+ private:
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& args) override {
+    auto* server = grpc_server_create(
+        args.SetIfUnset(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM,
+                        GRPC_COMPRESS_GZIP)
+            .ToC()
+            .get(),
+        nullptr);
+    grpc_server_register_completion_queue(server, cq(), nullptr);
+    grpc_server_credentials* server_creds =
+        grpc_insecure_server_credentials_create();
+    GPR_ASSERT(
+        grpc_server_add_http2_port(server, localaddr_.c_str(), server_creds));
+    grpc_server_credentials_release(server_creds);
+    grpc_server_start(server);
+    return server;
+  }
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& args) override {
+    grpc_channel_credentials* creds = grpc_insecure_credentials_create();
+    auto* client = grpc_channel_create(
+        localaddr_.c_str(), creds,
+        args.SetIfUnset(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM,
+                        GRPC_COMPRESS_GZIP)
+            .ToC()
+            .get());
+    grpc_channel_credentials_release(creds);
+    return client;
+  }
+
+  std::string localaddr_ =
+      grpc_core::JoinHostPort("localhost", grpc_pick_unused_port_or_die());
 };
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack_compression(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_compression_fixture_data* ffd =
-      new fullstack_compression_fixture_data();
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-
-  memset(&f, 0, sizeof(f));
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-void chttp2_init_client_fullstack_compression(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  fullstack_compression_fixture_data* ffd =
-      static_cast<fullstack_compression_fixture_data*>(f->fixture_data);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create(
-      ffd->localaddr.c_str(), creds,
-      grpc_core::ChannelArgs::FromC(client_args)
-          .SetIfUnset(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM,
-                      GRPC_COMPRESS_GZIP)
-          .ToC()
-          .get());
-  grpc_channel_credentials_release(creds);
-}
-
-void chttp2_init_server_fullstack_compression(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  fullstack_compression_fixture_data* ffd =
-      static_cast<fullstack_compression_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(
-      grpc_core::ChannelArgs::FromC(server_args)
-          .SetIfUnset(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM,
-                      GRPC_COMPRESS_GZIP)
-          .ToC()
-          .get(),
-      nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_fullstack_compression(grpc_end2end_test_fixture* f) {
-  grpc_core::ExecCtx exec_ctx;
-  fullstack_compression_fixture_data* ffd =
-      static_cast<fullstack_compression_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack_compression",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack_compression,
-     chttp2_init_client_fullstack_compression,
-     chttp2_init_server_fullstack_compression,
-     chttp2_tear_down_fullstack_compression},
+     nullptr,
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<CompressionFixture>();
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_fakesec.cc b/test/core/end2end/fixtures/h2_fakesec.cc
index d317710..5571fc8 100644
--- a/test/core/end2end/fixtures/h2_fakesec.cc
+++ b/test/core/end2end/fixtures/h2_fakesec.cc
@@ -18,38 +18,20 @@
 
 #include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/grpc_security.h>
 #include <grpc/status.h>
 #include <grpc/support/log.h>
 
-#include "src/core/lib/gprpp/host_port.h"
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/security/credentials/fake/fake_credentials.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_secure_fixture_data {
-  std::string localaddr;
-};
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
 static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
                                  const grpc_metadata* /*md*/,
                                  size_t /*md_count*/,
@@ -59,83 +41,37 @@
   cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
 }
 
-static void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-static void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
+class FakesecFixture : public SecureFixture {
+ private:
+  grpc_channel_credentials* MakeClientCreds(
+      const grpc_core::ChannelArgs&) override {
+    return grpc_fake_transport_security_credentials_create();
   }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
-static void chttp2_init_client_fake_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_channel_credentials* fake_ts_creds =
-      grpc_fake_transport_security_credentials_create();
-  chttp2_init_client_secure_fullstack(f, client_args, fake_ts_creds);
-}
-
-static int fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
+  grpc_server_credentials* MakeServerCreds(
+      const grpc_core::ChannelArgs& args) override {
+    grpc_server_credentials* fake_ts_creds =
+        grpc_fake_transport_security_server_credentials_create();
+    if (args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)) {
+      grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
+                                                nullptr};
+      grpc_server_credentials_set_auth_metadata_processor(fake_ts_creds,
+                                                          processor);
     }
+    return fake_ts_creds;
   }
-  return 0;
-}
-
-static void chttp2_init_server_fake_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_server_credentials* fake_ts_creds =
-      grpc_fake_transport_security_server_credentials_create();
-  if (fail_server_auth_check(server_args)) {
-    grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
-                                              nullptr};
-    grpc_server_credentials_set_auth_metadata_processor(fake_ts_creds,
-                                                        processor);
-  }
-  chttp2_init_server_secure_fullstack(f, server_args, fake_ts_creds);
-}
+};
 
 // All test configurations
 
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fake_secure_fullstack",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS_LEVEL_INSECURE,
-     nullptr, chttp2_create_fixture_secure_fullstack,
-     chttp2_init_client_fake_secure_fullstack,
-     chttp2_init_server_fake_secure_fullstack,
-     chttp2_tear_down_secure_fullstack},
-};
+     nullptr, [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<FakesecFixture>();
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
diff --git a/test/core/end2end/fixtures/h2_fd.cc b/test/core/end2end/fixtures/h2_fd.cc
index 1422d89..396d90b 100644
--- a/test/core/end2end/fixtures/h2_fd.cc
+++ b/test/core/end2end/fixtures/h2_fd.cc
@@ -16,8 +16,12 @@
 //
 //
 
+#include <functional>
+#include <memory>
+
 #include "absl/status/status.h"
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/iomgr/error.h"
 #include "src/core/lib/iomgr/port.h"
 
@@ -30,7 +34,6 @@
 #include <grpc/grpc.h>
 #include <grpc/grpc_posix.h>
 #include <grpc/grpc_security.h>
-#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 
 #include "src/core/lib/iomgr/exec_ctx.h"
@@ -39,10 +42,6 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-typedef struct {
-  int fd_pair[2];
-} sp_fixture_data;
-
 static void create_sockets(int sv[2]) {
   int flags;
   grpc_create_socketpair_if_unix(sv);
@@ -54,58 +53,39 @@
   GPR_ASSERT(grpc_set_socket_no_sigpipe_if_possible(sv[1]) == absl::OkStatus());
 }
 
-static grpc_end2end_test_fixture chttp2_create_fixture_socketpair(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  sp_fixture_data* fixture_data =
-      static_cast<sp_fixture_data*>(gpr_malloc(sizeof(*fixture_data)));
+class FdFixture : public CoreTestFixture {
+ public:
+  FdFixture() { create_sockets(fd_pair_); }
 
-  grpc_end2end_test_fixture f;
-  memset(&f, 0, sizeof(f));
-  f.fixture_data = fixture_data;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
+ private:
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& args) override {
+    grpc_core::ExecCtx exec_ctx;
+    auto* server = grpc_server_create(args.ToC().get(), nullptr);
+    grpc_server_register_completion_queue(server, cq(), nullptr);
+    grpc_server_start(server);
+    grpc_server_credentials* creds = grpc_insecure_server_credentials_create();
+    grpc_server_add_channel_from_fd(server, fd_pair_[1], creds);
+    grpc_server_credentials_release(creds);
+    return server;
+  }
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& args) override {
+    grpc_core::ExecCtx exec_ctx;
+    grpc_channel_credentials* creds = grpc_insecure_credentials_create();
+    auto* client = grpc_channel_create_from_fd("fixture_client", fd_pair_[0],
+                                               creds, args.ToC().get());
+    grpc_channel_credentials_release(creds);
+    return client;
+  }
 
-  create_sockets(fixture_data->fd_pair);
-
-  return f;
-}
-
-static void chttp2_init_client_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_core::ExecCtx exec_ctx;
-  sp_fixture_data* sfd = static_cast<sp_fixture_data*>(f->fixture_data);
-
-  GPR_ASSERT(!f->client);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create_from_fd("fixture_client", sfd->fd_pair[0],
-                                          creds, client_args);
-  grpc_channel_credentials_release(creds);
-  GPR_ASSERT(f->client);
-}
-
-static void chttp2_init_server_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_core::ExecCtx exec_ctx;
-  sp_fixture_data* sfd = static_cast<sp_fixture_data*>(f->fixture_data);
-  GPR_ASSERT(!f->server);
-  f->server = grpc_server_create(server_args, nullptr);
-  GPR_ASSERT(f->server);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_start(f->server);
-  grpc_server_credentials* creds = grpc_insecure_server_credentials_create();
-  grpc_server_add_channel_from_fd(f->server, sfd->fd_pair[1], creds);
-  grpc_server_credentials_release(creds);
-}
-
-static void chttp2_tear_down_socketpair(grpc_end2end_test_fixture* f) {
-  gpr_free(f->fixture_data);
-}
+  int fd_pair_[2];
+};
 
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fd", FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, nullptr,
-     chttp2_create_fixture_socketpair, chttp2_init_client_socketpair,
-     chttp2_init_server_socketpair, chttp2_tear_down_socketpair},
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<FdFixture>();
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_full+pipe.cc b/test/core/end2end/fixtures/h2_full+pipe.cc
index 3546a68..4a2e5ce 100644
--- a/test/core/end2end/fixtures/h2_full+pipe.cc
+++ b/test/core/end2end/fixtures/h2_full+pipe.cc
@@ -16,10 +16,12 @@
 //
 //
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/iomgr/port.h"
 
 // This test requires posix wakeup fds
@@ -27,76 +29,22 @@
 
 #include <string.h>
 
-#include <grpc/grpc_security.h>
-#include <grpc/support/log.h>
-
-#include "src/core/lib/gprpp/host_port.h"
 #include "src/core/lib/iomgr/wakeup_fd_posix.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_fixture_data {
-  std::string localaddr;
-};
-
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_fixture_data* ffd = new fullstack_fixture_data();
-  memset(&f, 0, sizeof(f));
-
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-void chttp2_init_client_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* client_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  grpc_channel_credentials_release(creds);
-  GPR_ASSERT(f->client);
-}
-
-void chttp2_init_server_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* server_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack, chttp2_init_client_fullstack,
-     chttp2_init_server_fullstack, chttp2_tear_down_fullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs& /*client_args*/,
+        const grpc_core::ChannelArgs& /*server_args*/) {
+       return std::make_unique<InsecureFixture>();
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_full+trace.cc b/test/core/end2end/fixtures/h2_full+trace.cc
index b55f2e8..e6c2d89 100644
--- a/test/core/end2end/fixtures/h2_full+trace.cc
+++ b/test/core/end2end/fixtures/h2_full+trace.cc
@@ -18,89 +18,37 @@
 
 #include <grpc/support/port_platform.h>
 
-#include <string.h>
-
 #include <grpc/grpc.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/iomgr/port.h"  // IWYU pragma: keep
 
 #ifdef GRPC_POSIX_SOCKET
 #include <unistd.h>
 #endif
 
-#include <string>
+#include <functional>
+#include <memory>
 
-#include <grpc/grpc_security.h>
 #include <grpc/support/log.h>
 
 #include "src/core/lib/debug/trace.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/host_port.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_fixture_data {
-  std::string localaddr;
-};
-
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_fixture_data* ffd = new fullstack_fixture_data();
-  memset(&f, 0, sizeof(f));
-
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-void chttp2_init_client_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* client_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  grpc_channel_credentials_release(creds);
-  GPR_ASSERT(f->client);
-}
-
-void chttp2_init_server_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* server_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack, chttp2_init_client_fullstack,
-     chttp2_init_server_fullstack, chttp2_tear_down_fullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs& /*client_args*/,
+        const grpc_core::ChannelArgs& /*server_args*/) {
+       return std::make_unique<InsecureFixture>();
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_full.cc b/test/core/end2end/fixtures/h2_full.cc
index b4f9507..8975364 100644
--- a/test/core/end2end/fixtures/h2_full.cc
+++ b/test/core/end2end/fixtures/h2_full.cc
@@ -18,78 +18,27 @@
 
 #include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
-#include <grpc/support/log.h>
 
-#include "src/core/lib/gprpp/host_port.h"
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_fixture_data {
-  std::string localaddr;
-};
-
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_fixture_data* ffd = new fullstack_fixture_data();
-  memset(&f, 0, sizeof(f));
-
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-void chttp2_init_client_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* client_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  grpc_channel_credentials_release(creds);
-  GPR_ASSERT(f->client);
-}
-
-void chttp2_init_server_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* server_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack, chttp2_init_client_fullstack,
-     chttp2_init_server_fullstack, chttp2_tear_down_fullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs& /*client_args*/,
+        const grpc_core::ChannelArgs& /*server_args*/) {
+       return std::make_unique<InsecureFixture>();
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_full_no_retry.cc b/test/core/end2end/fixtures/h2_full_no_retry.cc
index cb2dff3..e3e361e 100644
--- a/test/core/end2end/fixtures/h2_full_no_retry.cc
+++ b/test/core/end2end/fixtures/h2_full_no_retry.cc
@@ -18,83 +18,35 @@
 
 #include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
-#include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gprpp/host_port.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_fixture_data {
-  std::string localaddr;
+class NoRetryFixture : public InsecureFixture {
+ private:
+  grpc_core::ChannelArgs MutateClientArgs(
+      grpc_core::ChannelArgs args) override {
+    return args.Set(GRPC_ARG_ENABLE_RETRIES, false);
+  }
 };
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_fixture_data* ffd = new fullstack_fixture_data();
-  memset(&f, 0, sizeof(f));
-
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-void chttp2_init_client_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* client_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  grpc_arg no_retry = grpc_channel_arg_integer_create(
-      const_cast<char*>(GRPC_ARG_ENABLE_RETRIES), 0);
-  client_args = grpc_channel_args_copy_and_add(client_args, &no_retry, 1);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  grpc_channel_args_destroy(client_args);
-  grpc_channel_credentials_release(creds);
-  GPR_ASSERT(f->client);
-}
-
-void chttp2_init_server_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* server_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack, chttp2_init_client_fullstack,
-     chttp2_init_server_fullstack, chttp2_tear_down_fullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs& /*client_args*/,
+        const grpc_core::ChannelArgs& /*server_args*/) {
+       return std::make_unique<NoRetryFixture>();
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_http_proxy.cc b/test/core/end2end/fixtures/h2_http_proxy.cc
index c28f48f..ebd01a5 100644
--- a/test/core/end2end/fixtures/h2_http_proxy.cc
+++ b/test/core/end2end/fixtures/h2_http_proxy.cc
@@ -18,10 +18,13 @@
 
 #include <string.h>
 
+#include <functional>
 #include <initializer_list>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_format.h"
+#include "absl/types/optional.h"
 
 #include <grpc/grpc.h>
 #include <grpc/grpc_security.h>
@@ -34,88 +37,69 @@
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_fixture_data {
-  ~fullstack_fixture_data() { grpc_end2end_http_proxy_destroy(proxy); }
-  std::string server_addr;
-  grpc_end2end_http_proxy* proxy = nullptr;
+class HttpProxyFilter : public CoreTestFixture {
+ public:
+  explicit HttpProxyFilter(const grpc_core::ChannelArgs& client_args)
+      : proxy_(grpc_end2end_http_proxy_create(client_args.ToC().get())) {}
+  ~HttpProxyFilter() override {
+    // Need to shut down the proxy users before closing the proxy (otherwise we
+    // become stuck).
+    ShutdownClient();
+    ShutdownServer();
+    grpc_end2end_http_proxy_destroy(proxy_);
+  }
+
+ private:
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& args) override {
+    auto* server = grpc_server_create(args.ToC().get(), nullptr);
+    grpc_server_register_completion_queue(server, cq(), nullptr);
+    grpc_server_credentials* server_creds =
+        grpc_insecure_server_credentials_create();
+    GPR_ASSERT(
+        grpc_server_add_http2_port(server, server_addr_.c_str(), server_creds));
+    grpc_server_credentials_release(server_creds);
+    grpc_server_start(server);
+    return server;
+  }
+
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& args) override {
+    // If testing for proxy auth, add credentials to proxy uri
+    absl::optional<std::string> proxy_auth_str =
+        args.GetOwnedString(GRPC_ARG_HTTP_PROXY_AUTH_CREDS);
+    std::string proxy_uri;
+    if (!proxy_auth_str.has_value()) {
+      proxy_uri = absl::StrFormat(
+          "http://%s", grpc_end2end_http_proxy_get_proxy_name(proxy_));
+    } else {
+      proxy_uri =
+          absl::StrFormat("http://%s@%s", proxy_auth_str->c_str(),
+                          grpc_end2end_http_proxy_get_proxy_name(proxy_));
+    }
+    grpc_channel_credentials* creds = grpc_insecure_credentials_create();
+    auto* client = grpc_channel_create(
+        server_addr_.c_str(), creds,
+        args.Set(GRPC_ARG_HTTP_PROXY, proxy_uri).ToC().get());
+    grpc_channel_credentials_release(creds);
+    GPR_ASSERT(client);
+    return client;
+  }
+
+  std::string server_addr_ =
+      grpc_core::JoinHostPort("localhost", grpc_pick_unused_port_or_die());
+  grpc_end2end_http_proxy* proxy_;
 };
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack(
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  memset(&f, 0, sizeof(f));
-  fullstack_fixture_data* ffd = new fullstack_fixture_data();
-  const int server_port = grpc_pick_unused_port_or_die();
-  ffd->server_addr = grpc_core::JoinHostPort("localhost", server_port);
-
-  // Passing client_args to proxy_create for the case of checking for proxy auth
-  //
-  ffd->proxy = grpc_end2end_http_proxy_create(client_args);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-void chttp2_init_client_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* client_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  // If testing for proxy auth, add credentials to proxy uri
-  const char* proxy_auth_str = grpc_channel_args_find_string(
-      client_args, GRPC_ARG_HTTP_PROXY_AUTH_CREDS);
-  std::string proxy_uri;
-  if (proxy_auth_str == nullptr) {
-    proxy_uri = absl::StrFormat(
-        "http://%s", grpc_end2end_http_proxy_get_proxy_name(ffd->proxy));
-  } else {
-    proxy_uri =
-        absl::StrFormat("http://%s@%s", proxy_auth_str,
-                        grpc_end2end_http_proxy_get_proxy_name(ffd->proxy));
-  }
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create(ffd->server_addr.c_str(), creds,
-                                  grpc_core::ChannelArgs::FromC(client_args)
-                                      .Set(GRPC_ARG_HTTP_PROXY, proxy_uri)
-                                      .ToC()
-                                      .get());
-  grpc_channel_credentials_release(creds);
-  GPR_ASSERT(f->client);
-}
-
-void chttp2_init_server_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* server_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->server_addr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack, chttp2_init_client_fullstack,
-     chttp2_init_server_fullstack, chttp2_tear_down_fullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs& client_args,
+        const grpc_core::ChannelArgs&) {
+       return std::make_unique<HttpProxyFilter>(client_args);
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_insecure.cc b/test/core/end2end/fixtures/h2_insecure.cc
index 936caf7..0250683 100644
--- a/test/core/end2end/fixtures/h2_insecure.cc
+++ b/test/core/end2end/fixtures/h2_insecure.cc
@@ -18,7 +18,8 @@
 
 #include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/grpc_security.h>
@@ -26,42 +27,12 @@
 #include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gprpp/host_port.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/test_config.h"
 
 namespace {
 
-struct Chttp2InsecureFullstackFixtureData {
-  std::string localaddr;
-};
-
-grpc_end2end_test_fixture Chttp2CreateFixtureInsecureFullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  Chttp2InsecureFullstackFixtureData* ffd =
-      new Chttp2InsecureFullstackFixtureData();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-void Chttp2InitClientInsecureFullstack(grpc_end2end_test_fixture* f,
-                                       const grpc_channel_args* client_args) {
-  Chttp2InsecureFullstackFixtureData* ffd =
-      static_cast<Chttp2InsecureFullstackFixtureData*>(f->fixture_data);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  grpc_channel_credentials_release(creds);
-  GPR_ASSERT(f->client);
-}
-
 void ProcessAuthFailure(void* state, grpc_auth_context* /*ctx*/,
                         const grpc_metadata* /*md*/, size_t /*md_count*/,
                         grpc_process_auth_metadata_done_cb cb,
@@ -70,46 +41,31 @@
   cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
 }
 
-void Chttp2InitServerInsecureFullstack(grpc_end2end_test_fixture* f,
-                                       const grpc_channel_args* server_args) {
-  Chttp2InsecureFullstackFixtureData* ffd =
-      static_cast<Chttp2InsecureFullstackFixtureData*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
+class InsecureCredsFixture : public InsecureFixture {
+ private:
+  grpc_server_credentials* MakeServerCreds(
+      const grpc_core::ChannelArgs& args) override {
+    auto* creds = grpc_insecure_server_credentials_create();
+    if (args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)) {
+      grpc_auth_metadata_processor processor = {ProcessAuthFailure, nullptr,
+                                                nullptr};
+      grpc_server_credentials_set_auth_metadata_processor(creds, processor);
+    }
+    return creds;
   }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  if (grpc_channel_args_find(server_args, FAIL_AUTH_CHECK_SERVER_ARG_NAME) !=
-      nullptr) {
-    grpc_auth_metadata_processor processor = {ProcessAuthFailure, nullptr,
-                                              nullptr};
-    grpc_server_credentials_set_auth_metadata_processor(server_creds,
-                                                        processor);
-  }
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void Chttp2TearDownInsecureFullstack(grpc_end2end_test_fixture* f) {
-  Chttp2InsecureFullstackFixtureData* ffd =
-      static_cast<Chttp2InsecureFullstackFixtureData*>(f->fixture_data);
-  delete ffd;
-}
+};
 
 // All test configurations
-grpc_end2end_test_config configs[] = {
+CoreTestConfiguration configs[] = {
     {"chttp2/insecure_fullstack",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS_LEVEL_INSECURE,
-     nullptr, Chttp2CreateFixtureInsecureFullstack,
-     Chttp2InitClientInsecureFullstack, Chttp2InitServerInsecureFullstack,
-     Chttp2TearDownInsecureFullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<InsecureCredsFixture>();
+     }},
 };
 
 }  // namespace
diff --git a/test/core/end2end/fixtures/h2_local_abstract_uds_percent_encoded.cc b/test/core/end2end/fixtures/h2_local_abstract_uds_percent_encoded.cc
index c651fe9..ee760f6 100644
--- a/test/core/end2end/fixtures/h2_local_abstract_uds_percent_encoded.cc
+++ b/test/core/end2end/fixtures/h2_local_abstract_uds_percent_encoded.cc
@@ -16,8 +16,9 @@
 #include <unistd.h>
 
 #include <atomic>
+#include <functional>
 #include <initializer_list>
-#include <string>
+#include <memory>
 
 #include "absl/strings/str_format.h"
 
@@ -25,45 +26,31 @@
 #include <grpc/grpc_security_constants.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/local_util.h"
 #include "test/core/util/test_config.h"
 
 static std::atomic<int> unique{0};
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack_uds(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f =
-      grpc_end2end_local_chttp2_create_fixture_fullstack();
-  gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC);
-  static_cast<grpc_end2end_local_fullstack_fixture_data*>(f.fixture_data)
-      ->localaddr = absl::StrFormat(
-      "unix-abstract:grpc_fullstack_test.%%00.%d.%" PRId64 ".%" PRId32 ".%d",
-      getpid(), now.tv_sec, now.tv_nsec, ++unique);
-  return f;
-}
-
-static void chttp2_init_client_fullstack_uds(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_client_fullstack(f, client_args, UDS);
-}
-
-static void chttp2_init_server_fullstack_uds(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_server_fullstack(f, client_args, UDS);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack_local_abstract_uds_percent_encoded",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS,
-     nullptr, chttp2_create_fixture_fullstack_uds,
-     chttp2_init_client_fullstack_uds, chttp2_init_server_fullstack_uds,
-     grpc_end2end_local_chttp2_tear_down_fullstack}};
+     nullptr,
+     [](const grpc_core::ChannelArgs& /*client_args*/,
+        const grpc_core::ChannelArgs& /*server_args*/) {
+       gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC);
+       return std::make_unique<LocalTestFixture>(
+           absl::StrFormat("unix-abstract:grpc_fullstack_test.%%00.%d.%" PRId64
+                           ".%" PRId32 ".%d",
+                           getpid(), now.tv_sec, now.tv_nsec,
+                           unique.fetch_add(1, std::memory_order_relaxed)),
+           UDS);
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
diff --git a/test/core/end2end/fixtures/h2_local_ipv4.cc b/test/core/end2end/fixtures/h2_local_ipv4.cc
index f7ca63d..2dcb1dd 100644
--- a/test/core/end2end/fixtures/h2_local_ipv4.cc
+++ b/test/core/end2end/fixtures/h2_local_ipv4.cc
@@ -18,48 +18,33 @@
 
 #include <unistd.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/grpc_security_constants.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/host_port.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/local_util.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack_ipv4(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f =
-      grpc_end2end_local_chttp2_create_fixture_fullstack();
-  int port = grpc_pick_unused_port_or_die();
-  static_cast<grpc_end2end_local_fullstack_fixture_data*>(f.fixture_data)
-      ->localaddr = grpc_core::JoinHostPort("127.0.0.1", port);
-  return f;
-}
-
-static void chttp2_init_client_fullstack_ipv4(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_client_fullstack(f, client_args, LOCAL_TCP);
-}
-
-static void chttp2_init_server_fullstack_ipv4(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_server_fullstack(f, client_args, LOCAL_TCP);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack_local_ipv4",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS,
-     nullptr, chttp2_create_fixture_fullstack_ipv4,
-     chttp2_init_client_fullstack_ipv4, chttp2_init_server_fullstack_ipv4,
-     grpc_end2end_local_chttp2_tear_down_fullstack}};
+     nullptr,
+     [](const grpc_core::ChannelArgs& /*client_args*/,
+        const grpc_core::ChannelArgs& /*server_args*/) {
+       int port = grpc_pick_unused_port_or_die();
+       return std::make_unique<LocalTestFixture>(
+           grpc_core::JoinHostPort("127.0.0.1", port), LOCAL_TCP);
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
diff --git a/test/core/end2end/fixtures/h2_local_ipv6.cc b/test/core/end2end/fixtures/h2_local_ipv6.cc
index f234d46..3001234 100644
--- a/test/core/end2end/fixtures/h2_local_ipv6.cc
+++ b/test/core/end2end/fixtures/h2_local_ipv6.cc
@@ -18,48 +18,33 @@
 
 #include <unistd.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/grpc_security_constants.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/host_port.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/local_util.h"
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack_ipv6(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f =
-      grpc_end2end_local_chttp2_create_fixture_fullstack();
-  int port = grpc_pick_unused_port_or_die();
-  static_cast<grpc_end2end_local_fullstack_fixture_data*>(f.fixture_data)
-      ->localaddr = grpc_core::JoinHostPort("[::1]", port);
-  return f;
-}
-
-static void chttp2_init_client_fullstack_ipv6(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_client_fullstack(f, client_args, LOCAL_TCP);
-}
-
-static void chttp2_init_server_fullstack_ipv6(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_server_fullstack(f, client_args, LOCAL_TCP);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack_local_ipv6",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS,
-     nullptr, chttp2_create_fixture_fullstack_ipv6,
-     chttp2_init_client_fullstack_ipv6, chttp2_init_server_fullstack_ipv6,
-     grpc_end2end_local_chttp2_tear_down_fullstack}};
+     nullptr,
+     [](const grpc_core::ChannelArgs& /*client_args*/,
+        const grpc_core::ChannelArgs& /*server_args*/) {
+       int port = grpc_pick_unused_port_or_die();
+       return std::make_unique<LocalTestFixture>(
+           grpc_core::JoinHostPort("[::1]", port), LOCAL_TCP);
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
diff --git a/test/core/end2end/fixtures/h2_local_uds.cc b/test/core/end2end/fixtures/h2_local_uds.cc
index ceb72ea..0ecc1ac 100644
--- a/test/core/end2end/fixtures/h2_local_uds.cc
+++ b/test/core/end2end/fixtures/h2_local_uds.cc
@@ -20,8 +20,9 @@
 #include <unistd.h>
 
 #include <atomic>
+#include <functional>
 #include <initializer_list>
-#include <string>
+#include <memory>
 
 #include "absl/strings/str_format.h"
 
@@ -29,45 +30,31 @@
 #include <grpc/grpc_security_constants.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/local_util.h"
 #include "test/core/util/test_config.h"
 
 static std::atomic<int> unique{1};
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack_uds(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f =
-      grpc_end2end_local_chttp2_create_fixture_fullstack();
-  gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
-  static_cast<grpc_end2end_local_fullstack_fixture_data*>(f.fixture_data)
-      ->localaddr = absl::StrFormat(
-      "unix:/tmp/grpc_fullstack_test.%d.%" PRId64 ".%" PRId32 ".%d", getpid(),
-      now.tv_sec, now.tv_nsec, unique++);
-  return f;
-}
-
-static void chttp2_init_client_fullstack_uds(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_client_fullstack(f, client_args, UDS);
-}
-
-static void chttp2_init_server_fullstack_uds(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_server_fullstack(f, client_args, UDS);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack_local_uds",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS,
-     nullptr, chttp2_create_fixture_fullstack_uds,
-     chttp2_init_client_fullstack_uds, chttp2_init_server_fullstack_uds,
-     grpc_end2end_local_chttp2_tear_down_fullstack}};
+     nullptr,
+     [](const grpc_core::ChannelArgs& /*client_args*/,
+        const grpc_core::ChannelArgs& /*server_args*/) {
+       gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
+       return std::make_unique<LocalTestFixture>(
+           absl::StrFormat("unix:/tmp/grpc_fullstack_test.%d.%" PRId64
+                           ".%" PRId32 ".%d",
+                           getpid(), now.tv_sec, now.tv_nsec,
+                           unique.fetch_add(1, std::memory_order_relaxed)),
+           UDS);
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
diff --git a/test/core/end2end/fixtures/h2_local_uds_percent_encoded.cc b/test/core/end2end/fixtures/h2_local_uds_percent_encoded.cc
index c8471ca..cd44807 100644
--- a/test/core/end2end/fixtures/h2_local_uds_percent_encoded.cc
+++ b/test/core/end2end/fixtures/h2_local_uds_percent_encoded.cc
@@ -20,8 +20,9 @@
 #include <unistd.h>
 
 #include <atomic>
+#include <functional>
 #include <initializer_list>
-#include <string>
+#include <memory>
 
 #include "absl/strings/str_format.h"
 
@@ -29,45 +30,31 @@
 #include <grpc/grpc_security_constants.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/local_util.h"
 #include "test/core/util/test_config.h"
 
 static std::atomic<int> unique{1};
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack_uds(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f =
-      grpc_end2end_local_chttp2_create_fixture_fullstack();
-  gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC);
-  static_cast<grpc_end2end_local_fullstack_fixture_data*>(f.fixture_data)
-      ->localaddr = absl::StrFormat(
-      "unix:/tmp/grpc_fullstack_test.%%25.%d.%" PRId64 ".%" PRId32 ".%d",
-      getpid(), now.tv_sec, now.tv_nsec, unique++);
-  return f;
-}
-
-static void chttp2_init_client_fullstack_uds(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_client_fullstack(f, client_args, UDS);
-}
-
-static void chttp2_init_server_fullstack_uds(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_end2end_local_chttp2_init_server_fullstack(f, client_args, UDS);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack_local_uds_percent_encoded",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS,
-     nullptr, chttp2_create_fixture_fullstack_uds,
-     chttp2_init_client_fullstack_uds, chttp2_init_server_fullstack_uds,
-     grpc_end2end_local_chttp2_tear_down_fullstack}};
+     nullptr,
+     [](const grpc_core::ChannelArgs& /*client_args*/,
+        const grpc_core::ChannelArgs& /*server_args*/) {
+       gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC);
+       return std::make_unique<LocalTestFixture>(
+           absl::StrFormat("unix:/tmp/grpc_fullstack_test.%%25.%d.%" PRId64
+                           ".%" PRId32 ".%d",
+                           getpid(), now.tv_sec, now.tv_nsec,
+                           unique.fetch_add(1, std::memory_order_relaxed)),
+           UDS);
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
diff --git a/test/core/end2end/fixtures/h2_oauth2_common.h b/test/core/end2end/fixtures/h2_oauth2_common.h
new file mode 100644
index 0000000..ae9103d
--- /dev/null
+++ b/test/core/end2end/fixtures/h2_oauth2_common.h
@@ -0,0 +1,181 @@
+// Copyright 2023 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GRPC_TEST_CORE_END2END_FIXTURES_H2_OAUTH2_COMMON_H
+#define GRPC_TEST_CORE_END2END_FIXTURES_H2_OAUTH2_COMMON_H
+
+#include <string.h>
+
+#include <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+#include <grpc/grpc_security_constants.h>
+#include <grpc/slice.h>
+#include <grpc/status.h>
+#include <grpc/support/log.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/load_file.h"
+#include "src/core/lib/security/credentials/credentials.h"
+#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
+#include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
+
+class Oauth2Fixture : public SecureFixture {
+ public:
+  explicit Oauth2Fixture(grpc_tls_version tls_version)
+      : tls_version_(tls_version) {}
+
+  static const char* CaCertPath() { return "src/core/tsi/test_creds/ca.pem"; }
+  static const char* ServerCertPath() {
+    return "src/core/tsi/test_creds/server1.pem";
+  }
+  static const char* ServerKeyPath() {
+    return "src/core/tsi/test_creds/server1.key";
+  }
+
+ private:
+  struct TestProcessorState {};
+
+  static const char* oauth2_md() { return "Bearer aaslkfjs424535asdf"; }
+  static const char* client_identity_property_name() { return "smurf_name"; }
+  static const char* client_identity() { return "Brainy Smurf"; }
+
+  static const grpc_metadata* find_metadata(const grpc_metadata* md,
+                                            size_t md_count, const char* key,
+                                            const char* value) {
+    size_t i;
+    for (i = 0; i < md_count; i++) {
+      if (grpc_slice_str_cmp(md[i].key, key) == 0 &&
+          grpc_slice_str_cmp(md[i].value, value) == 0) {
+        return &md[i];
+      }
+    }
+    return nullptr;
+  }
+
+  static void process_oauth2_success(void*, grpc_auth_context* ctx,
+                                     const grpc_metadata* md, size_t md_count,
+                                     grpc_process_auth_metadata_done_cb cb,
+                                     void* user_data) {
+    const grpc_metadata* oauth2 =
+        find_metadata(md, md_count, "authorization", oauth2_md());
+    GPR_ASSERT(oauth2 != nullptr);
+    grpc_auth_context_add_cstring_property(ctx, client_identity_property_name(),
+                                           client_identity());
+    GPR_ASSERT(grpc_auth_context_set_peer_identity_property_name(
+                   ctx, client_identity_property_name()) == 1);
+    cb(user_data, oauth2, 1, nullptr, 0, GRPC_STATUS_OK, nullptr);
+  }
+
+  static void process_oauth2_failure(void* state, grpc_auth_context* /*ctx*/,
+                                     const grpc_metadata* md, size_t md_count,
+                                     grpc_process_auth_metadata_done_cb cb,
+                                     void* user_data) {
+    const grpc_metadata* oauth2 =
+        find_metadata(md, md_count, "authorization", oauth2_md());
+    GPR_ASSERT(state != nullptr);
+    GPR_ASSERT(oauth2 != nullptr);
+    cb(user_data, oauth2, 1, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
+  }
+
+  static grpc_auth_metadata_processor test_processor_create(bool failing) {
+    auto* s = new TestProcessorState;
+    grpc_auth_metadata_processor result;
+    result.state = s;
+    result.destroy = [](void* p) {
+      delete static_cast<TestProcessorState*>(p);
+    };
+    if (failing) {
+      result.process = process_oauth2_failure;
+    } else {
+      result.process = process_oauth2_success;
+    }
+    return result;
+  }
+
+  grpc_core::ChannelArgs MutateClientArgs(
+      grpc_core::ChannelArgs args) override {
+    return args.Set(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, "foo.test.google.fr");
+  }
+
+  grpc_channel_credentials* MakeClientCreds(
+      const grpc_core::ChannelArgs&) override {
+    grpc_slice ca_slice;
+    GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
+                                 grpc_load_file(CaCertPath(), 1, &ca_slice)));
+    const char* test_root_cert =
+        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(ca_slice);
+    grpc_channel_credentials* ssl_creds =
+        grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr, nullptr);
+    if (ssl_creds != nullptr) {
+      // Set the min and max TLS version.
+      grpc_ssl_credentials* creds =
+          reinterpret_cast<grpc_ssl_credentials*>(ssl_creds);
+      creds->set_min_tls_version(tls_version_);
+      creds->set_max_tls_version(tls_version_);
+    }
+    grpc_call_credentials* oauth2_creds =
+        grpc_md_only_test_credentials_create("authorization", oauth2_md());
+    grpc_channel_credentials* ssl_oauth2_creds =
+        grpc_composite_channel_credentials_create(ssl_creds, oauth2_creds,
+                                                  nullptr);
+    grpc_channel_credentials_release(ssl_creds);
+    grpc_call_credentials_release(oauth2_creds);
+    grpc_slice_unref(ca_slice);
+    return ssl_oauth2_creds;
+  }
+
+  grpc_server_credentials* MakeServerCreds(
+      const grpc_core::ChannelArgs& args) override {
+    grpc_slice cert_slice, key_slice;
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "load_file", grpc_load_file(ServerCertPath(), 1, &cert_slice)));
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "load_file", grpc_load_file(ServerKeyPath(), 1, &key_slice)));
+    const char* server_cert =
+        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
+    const char* server_key =
+        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
+    grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
+    grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
+        nullptr, &pem_key_cert_pair, 1, 0, nullptr);
+    if (ssl_creds != nullptr) {
+      // Set the min and max TLS version.
+      grpc_ssl_server_credentials* creds =
+          reinterpret_cast<grpc_ssl_server_credentials*>(ssl_creds);
+      creds->set_min_tls_version(tls_version_);
+      creds->set_max_tls_version(tls_version_);
+    }
+    grpc_server_credentials_set_auth_metadata_processor(
+        ssl_creds,
+        test_processor_create(args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)));
+    grpc_slice_unref(cert_slice);
+    grpc_slice_unref(key_slice);
+    return ssl_creds;
+  }
+
+  static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
+                                   const grpc_metadata* /*md*/,
+                                   size_t /*md_count*/,
+                                   grpc_process_auth_metadata_done_cb cb,
+                                   void* user_data) {
+    GPR_ASSERT(state == nullptr);
+    cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
+  }
+
+  grpc_tls_version tls_version_;
+};
+
+#endif  // GRPC_TEST_CORE_END2END_FIXTURES_H2_OAUTH2_COMMON_H
diff --git a/test/core/end2end/fixtures/h2_oauth2_tls12.cc b/test/core/end2end/fixtures/h2_oauth2_tls12.cc
index 5d3681c..0a0c747 100644
--- a/test/core/end2end/fixtures/h2_oauth2_tls12.cc
+++ b/test/core/end2end/fixtures/h2_oauth2_tls12.cc
@@ -17,258 +17,30 @@
 //
 
 #include <stdio.h>
-#include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
 #include <grpc/grpc_security_constants.h>
-#include <grpc/slice.h>
-#include <grpc/status.h>
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gprpp/host_port.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/exec_ctx.h"
-#include "src/core/lib/iomgr/load_file.h"
-#include "src/core/lib/security/credentials/credentials.h"
-#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/h2_oauth2_common.h"
 #include "test/core/util/test_config.h"
 
-#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem"
-#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem"
-#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key"
-
-static const char oauth2_md[] = "Bearer aaslkfjs424535asdf";
-static const char* client_identity_property_name = "smurf_name";
-static const char* client_identity = "Brainy Smurf";
-
-struct fullstack_secure_fixture_data {
-  std::string localaddr;
-  grpc_tls_version tls_version;
-};
-
-static const grpc_metadata* find_metadata(const grpc_metadata* md,
-                                          size_t md_count, const char* key,
-                                          const char* value) {
-  size_t i;
-  for (i = 0; i < md_count; i++) {
-    if (grpc_slice_str_cmp(md[i].key, key) == 0 &&
-        grpc_slice_str_cmp(md[i].value, value) == 0) {
-      return &md[i];
-    }
-  }
-  return nullptr;
-}
-
-typedef struct {
-  size_t pseudo_refcount;
-} test_processor_state;
-
-static void process_oauth2_success(void* state, grpc_auth_context* ctx,
-                                   const grpc_metadata* md, size_t md_count,
-                                   grpc_process_auth_metadata_done_cb cb,
-                                   void* user_data) {
-  const grpc_metadata* oauth2 =
-      find_metadata(md, md_count, "authorization", oauth2_md);
-  test_processor_state* s;
-
-  GPR_ASSERT(state != nullptr);
-  s = static_cast<test_processor_state*>(state);
-  GPR_ASSERT(s->pseudo_refcount == 1);
-  GPR_ASSERT(oauth2 != nullptr);
-  grpc_auth_context_add_cstring_property(ctx, client_identity_property_name,
-                                         client_identity);
-  GPR_ASSERT(grpc_auth_context_set_peer_identity_property_name(
-                 ctx, client_identity_property_name) == 1);
-  cb(user_data, oauth2, 1, nullptr, 0, GRPC_STATUS_OK, nullptr);
-}
-
-static void process_oauth2_failure(void* state, grpc_auth_context* /*ctx*/,
-                                   const grpc_metadata* md, size_t md_count,
-                                   grpc_process_auth_metadata_done_cb cb,
-                                   void* user_data) {
-  const grpc_metadata* oauth2 =
-      find_metadata(md, md_count, "authorization", oauth2_md);
-  test_processor_state* s;
-  GPR_ASSERT(state != nullptr);
-  s = static_cast<test_processor_state*>(state);
-  GPR_ASSERT(s->pseudo_refcount == 1);
-  GPR_ASSERT(oauth2 != nullptr);
-  cb(user_data, oauth2, 1, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/, grpc_tls_version tls_version) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  ffd->tls_version = tls_version;
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  return f;
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_2(
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* server_args) {
-  return chttp2_create_fixture_secure_fullstack(client_args, server_args,
-                                                grpc_tls_version::TLS1_2);
-}
-
-static void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-static void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
-static void chttp2_init_client_simple_ssl_with_oauth2_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_core::ExecCtx exec_ctx;
-  grpc_slice ca_slice;
-  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                               grpc_load_file(CA_CERT_PATH, 1, &ca_slice)));
-  const char* test_root_cert =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(ca_slice);
-  grpc_channel_credentials* ssl_creds =
-      grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_credentials* creds =
-        reinterpret_cast<grpc_ssl_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_call_credentials* oauth2_creds =
-      grpc_md_only_test_credentials_create("authorization", oauth2_md);
-  grpc_channel_credentials* ssl_oauth2_creds =
-      grpc_composite_channel_credentials_create(ssl_creds, oauth2_creds,
-                                                nullptr);
-  grpc_arg ssl_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr")}};
-  const grpc_channel_args* new_client_args =
-      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
-  chttp2_init_client_secure_fullstack(f, new_client_args, ssl_oauth2_creds);
-  grpc_channel_args_destroy(new_client_args);
-  grpc_channel_credentials_release(ssl_creds);
-  grpc_call_credentials_release(oauth2_creds);
-  grpc_slice_unref(ca_slice);
-}
-
-static int fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-static void processor_destroy(void* state) {
-  test_processor_state* s = static_cast<test_processor_state*>(state);
-  GPR_ASSERT((s->pseudo_refcount--) == 1);
-  gpr_free(s);
-}
-
-static grpc_auth_metadata_processor test_processor_create(int failing) {
-  test_processor_state* s =
-      static_cast<test_processor_state*>(gpr_malloc(sizeof(*s)));
-  grpc_auth_metadata_processor result;
-  s->pseudo_refcount = 1;
-  result.state = s;
-  result.destroy = processor_destroy;
-  if (failing) {
-    result.process = process_oauth2_failure;
-  } else {
-    result.process = process_oauth2_success;
-  }
-  return result;
-}
-
-static void chttp2_init_server_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_slice cert_slice, key_slice;
-  GPR_ASSERT(GRPC_LOG_IF_ERROR(
-      "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
-  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                               grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
-  const char* server_cert =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
-  const char* server_key =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
-  grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
-  grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
-      nullptr, &pem_key_cert_pair, 1, 0, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_server_credentials* creds =
-        reinterpret_cast<grpc_ssl_server_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_server_credentials_set_auth_metadata_processor(
-      ssl_creds, test_processor_create(fail_server_auth_check(server_args)));
-  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
-  grpc_slice_unref(cert_slice);
-  grpc_slice_unref(key_slice);
-}
-
 // All test configurations
 
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/simple_ssl_with_oauth2_fullstack_tls1_2",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_2,
-     chttp2_init_client_simple_ssl_with_oauth2_secure_fullstack,
-     chttp2_init_server_simple_ssl_secure_fullstack,
-     chttp2_tear_down_secure_fullstack},
-};
+     "foo.test.google.fr",
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<Oauth2Fixture>(grpc_tls_version::TLS1_2);
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
diff --git a/test/core/end2end/fixtures/h2_oauth2_tls13.cc b/test/core/end2end/fixtures/h2_oauth2_tls13.cc
index df8b330..72559dd 100644
--- a/test/core/end2end/fixtures/h2_oauth2_tls13.cc
+++ b/test/core/end2end/fixtures/h2_oauth2_tls13.cc
@@ -17,259 +17,31 @@
 //
 
 #include <stdio.h>
-#include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
 #include <grpc/grpc_security_constants.h>
-#include <grpc/slice.h>
-#include <grpc/status.h>
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gprpp/host_port.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/exec_ctx.h"
-#include "src/core/lib/iomgr/load_file.h"
-#include "src/core/lib/security/credentials/credentials.h"
-#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/h2_oauth2_common.h"
 #include "test/core/util/test_config.h"
 
-#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem"
-#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem"
-#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key"
-
-static const char oauth2_md[] = "Bearer aaslkfjs424535asdf";
-static const char* client_identity_property_name = "smurf_name";
-static const char* client_identity = "Brainy Smurf";
-
-struct fullstack_secure_fixture_data {
-  std::string localaddr;
-  grpc_tls_version tls_version;
-};
-
-static const grpc_metadata* find_metadata(const grpc_metadata* md,
-                                          size_t md_count, const char* key,
-                                          const char* value) {
-  size_t i;
-  for (i = 0; i < md_count; i++) {
-    if (grpc_slice_str_cmp(md[i].key, key) == 0 &&
-        grpc_slice_str_cmp(md[i].value, value) == 0) {
-      return &md[i];
-    }
-  }
-  return nullptr;
-}
-
-typedef struct {
-  size_t pseudo_refcount;
-} test_processor_state;
-
-static void process_oauth2_success(void* state, grpc_auth_context* ctx,
-                                   const grpc_metadata* md, size_t md_count,
-                                   grpc_process_auth_metadata_done_cb cb,
-                                   void* user_data) {
-  const grpc_metadata* oauth2 =
-      find_metadata(md, md_count, "authorization", oauth2_md);
-  test_processor_state* s;
-
-  GPR_ASSERT(state != nullptr);
-  s = static_cast<test_processor_state*>(state);
-  GPR_ASSERT(s->pseudo_refcount == 1);
-  GPR_ASSERT(oauth2 != nullptr);
-  grpc_auth_context_add_cstring_property(ctx, client_identity_property_name,
-                                         client_identity);
-  GPR_ASSERT(grpc_auth_context_set_peer_identity_property_name(
-                 ctx, client_identity_property_name) == 1);
-  cb(user_data, oauth2, 1, nullptr, 0, GRPC_STATUS_OK, nullptr);
-}
-
-static void process_oauth2_failure(void* state, grpc_auth_context* /*ctx*/,
-                                   const grpc_metadata* md, size_t md_count,
-                                   grpc_process_auth_metadata_done_cb cb,
-                                   void* user_data) {
-  const grpc_metadata* oauth2 =
-      find_metadata(md, md_count, "authorization", oauth2_md);
-  test_processor_state* s;
-  GPR_ASSERT(state != nullptr);
-  s = static_cast<test_processor_state*>(state);
-  GPR_ASSERT(s->pseudo_refcount == 1);
-  GPR_ASSERT(oauth2 != nullptr);
-  cb(user_data, oauth2, 1, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/, grpc_tls_version tls_version) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  ffd->tls_version = tls_version;
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  return f;
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_3(
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* server_args) {
-  return chttp2_create_fixture_secure_fullstack(client_args, server_args,
-                                                grpc_tls_version::TLS1_3);
-}
-
-static void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-static void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
-static void chttp2_init_client_simple_ssl_with_oauth2_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_core::ExecCtx exec_ctx;
-  grpc_slice ca_slice;
-  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                               grpc_load_file(CA_CERT_PATH, 1, &ca_slice)));
-  const char* test_root_cert =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(ca_slice);
-  grpc_channel_credentials* ssl_creds =
-      grpc_ssl_credentials_create(test_root_cert, nullptr, nullptr, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_credentials* creds =
-        reinterpret_cast<grpc_ssl_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_call_credentials* oauth2_creds =
-      grpc_md_only_test_credentials_create("authorization", oauth2_md);
-  grpc_channel_credentials* ssl_oauth2_creds =
-      grpc_composite_channel_credentials_create(ssl_creds, oauth2_creds,
-                                                nullptr);
-  grpc_arg ssl_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr")}};
-  const grpc_channel_args* new_client_args =
-      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
-  chttp2_init_client_secure_fullstack(f, new_client_args, ssl_oauth2_creds);
-  grpc_channel_args_destroy(new_client_args);
-  grpc_channel_credentials_release(ssl_creds);
-  grpc_call_credentials_release(oauth2_creds);
-  grpc_slice_unref(ca_slice);
-}
-
-static int fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-static void processor_destroy(void* state) {
-  test_processor_state* s = static_cast<test_processor_state*>(state);
-  GPR_ASSERT((s->pseudo_refcount--) == 1);
-  gpr_free(s);
-}
-
-static grpc_auth_metadata_processor test_processor_create(int failing) {
-  test_processor_state* s =
-      static_cast<test_processor_state*>(gpr_malloc(sizeof(*s)));
-  grpc_auth_metadata_processor result;
-  s->pseudo_refcount = 1;
-  result.state = s;
-  result.destroy = processor_destroy;
-  if (failing) {
-    result.process = process_oauth2_failure;
-  } else {
-    result.process = process_oauth2_success;
-  }
-  return result;
-}
-
-static void chttp2_init_server_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_slice cert_slice, key_slice;
-  GPR_ASSERT(GRPC_LOG_IF_ERROR(
-      "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
-  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                               grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
-  const char* server_cert =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
-  const char* server_key =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
-  grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
-  grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
-      nullptr, &pem_key_cert_pair, 1, 0, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_server_credentials* creds =
-        reinterpret_cast<grpc_ssl_server_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_server_credentials_set_auth_metadata_processor(
-      ssl_creds, test_processor_create(fail_server_auth_check(server_args)));
-  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
-  grpc_slice_unref(cert_slice);
-  grpc_slice_unref(key_slice);
-}
-
 // All test configurations
 
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/simple_ssl_with_oauth2_fullstack_tls1_3",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_DOES_NOT_SUPPORT_CLIENT_HANDSHAKE_COMPLETE_FIRST,
-     "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_3,
-     chttp2_init_client_simple_ssl_with_oauth2_secure_fullstack,
-     chttp2_init_server_simple_ssl_secure_fullstack,
-     chttp2_tear_down_secure_fullstack},
-};
+     "foo.test.google.fr",
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<Oauth2Fixture>(grpc_tls_version::TLS1_3);
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
diff --git a/test/core/end2end/fixtures/h2_proxy.cc b/test/core/end2end/fixtures/h2_proxy.cc
index 6df6d08..4dc7864 100644
--- a/test/core/end2end/fixtures/h2_proxy.cc
+++ b/test/core/end2end/fixtures/h2_proxy.cc
@@ -18,100 +18,82 @@
 
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/grpc_security.h>
-#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/proxy.h"
 #include "test/core/util/test_config.h"
 
-typedef struct fullstack_fixture_data {
-  grpc_end2end_proxy* proxy;
-} fullstack_fixture_data;
+class ProxyFixture : public CoreTestFixture {
+ public:
+  ProxyFixture(const grpc_core::ChannelArgs& client_args,
+               const grpc_core::ChannelArgs& server_args)
+      : proxy_(grpc_end2end_proxy_create(&proxy_def_, client_args.ToC().get(),
+                                         server_args.ToC().get())) {}
+  ~ProxyFixture() override { grpc_end2end_proxy_destroy(proxy_); }
 
-static grpc_server* create_proxy_server(const char* port,
+ private:
+  static grpc_server* CreateProxyServer(const char* port,
                                         const grpc_channel_args* server_args) {
-  grpc_server* s = grpc_server_create(server_args, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(s, port, server_creds));
-  grpc_server_credentials_release(server_creds);
-  return s;
-}
-
-static grpc_channel* create_proxy_client(const char* target,
-                                         const grpc_channel_args* client_args) {
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  grpc_channel* channel = grpc_channel_create(target, creds, client_args);
-  grpc_channel_credentials_release(creds);
-  return channel;
-}
-
-static const grpc_end2end_proxy_def proxy_def = {create_proxy_server,
-                                                 create_proxy_client};
-
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack(
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
-  fullstack_fixture_data* ffd = static_cast<fullstack_fixture_data*>(
-      gpr_malloc(sizeof(fullstack_fixture_data)));
-  memset(&f, 0, sizeof(f));
-
-  ffd->proxy = grpc_end2end_proxy_create(&proxy_def, client_args, server_args);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-void chttp2_init_client_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* client_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create(
-      grpc_end2end_proxy_get_client_target(ffd->proxy), creds, client_args);
-  grpc_channel_credentials_release(creds);
-  GPR_ASSERT(f->client);
-}
-
-void chttp2_init_server_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* server_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
+    grpc_server* s = grpc_server_create(server_args, nullptr);
+    grpc_server_credentials* server_creds =
+        grpc_insecure_server_credentials_create();
+    GPR_ASSERT(grpc_server_add_http2_port(s, port, server_creds));
+    grpc_server_credentials_release(server_creds);
+    return s;
   }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(
-      f->server, grpc_end2end_proxy_get_server_port(ffd->proxy), server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
 
-void chttp2_tear_down_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_end2end_proxy_destroy(ffd->proxy);
-  gpr_free(ffd);
-}
+  static grpc_channel* CreateProxyClient(const char* target,
+                                         const grpc_channel_args* client_args) {
+    grpc_channel_credentials* creds = grpc_insecure_credentials_create();
+    grpc_channel* channel = grpc_channel_create(target, creds, client_args);
+    grpc_channel_credentials_release(creds);
+    return channel;
+  }
+
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& args) override {
+    auto* server = grpc_server_create(args.ToC().get(), nullptr);
+    grpc_server_register_completion_queue(server, cq(), nullptr);
+    grpc_server_credentials* server_creds =
+        grpc_insecure_server_credentials_create();
+    GPR_ASSERT(grpc_server_add_http2_port(
+        server, grpc_end2end_proxy_get_server_port(proxy_), server_creds));
+    grpc_server_credentials_release(server_creds);
+    grpc_server_start(server);
+    return server;
+  }
+
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& args) override {
+    grpc_channel_credentials* creds = grpc_insecure_credentials_create();
+    auto* client = grpc_channel_create(
+        grpc_end2end_proxy_get_client_target(proxy_), creds, args.ToC().get());
+    grpc_channel_credentials_release(creds);
+    GPR_ASSERT(client);
+    return client;
+  }
+  const grpc_end2end_proxy_def proxy_def_ = {CreateProxyServer,
+                                             CreateProxyClient};
+  grpc_end2end_proxy* proxy_;
+};
 
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack+proxy",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_REQUEST_PROXYING |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack, chttp2_init_client_fullstack,
-     chttp2_init_server_fullstack, chttp2_tear_down_fullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs& client_args,
+        const grpc_core::ChannelArgs& server_args) {
+       return std::make_unique<ProxyFixture>(client_args, server_args);
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_sockpair+trace.cc b/test/core/end2end/fixtures/h2_sockpair+trace.cc
index c9a2634..bab3371 100644
--- a/test/core/end2end/fixtures/h2_sockpair+trace.cc
+++ b/test/core/end2end/fixtures/h2_sockpair+trace.cc
@@ -16,145 +16,30 @@
 //
 //
 
-#include <string.h>
-
-#include "absl/status/status.h"
-#include "absl/status/statusor.h"
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/status.h>
-#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 
-#include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/channel/channel_args_preconditioning.h"
-#include "src/core/lib/channel/channelz.h"
-#include "src/core/lib/config/core_configuration.h"
 #include "src/core/lib/debug/trace.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/ref_counted_ptr.h"
-#include "src/core/lib/iomgr/endpoint.h"
-#include "src/core/lib/iomgr/endpoint_pair.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/exec_ctx.h"
 #include "src/core/lib/iomgr/port.h"
-#include "src/core/lib/surface/channel.h"
-#include "src/core/lib/surface/channel_stack_type.h"
-#include "src/core/lib/surface/completion_queue.h"
-#include "src/core/lib/surface/server.h"
-#include "src/core/lib/transport/transport.h"
-#include "src/core/lib/transport/transport_fwd.h"
 #include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/sockpair_fixture.h"
 #include "test/core/util/test_config.h"
 
 #ifdef GRPC_POSIX_SOCKET
 #include <unistd.h>
 #endif
 
-// chttp2 transport that is immediately available (used for testing
-// connected_channel without a client_channel
-
-struct custom_fixture_data {
-  grpc_endpoint_pair ep;
-};
-
-static void server_setup_transport(void* ts, grpc_transport* transport) {
-  grpc_end2end_test_fixture* f = static_cast<grpc_end2end_test_fixture*>(ts);
-  grpc_core::ExecCtx exec_ctx;
-  custom_fixture_data* fixture_data =
-      static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_endpoint_add_to_pollset(fixture_data->ep.server, grpc_cq_pollset(f->cq));
-  grpc_core::Server* core_server = grpc_core::Server::FromC(f->server);
-  grpc_error_handle error = core_server->SetupTransport(
-      transport, nullptr, core_server->channel_args(), nullptr);
-  if (error.ok()) {
-    grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
-  } else {
-    grpc_transport_destroy(transport);
-  }
-}
-
-typedef struct {
-  grpc_end2end_test_fixture* f;
-  const grpc_channel_args* client_args;
-} sp_client_setup;
-
-static void client_setup_transport(void* ts, grpc_transport* transport) {
-  sp_client_setup* cs = static_cast<sp_client_setup*>(ts);
-
-  auto args = grpc_core::ChannelArgs::FromC(cs->client_args)
-                  .Set(GRPC_ARG_DEFAULT_AUTHORITY, "test-authority");
-  auto channel = grpc_core::Channel::Create(
-      "socketpair-target", args, GRPC_CLIENT_DIRECT_CHANNEL, transport);
-  if (channel.ok()) {
-    cs->f->client = channel->release()->c_ptr();
-    grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
-  } else {
-    cs->f->client = grpc_lame_client_channel_create(
-        nullptr, static_cast<grpc_status_code>(channel.status().code()),
-        "lame channel");
-    grpc_transport_destroy(transport);
-  }
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_socketpair(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  custom_fixture_data* fixture_data = static_cast<custom_fixture_data*>(
-      gpr_malloc(sizeof(custom_fixture_data)));
-  grpc_end2end_test_fixture f;
-  memset(&f, 0, sizeof(f));
-  f.fixture_data = fixture_data;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  fixture_data->ep = grpc_iomgr_create_endpoint_pair("fixture", nullptr);
-  return f;
-}
-
-static void chttp2_init_client_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_core::ExecCtx exec_ctx;
-  auto* fixture_data = static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_transport* transport;
-  sp_client_setup cs;
-  cs.client_args = client_args;
-  cs.f = f;
-  auto client_channel_args = grpc_core::CoreConfiguration::Get()
-                                 .channel_args_preconditioning()
-                                 .PreconditionChannelArgs(client_args);
-  transport = grpc_create_chttp2_transport(client_channel_args,
-                                           fixture_data->ep.client, true);
-  client_setup_transport(&cs, transport);
-  GPR_ASSERT(f->client);
-}
-
-static void chttp2_init_server_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_core::ExecCtx exec_ctx;
-  auto* fixture_data = static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_transport* transport;
-  GPR_ASSERT(!f->server);
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_start(f->server);
-  auto server_channel_args = grpc_core::CoreConfiguration::Get()
-                                 .channel_args_preconditioning()
-                                 .PreconditionChannelArgs(server_args);
-  transport = grpc_create_chttp2_transport(server_channel_args,
-                                           fixture_data->ep.server, false);
-  server_setup_transport(f, transport);
-}
-
-static void chttp2_tear_down_socketpair(grpc_end2end_test_fixture* f) {
-  grpc_core::ExecCtx exec_ctx;
-  gpr_free(f->fixture_data);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/socketpair", FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, nullptr,
-     chttp2_create_fixture_socketpair, chttp2_init_client_socketpair,
-     chttp2_init_server_socketpair, chttp2_tear_down_socketpair},
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<SockpairFixture>(grpc_core::ChannelArgs());
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_sockpair.cc b/test/core/end2end/fixtures/h2_sockpair.cc
index 37ec5bd..f9e8b45 100644
--- a/test/core/end2end/fixtures/h2_sockpair.cc
+++ b/test/core/end2end/fixtures/h2_sockpair.cc
@@ -18,137 +18,22 @@
 
 #include <string.h>
 
-#include "absl/status/status.h"
-#include "absl/status/statusor.h"
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/status.h>
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
 
-#include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/channel/channel_args_preconditioning.h"
-#include "src/core/lib/channel/channelz.h"
-#include "src/core/lib/config/core_configuration.h"
-#include "src/core/lib/gprpp/ref_counted_ptr.h"
-#include "src/core/lib/iomgr/endpoint.h"
-#include "src/core/lib/iomgr/endpoint_pair.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/exec_ctx.h"
-#include "src/core/lib/surface/channel.h"
-#include "src/core/lib/surface/channel_stack_type.h"
-#include "src/core/lib/surface/completion_queue.h"
-#include "src/core/lib/surface/server.h"
-#include "src/core/lib/transport/transport.h"
-#include "src/core/lib/transport/transport_fwd.h"
 #include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/sockpair_fixture.h"
 #include "test/core/util/test_config.h"
 
-// chttp2 transport that is immediately available (used for testing
-// connected_channel without a client_channel
-
-struct custom_fixture_data {
-  grpc_endpoint_pair ep;
-};
-
-static void server_setup_transport(void* ts, grpc_transport* transport) {
-  grpc_end2end_test_fixture* f = static_cast<grpc_end2end_test_fixture*>(ts);
-  grpc_core::ExecCtx exec_ctx;
-  custom_fixture_data* fixture_data =
-      static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_endpoint_add_to_pollset(fixture_data->ep.server, grpc_cq_pollset(f->cq));
-  grpc_core::Server* core_server = grpc_core::Server::FromC(f->server);
-  grpc_error_handle error = core_server->SetupTransport(
-      transport, nullptr, core_server->channel_args(), nullptr);
-  if (error.ok()) {
-    grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
-  } else {
-    grpc_transport_destroy(transport);
-  }
-}
-
-typedef struct {
-  grpc_end2end_test_fixture* f;
-  const grpc_channel_args* client_args;
-} sp_client_setup;
-
-static void client_setup_transport(void* ts, grpc_transport* transport) {
-  sp_client_setup* cs = static_cast<sp_client_setup*>(ts);
-
-  auto args = grpc_core::ChannelArgs::FromC(cs->client_args)
-                  .Set(GRPC_ARG_DEFAULT_AUTHORITY, "test-authority");
-  auto channel = grpc_core::Channel::Create(
-      "socketpair-target", args, GRPC_CLIENT_DIRECT_CHANNEL, transport);
-  if (channel.ok()) {
-    cs->f->client = channel->release()->c_ptr();
-    grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
-  } else {
-    cs->f->client = grpc_lame_client_channel_create(
-        nullptr, static_cast<grpc_status_code>(channel.status().code()),
-        "lame channel");
-    grpc_transport_destroy(transport);
-  }
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_socketpair(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  custom_fixture_data* fixture_data = static_cast<custom_fixture_data*>(
-      gpr_malloc(sizeof(custom_fixture_data)));
-  grpc_end2end_test_fixture f;
-  memset(&f, 0, sizeof(f));
-  f.fixture_data = fixture_data;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  fixture_data->ep = grpc_iomgr_create_endpoint_pair("fixture", nullptr);
-  return f;
-}
-
-static void chttp2_init_client_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_core::ExecCtx exec_ctx;
-  auto* fixture_data = static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_transport* transport;
-  sp_client_setup cs;
-  auto client_channel_args = grpc_core::CoreConfiguration::Get()
-                                 .channel_args_preconditioning()
-                                 .PreconditionChannelArgs(client_args);
-  cs.client_args = client_channel_args.ToC().release();
-  cs.f = f;
-  transport = grpc_create_chttp2_transport(client_channel_args,
-                                           fixture_data->ep.client, true);
-  client_setup_transport(&cs, transport);
-  grpc_channel_args_destroy(cs.client_args);
-  GPR_ASSERT(f->client);
-}
-
-static void chttp2_init_server_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_core::ExecCtx exec_ctx;
-  auto* fixture_data = static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_transport* transport;
-  GPR_ASSERT(!f->server);
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_start(f->server);
-  auto server_channel_args = grpc_core::CoreConfiguration::Get()
-                                 .channel_args_preconditioning()
-                                 .PreconditionChannelArgs(server_args);
-  transport = grpc_create_chttp2_transport(server_channel_args,
-                                           fixture_data->ep.server, false);
-  server_setup_transport(f, transport);
-}
-
-static void chttp2_tear_down_socketpair(grpc_end2end_test_fixture* f) {
-  grpc_core::ExecCtx exec_ctx;
-  gpr_free(f->fixture_data);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/socketpair", FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, nullptr,
-     chttp2_create_fixture_socketpair, chttp2_init_client_socketpair,
-     chttp2_init_server_socketpair, chttp2_tear_down_socketpair},
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<SockpairFixture>(grpc_core::ChannelArgs());
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_sockpair_1byte.cc b/test/core/end2end/fixtures/h2_sockpair_1byte.cc
index e19d44d..504460e 100644
--- a/test/core/end2end/fixtures/h2_sockpair_1byte.cc
+++ b/test/core/end2end/fixtures/h2_sockpair_1byte.cc
@@ -18,150 +18,26 @@
 
 #include <string.h>
 
-#include "absl/status/status.h"
-#include "absl/status/statusor.h"
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/status.h>
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
 
-#include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/channel/channel_args_preconditioning.h"
-#include "src/core/lib/channel/channelz.h"
-#include "src/core/lib/config/core_configuration.h"
-#include "src/core/lib/gpr/useful.h"
-#include "src/core/lib/gprpp/ref_counted_ptr.h"
-#include "src/core/lib/iomgr/endpoint.h"
-#include "src/core/lib/iomgr/endpoint_pair.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/exec_ctx.h"
-#include "src/core/lib/surface/channel.h"
-#include "src/core/lib/surface/channel_stack_type.h"
-#include "src/core/lib/surface/completion_queue.h"
-#include "src/core/lib/surface/server.h"
-#include "src/core/lib/transport/transport.h"
-#include "src/core/lib/transport/transport_fwd.h"
 #include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/sockpair_fixture.h"
 #include "test/core/util/test_config.h"
 
-// chttp2 transport that is immediately available (used for testing
-// connected_channel without a client_channel
-
-struct custom_fixture_data {
-  grpc_endpoint_pair ep;
-};
-
-static void server_setup_transport(void* ts, grpc_transport* transport) {
-  grpc_end2end_test_fixture* f = static_cast<grpc_end2end_test_fixture*>(ts);
-  grpc_core::ExecCtx exec_ctx;
-  custom_fixture_data* fixture_data =
-      static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_endpoint_add_to_pollset(fixture_data->ep.server, grpc_cq_pollset(f->cq));
-  grpc_core::Server* core_server = grpc_core::Server::FromC(f->server);
-  grpc_error_handle error = core_server->SetupTransport(
-      transport, nullptr, core_server->channel_args(), nullptr);
-  if (error.ok()) {
-    grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
-  } else {
-    grpc_transport_destroy(transport);
-  }
-}
-
-typedef struct {
-  grpc_end2end_test_fixture* f;
-  const grpc_channel_args* client_args;
-} sp_client_setup;
-
-static void client_setup_transport(void* ts, grpc_transport* transport) {
-  sp_client_setup* cs = static_cast<sp_client_setup*>(ts);
-
-  auto args = grpc_core::ChannelArgs::FromC(cs->client_args)
-                  .Set(GRPC_ARG_DEFAULT_AUTHORITY, "test-authority");
-  auto channel = grpc_core::Channel::Create(
-      "socketpair-target", args, GRPC_CLIENT_DIRECT_CHANNEL, transport);
-  if (channel.ok()) {
-    cs->f->client = channel->release()->c_ptr();
-    grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
-  } else {
-    cs->f->client = grpc_lame_client_channel_create(
-        nullptr, static_cast<grpc_status_code>(channel.status().code()),
-        "lame channel");
-    grpc_transport_destroy(transport);
-  }
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_socketpair(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  custom_fixture_data* fixture_data = static_cast<custom_fixture_data*>(
-      gpr_malloc(sizeof(custom_fixture_data)));
-  grpc_end2end_test_fixture f;
-  memset(&f, 0, sizeof(f));
-  f.fixture_data = fixture_data;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  grpc_arg a[3];
-  a[0].key = const_cast<char*>(GRPC_ARG_TCP_READ_CHUNK_SIZE);
-  a[0].type = GRPC_ARG_INTEGER;
-  a[0].value.integer = 1;
-  a[1].key = const_cast<char*>(GRPC_ARG_TCP_MIN_READ_CHUNK_SIZE);
-  a[1].type = GRPC_ARG_INTEGER;
-  a[1].value.integer = 1;
-  a[2].key = const_cast<char*>(GRPC_ARG_TCP_MAX_READ_CHUNK_SIZE);
-  a[2].type = GRPC_ARG_INTEGER;
-  a[2].value.integer = 1;
-  grpc_channel_args args = {GPR_ARRAY_SIZE(a), a};
-  fixture_data->ep = grpc_iomgr_create_endpoint_pair("fixture", &args);
-  return f;
-}
-
-static void chttp2_init_client_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_core::ExecCtx exec_ctx;
-  auto* fixture_data = static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_transport* transport;
-  sp_client_setup cs;
-  auto client_channel_args = grpc_core::CoreConfiguration::Get()
-                                 .channel_args_preconditioning()
-                                 .PreconditionChannelArgs(client_args);
-  cs.client_args = client_channel_args.ToC().release();
-  cs.f = f;
-  transport = grpc_create_chttp2_transport(client_channel_args,
-                                           fixture_data->ep.client, true);
-  client_setup_transport(&cs, transport);
-  grpc_channel_args_destroy(cs.client_args);
-  GPR_ASSERT(f->client);
-}
-
-static void chttp2_init_server_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_core::ExecCtx exec_ctx;
-  auto* fixture_data = static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_transport* transport;
-  GPR_ASSERT(!f->server);
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_start(f->server);
-  auto server_channel_args = grpc_core::CoreConfiguration::Get()
-                                 .channel_args_preconditioning()
-                                 .PreconditionChannelArgs(server_args);
-  transport = grpc_create_chttp2_transport(server_channel_args,
-                                           fixture_data->ep.server, false);
-  server_setup_transport(f, transport);
-}
-
-static void chttp2_tear_down_socketpair(grpc_end2end_test_fixture* f) {
-  grpc_core::ExecCtx exec_ctx;
-  gpr_free(f->fixture_data);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
-    {"chttp2/socketpair_one_byte_at_a_time",
-     FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, nullptr,
-     chttp2_create_fixture_socketpair, chttp2_init_client_socketpair,
-     chttp2_init_server_socketpair, chttp2_tear_down_socketpair},
+static CoreTestConfiguration configs[] = {
+    {"chttp2/socketpair", FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, nullptr,
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<SockpairFixture>(
+           grpc_core::ChannelArgs()
+               .Set(GRPC_ARG_TCP_READ_CHUNK_SIZE, 1)
+               .Set(GRPC_ARG_TCP_MIN_READ_CHUNK_SIZE, 1)
+               .Set(GRPC_ARG_TCP_MAX_READ_CHUNK_SIZE, 1));
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_sockpair_with_minstack.cc b/test/core/end2end/fixtures/h2_sockpair_with_minstack.cc
index 5eb130e..a70d2af 100644
--- a/test/core/end2end/fixtures/h2_sockpair_with_minstack.cc
+++ b/test/core/end2end/fixtures/h2_sockpair_with_minstack.cc
@@ -18,143 +18,41 @@
 
 #include <string.h>
 
-#include "absl/status/status.h"
-#include "absl/status/statusor.h"
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/status.h>
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
 
-#include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/channel/channel_args_preconditioning.h"
-#include "src/core/lib/channel/channelz.h"
-#include "src/core/lib/config/core_configuration.h"
-#include "src/core/lib/gprpp/ref_counted_ptr.h"
-#include "src/core/lib/iomgr/endpoint.h"
-#include "src/core/lib/iomgr/endpoint_pair.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/exec_ctx.h"
-#include "src/core/lib/surface/channel.h"
-#include "src/core/lib/surface/channel_stack_type.h"
-#include "src/core/lib/surface/completion_queue.h"
-#include "src/core/lib/surface/server.h"
-#include "src/core/lib/transport/transport.h"
-#include "src/core/lib/transport/transport_fwd.h"
 #include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/sockpair_fixture.h"
 #include "test/core/util/test_config.h"
 
-// chttp2 transport that is immediately available (used for testing
-// connected_channel without a client_channel)
+class SockpairWithMinstackFixture : public SockpairFixture {
+ public:
+  using SockpairFixture::SockpairFixture;
 
-struct custom_fixture_data {
-  grpc_endpoint_pair ep;
+ private:
+  grpc_core::ChannelArgs MutateClientArgs(
+      grpc_core::ChannelArgs args) override {
+    return args.Set(GRPC_ARG_MINIMAL_STACK, true);
+  }
+  grpc_core::ChannelArgs MutateServerArgs(
+      grpc_core::ChannelArgs args) override {
+    return args.Set(GRPC_ARG_MINIMAL_STACK, true);
+  }
 };
 
-static void server_setup_transport(void* ts, grpc_transport* transport) {
-  grpc_end2end_test_fixture* f = static_cast<grpc_end2end_test_fixture*>(ts);
-  grpc_core::ExecCtx exec_ctx;
-  custom_fixture_data* fixture_data =
-      static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_endpoint_add_to_pollset(fixture_data->ep.server, grpc_cq_pollset(f->cq));
-  grpc_core::Server* core_server = grpc_core::Server::FromC(f->server);
-  grpc_error_handle error = core_server->SetupTransport(
-      transport, nullptr, core_server->channel_args(), nullptr);
-  if (error == absl::OkStatus()) {
-    grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
-  } else {
-    grpc_transport_destroy(transport);
-  }
-}
-
-typedef struct {
-  grpc_end2end_test_fixture* f;
-  const grpc_channel_args* client_args;
-} sp_client_setup;
-
-static void client_setup_transport(void* ts, grpc_transport* transport) {
-  sp_client_setup* cs = static_cast<sp_client_setup*>(ts);
-
-  auto args = grpc_core::ChannelArgs::FromC(cs->client_args)
-                  .Set(GRPC_ARG_MINIMAL_STACK, true)
-                  .Set(GRPC_ARG_DEFAULT_AUTHORITY, "test-authority");
-  auto channel = grpc_core::Channel::Create(
-      "socketpair-target", args, GRPC_CLIENT_DIRECT_CHANNEL, transport);
-  if (channel.ok()) {
-    cs->f->client = channel->release()->c_ptr();
-    grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
-  } else {
-    cs->f->client = grpc_lame_client_channel_create(
-        nullptr, static_cast<grpc_status_code>(channel.status().code()),
-        "lame channel");
-    grpc_transport_destroy(transport);
-  }
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_socketpair(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  custom_fixture_data* fixture_data = static_cast<custom_fixture_data*>(
-      gpr_malloc(sizeof(custom_fixture_data)));
-  grpc_end2end_test_fixture f;
-  memset(&f, 0, sizeof(f));
-  f.fixture_data = fixture_data;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  fixture_data->ep = grpc_iomgr_create_endpoint_pair("fixture", nullptr);
-  return f;
-}
-
-static void chttp2_init_client_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_core::ExecCtx exec_ctx;
-  auto* fixture_data = static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_transport* transport;
-  sp_client_setup cs;
-  auto final_client_args = grpc_core::CoreConfiguration::Get()
-                               .channel_args_preconditioning()
-                               .PreconditionChannelArgs(client_args)
-                               .Set(GRPC_ARG_MINIMAL_STACK, true);
-  auto c_client_args = final_client_args.ToC();
-  cs.client_args = c_client_args.get();
-  cs.f = f;
-  transport = grpc_create_chttp2_transport(final_client_args,
-                                           fixture_data->ep.client, true);
-  client_setup_transport(&cs, transport);
-  GPR_ASSERT(f->client);
-}
-
-static void chttp2_init_server_socketpair(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_core::ExecCtx exec_ctx;
-  auto* fixture_data = static_cast<custom_fixture_data*>(f->fixture_data);
-  grpc_transport* transport;
-  GPR_ASSERT(!f->server);
-  f->server = grpc_server_create(grpc_core::ChannelArgs::FromC(server_args)
-                                     .Set(GRPC_ARG_MINIMAL_STACK, true)
-                                     .ToC()
-                                     .get(),
-                                 nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_start(f->server);
-  transport = grpc_create_chttp2_transport(
-      grpc_core::Server::FromC(f->server)->channel_args(),
-      fixture_data->ep.server, false);
-  server_setup_transport(f, transport);
-}
-
-static void chttp2_tear_down_socketpair(grpc_end2end_test_fixture* f) {
-  grpc_core::ExecCtx exec_ctx;
-  gpr_free(f->fixture_data);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/socketpair+minstack",
      FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_DOES_NOT_SUPPORT_DEADLINES,
-     nullptr, chttp2_create_fixture_socketpair, chttp2_init_client_socketpair,
-     chttp2_init_server_socketpair, chttp2_tear_down_socketpair},
+     nullptr,
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<SockpairWithMinstackFixture>(
+           grpc_core::ChannelArgs());
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_ssl_cred_reload_fixture.h b/test/core/end2end/fixtures/h2_ssl_cred_reload_fixture.h
new file mode 100644
index 0000000..2cafcdc
--- /dev/null
+++ b/test/core/end2end/fixtures/h2_ssl_cred_reload_fixture.h
@@ -0,0 +1,139 @@
+// Copyright 2015 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GRPC_TEST_CORE_END2END_FIXTURES_H2_SSL_CRED_RELOAD_FIXTURE_H
+#define GRPC_TEST_CORE_END2END_FIXTURES_H2_SSL_CRED_RELOAD_FIXTURE_H
+
+#include <stddef.h>
+
+#include <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+#include <grpc/grpc_security_constants.h>
+#include <grpc/slice.h>
+#include <grpc/status.h>
+#include <grpc/support/log.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/load_file.h"
+#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
+#include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
+
+class SslCredReloadFixture : public SecureFixture {
+ public:
+  explicit SslCredReloadFixture(grpc_tls_version tls_version)
+      : tls_version_(tls_version) {}
+
+  static const char* CaCertPath() { return "src/core/tsi/test_creds/ca.pem"; }
+  static const char* CertPath() {
+    return "src/core/tsi/test_creds/server1.pem";
+  }
+  static const char* KeyPath() { return "src/core/tsi/test_creds/server1.key"; }
+
+ private:
+  grpc_core::ChannelArgs MutateClientArgs(
+      grpc_core::ChannelArgs args) override {
+    return args.Set(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, "foo.test.google.fr");
+  }
+  grpc_channel_credentials* MakeClientCreds(
+      const grpc_core::ChannelArgs&) override {
+    grpc_channel_credentials* ssl_creds =
+        grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
+    if (ssl_creds != nullptr) {
+      // Set the min and max TLS version.
+      grpc_ssl_credentials* creds =
+          reinterpret_cast<grpc_ssl_credentials*>(ssl_creds);
+      creds->set_min_tls_version(tls_version_);
+      creds->set_max_tls_version(tls_version_);
+    }
+    return ssl_creds;
+  }
+  grpc_server_credentials* MakeServerCreds(
+      const grpc_core::ChannelArgs& args) override {
+    server_credential_reloaded_ = false;
+    grpc_ssl_server_credentials_options* options =
+        grpc_ssl_server_credentials_create_options_using_config_fetcher(
+            GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE,
+            ssl_server_certificate_config_callback, this);
+    grpc_server_credentials* ssl_creds =
+        grpc_ssl_server_credentials_create_with_options(options);
+    if (ssl_creds != nullptr) {
+      // Set the min and max TLS version.
+      grpc_ssl_server_credentials* creds =
+          reinterpret_cast<grpc_ssl_server_credentials*>(ssl_creds);
+      creds->set_min_tls_version(tls_version_);
+      creds->set_max_tls_version(tls_version_);
+    }
+    if (args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)) {
+      grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
+                                                nullptr};
+      grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
+    }
+    return ssl_creds;
+  }
+
+  static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
+                                   const grpc_metadata* /*md*/,
+                                   size_t /*md_count*/,
+                                   grpc_process_auth_metadata_done_cb cb,
+                                   void* user_data) {
+    GPR_ASSERT(state == nullptr);
+    cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
+  }
+
+  grpc_ssl_certificate_config_reload_status SslServerCertificateConfigCallback(
+      grpc_ssl_server_certificate_config** config) {
+    if (config == nullptr) {
+      return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_FAIL;
+    }
+    if (!server_credential_reloaded_) {
+      grpc_slice ca_slice, cert_slice, key_slice;
+      GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
+                                   grpc_load_file(CaCertPath(), 1, &ca_slice)));
+      GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
+                                   grpc_load_file(CertPath(), 1, &cert_slice)));
+      GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
+                                   grpc_load_file(KeyPath(), 1, &key_slice)));
+      const char* ca_cert =
+          reinterpret_cast<const char*> GRPC_SLICE_START_PTR(ca_slice);
+      const char* server_cert =
+          reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
+      const char* server_key =
+          reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
+      grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
+      *config = grpc_ssl_server_certificate_config_create(
+          ca_cert, &pem_key_cert_pair, 1);
+      grpc_slice_unref(cert_slice);
+      grpc_slice_unref(key_slice);
+      grpc_slice_unref(ca_slice);
+      server_credential_reloaded_ = true;
+      return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_NEW;
+    } else {
+      return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_UNCHANGED;
+    }
+  }
+
+  static grpc_ssl_certificate_config_reload_status
+  ssl_server_certificate_config_callback(
+      void* user_data, grpc_ssl_server_certificate_config** config) {
+    return static_cast<SslCredReloadFixture*>(user_data)
+        ->SslServerCertificateConfigCallback(config);
+  }
+
+  grpc_tls_version tls_version_;
+  bool server_credential_reloaded_ = false;
+};
+
+#endif  // GRPC_TEST_CORE_END2END_FIXTURES_H2_SSL_CRED_RELOAD_FIXTURE_H
diff --git a/test/core/end2end/fixtures/h2_ssl_cred_reload_tls12.cc b/test/core/end2end/fixtures/h2_ssl_cred_reload_tls12.cc
index 722fee8..8f8defb 100644
--- a/test/core/end2end/fixtures/h2_ssl_cred_reload_tls12.cc
+++ b/test/core/end2end/fixtures/h2_ssl_cred_reload_tls12.cc
@@ -18,208 +18,31 @@
 
 #include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
 #include <grpc/grpc_security_constants.h>
-#include <grpc/slice.h>
-#include <grpc/status.h>
-#include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/host_port.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/load_file.h"
-#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/h2_ssl_cred_reload_fixture.h"
 #include "test/core/util/test_config.h"
 
-#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem"
-#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem"
-#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key"
-
-struct fullstack_secure_fixture_data {
-  std::string localaddr;
-  grpc_tls_version tls_version;
-  bool server_credential_reloaded = false;
-};
-
-static grpc_ssl_certificate_config_reload_status
-ssl_server_certificate_config_callback(
-    void* user_data, grpc_ssl_server_certificate_config** config) {
-  if (config == nullptr) {
-    return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_FAIL;
-  }
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(user_data);
-  if (!ffd->server_credential_reloaded) {
-    grpc_slice ca_slice, cert_slice, key_slice;
-    GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                                 grpc_load_file(CA_CERT_PATH, 1, &ca_slice)));
-    GPR_ASSERT(GRPC_LOG_IF_ERROR(
-        "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
-    GPR_ASSERT(GRPC_LOG_IF_ERROR(
-        "load_file", grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
-    const char* ca_cert =
-        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(ca_slice);
-    const char* server_cert =
-        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
-    const char* server_key =
-        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
-    grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
-    *config = grpc_ssl_server_certificate_config_create(ca_cert,
-                                                        &pem_key_cert_pair, 1);
-    grpc_slice_unref(cert_slice);
-    grpc_slice_unref(key_slice);
-    grpc_slice_unref(ca_slice);
-    ffd->server_credential_reloaded = true;
-    return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_NEW;
-  } else {
-    return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_UNCHANGED;
-  }
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/, grpc_tls_version tls_version) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  ffd->tls_version = tls_version;
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_2(
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* server_args) {
-  return chttp2_create_fixture_secure_fullstack(client_args, server_args,
-                                                grpc_tls_version::TLS1_2);
-}
-
-static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
-                                 const grpc_metadata* /*md*/,
-                                 size_t /*md_count*/,
-                                 grpc_process_auth_metadata_done_cb cb,
-                                 void* user_data) {
-  GPR_ASSERT(state == nullptr);
-  cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
-}
-
-static void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-static void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  ffd->server_credential_reloaded = false;
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
-static void chttp2_init_client_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_channel_credentials* ssl_creds =
-      grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_credentials* creds =
-        reinterpret_cast<grpc_ssl_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_arg ssl_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr")}};
-  const grpc_channel_args* new_client_args =
-      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
-  chttp2_init_client_secure_fullstack(f, new_client_args, ssl_creds);
-  grpc_channel_args_destroy(new_client_args);
-}
-
-static int fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-static void chttp2_init_server_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_ssl_server_credentials_options* options =
-      grpc_ssl_server_credentials_create_options_using_config_fetcher(
-          GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE,
-          ssl_server_certificate_config_callback, f->fixture_data);
-  grpc_server_credentials* ssl_creds =
-      grpc_ssl_server_credentials_create_with_options(options);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_server_credentials* creds =
-        reinterpret_cast<grpc_ssl_server_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  if (fail_server_auth_check(server_args)) {
-    grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
-                                              nullptr};
-    grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
-  }
-  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
-}
-
 // All test configurations
 
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/simple_ssl_fullstack_tls1_2",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_2,
-     chttp2_init_client_simple_ssl_secure_fullstack,
-     chttp2_init_server_simple_ssl_secure_fullstack,
-     chttp2_tear_down_secure_fullstack},
+     "foo.test.google.fr",
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<SslCredReloadFixture>(TLS1_2);
+     }},
 };
 
 int main(int argc, char** argv) {
@@ -227,7 +50,8 @@
 
   grpc::testing::TestEnvironment env(&argc, argv);
   grpc_end2end_tests_pre_init();
-  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, CA_CERT_PATH);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path,
+                        SslCredReloadFixture::CaCertPath());
 
   grpc_init();
 
diff --git a/test/core/end2end/fixtures/h2_ssl_cred_reload_tls13.cc b/test/core/end2end/fixtures/h2_ssl_cred_reload_tls13.cc
index 41ece7c..572d467 100644
--- a/test/core/end2end/fixtures/h2_ssl_cred_reload_tls13.cc
+++ b/test/core/end2end/fixtures/h2_ssl_cred_reload_tls13.cc
@@ -18,217 +18,40 @@
 
 #include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
 #include <grpc/grpc_security_constants.h>
-#include <grpc/slice.h>
-#include <grpc/status.h>
-#include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/host_port.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/load_file.h"
-#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/h2_ssl_cred_reload_fixture.h"
 #include "test/core/util/test_config.h"
 
-#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem"
-#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem"
-#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key"
-
-struct fullstack_secure_fixture_data {
-  std::string localaddr;
-  grpc_tls_version tls_version;
-  bool server_credential_reloaded = false;
-};
-
-static grpc_ssl_certificate_config_reload_status
-ssl_server_certificate_config_callback(
-    void* user_data, grpc_ssl_server_certificate_config** config) {
-  if (config == nullptr) {
-    return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_FAIL;
-  }
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(user_data);
-  if (!ffd->server_credential_reloaded) {
-    grpc_slice ca_slice, cert_slice, key_slice;
-    GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                                 grpc_load_file(CA_CERT_PATH, 1, &ca_slice)));
-    GPR_ASSERT(GRPC_LOG_IF_ERROR(
-        "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
-    GPR_ASSERT(GRPC_LOG_IF_ERROR(
-        "load_file", grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
-    const char* ca_cert =
-        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(ca_slice);
-    const char* server_cert =
-        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
-    const char* server_key =
-        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
-    grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
-    *config = grpc_ssl_server_certificate_config_create(ca_cert,
-                                                        &pem_key_cert_pair, 1);
-    grpc_slice_unref(cert_slice);
-    grpc_slice_unref(key_slice);
-    grpc_slice_unref(ca_slice);
-    ffd->server_credential_reloaded = true;
-    return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_NEW;
-  } else {
-    return GRPC_SSL_CERTIFICATE_CONFIG_RELOAD_UNCHANGED;
-  }
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/, grpc_tls_version tls_version) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  ffd->tls_version = tls_version;
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_3(
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* server_args) {
-  return chttp2_create_fixture_secure_fullstack(client_args, server_args,
-                                                grpc_tls_version::TLS1_3);
-}
-
-static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
-                                 const grpc_metadata* /*md*/,
-                                 size_t /*md_count*/,
-                                 grpc_process_auth_metadata_done_cb cb,
-                                 void* user_data) {
-  GPR_ASSERT(state == nullptr);
-  cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
-}
-
-static void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-static void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  ffd->server_credential_reloaded = false;
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
-static void chttp2_init_client_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_channel_credentials* ssl_creds =
-      grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_credentials* creds =
-        reinterpret_cast<grpc_ssl_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_arg ssl_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr")}};
-  const grpc_channel_args* new_client_args =
-      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
-  chttp2_init_client_secure_fullstack(f, new_client_args, ssl_creds);
-  grpc_channel_args_destroy(new_client_args);
-}
-
-static int fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-static void chttp2_init_server_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_ssl_server_credentials_options* options =
-      grpc_ssl_server_credentials_create_options_using_config_fetcher(
-          GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE,
-          ssl_server_certificate_config_callback, f->fixture_data);
-  grpc_server_credentials* ssl_creds =
-      grpc_ssl_server_credentials_create_with_options(options);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_server_credentials* creds =
-        reinterpret_cast<grpc_ssl_server_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  if (fail_server_auth_check(server_args)) {
-    grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
-                                              nullptr};
-    grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
-  }
-  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
-}
-
 // All test configurations
 
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/simple_ssl_fullstack_tls1_3",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_DOES_NOT_SUPPORT_CLIENT_HANDSHAKE_COMPLETE_FIRST,
-     "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_3,
-     chttp2_init_client_simple_ssl_secure_fullstack,
-     chttp2_init_server_simple_ssl_secure_fullstack,
-     chttp2_tear_down_secure_fullstack},
-};
+     "foo.test.google.fr",
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<SslCredReloadFixture>(TLS1_3);
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
 
   grpc::testing::TestEnvironment env(&argc, argv);
   grpc_end2end_tests_pre_init();
-  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, CA_CERT_PATH);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path,
+                        SslCredReloadFixture::CaCertPath());
 
   grpc_init();
 
diff --git a/test/core/end2end/fixtures/h2_ssl_proxy.cc b/test/core/end2end/fixtures/h2_ssl_proxy.cc
index 7011efe..28514ae 100644
--- a/test/core/end2end/fixtures/h2_ssl_proxy.cc
+++ b/test/core/end2end/fixtures/h2_ssl_proxy.cc
@@ -18,11 +18,13 @@
 
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/grpc_security.h>
 #include <grpc/slice.h>
 #include <grpc/status.h>
-#include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
@@ -39,72 +41,6 @@
 #define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem"
 #define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key"
 
-typedef struct fullstack_secure_fixture_data {
-  grpc_end2end_proxy* proxy;
-} fullstack_secure_fixture_data;
-
-static grpc_server* create_proxy_server(const char* port,
-                                        const grpc_channel_args* server_args) {
-  grpc_server* s = grpc_server_create(server_args, nullptr);
-  grpc_slice cert_slice, key_slice;
-  GPR_ASSERT(GRPC_LOG_IF_ERROR(
-      "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
-  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                               grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
-  const char* server_cert =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
-  const char* server_key =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
-  grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
-  grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
-      nullptr, &pem_key_cert_pair, 1, 0, nullptr);
-  grpc_slice_unref(cert_slice);
-  grpc_slice_unref(key_slice);
-  GPR_ASSERT(grpc_server_add_http2_port(s, port, ssl_creds));
-  grpc_server_credentials_release(ssl_creds);
-  return s;
-}
-
-static grpc_channel* create_proxy_client(const char* target,
-                                         const grpc_channel_args* client_args) {
-  grpc_channel* channel;
-  grpc_channel_credentials* ssl_creds =
-      grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
-  grpc_arg ssl_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr")}};
-  const grpc_channel_args* new_client_args =
-      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
-  channel = grpc_channel_create(target, ssl_creds, new_client_args);
-  grpc_channel_credentials_release(ssl_creds);
-  {
-    grpc_core::ExecCtx exec_ctx;
-    grpc_channel_args_destroy(new_client_args);
-  }
-  return channel;
-}
-
-static const grpc_end2end_proxy_def proxy_def = {create_proxy_server,
-                                                 create_proxy_client};
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(
-          gpr_malloc(sizeof(fullstack_secure_fixture_data)));
-  memset(&f, 0, sizeof(f));
-
-  ffd->proxy = grpc_end2end_proxy_create(&proxy_def, client_args, server_args);
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
 static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
                                  const grpc_metadata* /*md*/,
                                  size_t /*md_count*/,
@@ -114,106 +50,118 @@
   cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
 }
 
-static void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(
-      grpc_end2end_proxy_get_client_target(ffd->proxy), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
+class SslProxyFixture : public CoreTestFixture {
+ public:
+  SslProxyFixture(const grpc_core::ChannelArgs& client_args,
+                  const grpc_core::ChannelArgs& server_args)
+      : proxy_(grpc_end2end_proxy_create(&proxy_def_, client_args.ToC().get(),
+                                         server_args.ToC().get())) {}
+  ~SslProxyFixture() override { grpc_end2end_proxy_destroy(proxy_); }
 
-static void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
+ private:
+  static grpc_server* CreateProxyServer(const char* port,
+                                        const grpc_channel_args* server_args) {
+    grpc_server* s = grpc_server_create(server_args, nullptr);
+    grpc_slice cert_slice, key_slice;
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "load_file", grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
+    const char* server_cert =
+        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
+    const char* server_key =
+        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
+    grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
+    grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
+        nullptr, &pem_key_cert_pair, 1, 0, nullptr);
+    grpc_slice_unref(cert_slice);
+    grpc_slice_unref(key_slice);
+    GPR_ASSERT(grpc_server_add_http2_port(s, port, ssl_creds));
+    grpc_server_credentials_release(ssl_creds);
+    return s;
   }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(
-      f->server, grpc_end2end_proxy_get_server_port(ffd->proxy), server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
 
-void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  grpc_end2end_proxy_destroy(ffd->proxy);
-  gpr_free(ffd);
-}
-
-static void chttp2_init_client_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_channel_credentials* ssl_creds =
-      grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
-  grpc_arg ssl_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr")}};
-  const grpc_channel_args* new_client_args =
-      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
-  chttp2_init_client_secure_fullstack(f, new_client_args, ssl_creds);
-  {
-    grpc_core::ExecCtx exec_ctx;
-    grpc_channel_args_destroy(new_client_args);
-  }
-}
-
-static int fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
+  static grpc_channel* CreateProxyClient(const char* target,
+                                         const grpc_channel_args* client_args) {
+    grpc_channel* channel;
+    grpc_channel_credentials* ssl_creds =
+        grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
+    grpc_arg ssl_name_override = {
+        GRPC_ARG_STRING,
+        const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
+        {const_cast<char*>("foo.test.google.fr")}};
+    const grpc_channel_args* new_client_args =
+        grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
+    channel = grpc_channel_create(target, ssl_creds, new_client_args);
+    grpc_channel_credentials_release(ssl_creds);
+    {
+      grpc_core::ExecCtx exec_ctx;
+      grpc_channel_args_destroy(new_client_args);
     }
+    return channel;
   }
-  return 0;
-}
 
-static void chttp2_init_server_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_slice cert_slice, key_slice;
-  GPR_ASSERT(GRPC_LOG_IF_ERROR(
-      "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
-  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                               grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
-  const char* server_cert =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
-  const char* server_key =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
-  grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
-  grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
-      nullptr, &pem_key_cert_pair, 1, 0, nullptr);
-  grpc_slice_unref(cert_slice);
-  grpc_slice_unref(key_slice);
-  if (fail_server_auth_check(server_args)) {
-    grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
-                                              nullptr};
-    grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& args) override {
+    grpc_slice cert_slice, key_slice;
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "load_file", grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
+    const char* server_cert =
+        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
+    const char* server_key =
+        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
+    grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
+    grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
+        nullptr, &pem_key_cert_pair, 1, 0, nullptr);
+    grpc_slice_unref(cert_slice);
+    grpc_slice_unref(key_slice);
+    if (args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)) {
+      grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
+                                                nullptr};
+      grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
+    }
+
+    auto* server = grpc_server_create(args.ToC().get(), nullptr);
+    grpc_server_register_completion_queue(server, cq(), nullptr);
+    GPR_ASSERT(grpc_server_add_http2_port(
+        server, grpc_end2end_proxy_get_server_port(proxy_), ssl_creds));
+    grpc_server_credentials_release(ssl_creds);
+    grpc_server_start(server);
+    return server;
   }
-  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
-}
+
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& args) override {
+    grpc_channel_credentials* ssl_creds =
+        grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
+    auto* client = grpc_channel_create(
+        grpc_end2end_proxy_get_client_target(proxy_), ssl_creds,
+        args.Set(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, "foo.test.google.fr")
+            .ToC()
+            .get());
+    GPR_ASSERT(client != nullptr);
+    grpc_channel_credentials_release(ssl_creds);
+    return client;
+  }
+  const grpc_end2end_proxy_def proxy_def_ = {CreateProxyServer,
+                                             CreateProxyClient};
+  grpc_end2end_proxy* proxy_;
+};
 
 // All test configurations
 
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/simple_ssl_fullstack",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_REQUEST_PROXYING |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     "foo.test.google.fr", chttp2_create_fixture_secure_fullstack,
-     chttp2_init_client_simple_ssl_secure_fullstack,
-     chttp2_init_server_simple_ssl_secure_fullstack,
-     chttp2_tear_down_secure_fullstack},
+     "foo.test.google.fr",
+     [](const grpc_core::ChannelArgs& client_args,
+        const grpc_core::ChannelArgs& server_args) {
+       return std::make_unique<SslProxyFixture>(client_args, server_args);
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_ssl_tls12.cc b/test/core/end2end/fixtures/h2_ssl_tls12.cc
index 9f5079a..a50661a 100644
--- a/test/core/end2end/fixtures/h2_ssl_tls12.cc
+++ b/test/core/end2end/fixtures/h2_ssl_tls12.cc
@@ -18,187 +18,38 @@
 
 #include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
 #include <grpc/grpc_security_constants.h>
-#include <grpc/slice.h>
-#include <grpc/status.h>
-#include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/host_port.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/load_file.h"
-#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/h2_ssl_tls_common.h"
 #include "test/core/util/test_config.h"
 
-#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem"
-#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem"
-#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key"
-
-struct fullstack_secure_fixture_data {
-  std::string localaddr;
-  grpc_tls_version tls_version;
-};
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/, grpc_tls_version tls_version) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  ffd->tls_version = tls_version;
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_2(
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* server_args) {
-  return chttp2_create_fixture_secure_fullstack(client_args, server_args,
-                                                grpc_tls_version::TLS1_2);
-}
-
-static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
-                                 const grpc_metadata* /*md*/,
-                                 size_t /*md_count*/,
-                                 grpc_process_auth_metadata_done_cb cb,
-                                 void* user_data) {
-  GPR_ASSERT(state == nullptr);
-  cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
-}
-
-static void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-static void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
-static void chttp2_init_client_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_channel_credentials* ssl_creds =
-      grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_credentials* creds =
-        reinterpret_cast<grpc_ssl_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_arg ssl_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr")}};
-  const grpc_channel_args* new_client_args =
-      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
-  chttp2_init_client_secure_fullstack(f, new_client_args, ssl_creds);
-  grpc_channel_args_destroy(new_client_args);
-}
-
-static int fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-static void chttp2_init_server_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_slice cert_slice, key_slice;
-  GPR_ASSERT(GRPC_LOG_IF_ERROR(
-      "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
-  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                               grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
-  const char* server_cert =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
-  const char* server_key =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
-  grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
-  grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
-      nullptr, &pem_key_cert_pair, 1, 0, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_server_credentials* creds =
-        reinterpret_cast<grpc_ssl_server_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_slice_unref(cert_slice);
-  grpc_slice_unref(key_slice);
-  if (fail_server_auth_check(server_args)) {
-    grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
-                                              nullptr};
-    grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
-  }
-  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
-}
-
 // All test configurations
-
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/simple_ssl_fullstack_tls1_2",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_2,
-     chttp2_init_client_simple_ssl_secure_fullstack,
-     chttp2_init_server_simple_ssl_secure_fullstack,
-     chttp2_tear_down_secure_fullstack},
+     "foo.test.google.fr",
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<SslTlsFixture>(grpc_tls_version::TLS1_2);
+     }},
 };
 
 int main(int argc, char** argv) {
   size_t i;
   grpc::testing::TestEnvironment env(&argc, argv);
   grpc_end2end_tests_pre_init();
-  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, CA_CERT_PATH);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path,
+                        SslTlsFixture::CaCertPath());
 
   grpc_init();
 
diff --git a/test/core/end2end/fixtures/h2_ssl_tls13.cc b/test/core/end2end/fixtures/h2_ssl_tls13.cc
index ca7a00c..a68b2b8 100644
--- a/test/core/end2end/fixtures/h2_ssl_tls13.cc
+++ b/test/core/end2end/fixtures/h2_ssl_tls13.cc
@@ -18,188 +18,38 @@
 
 #include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
 #include <grpc/grpc_security_constants.h>
-#include <grpc/slice.h>
-#include <grpc/status.h>
-#include <grpc/support/log.h>
 
 #include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/host_port.h"
-#include "src/core/lib/iomgr/error.h"
-#include "src/core/lib/iomgr/load_file.h"
-#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/h2_ssl_tls_common.h"
 #include "test/core/util/test_config.h"
 
-#define CA_CERT_PATH "src/core/tsi/test_creds/ca.pem"
-#define SERVER_CERT_PATH "src/core/tsi/test_creds/server1.pem"
-#define SERVER_KEY_PATH "src/core/tsi/test_creds/server1.key"
-
-struct fullstack_secure_fixture_data {
-  std::string localaddr;
-  grpc_tls_version tls_version;
-};
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/, grpc_tls_version tls_version) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  ffd->tls_version = tls_version;
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack_tls1_3(
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* server_args) {
-  return chttp2_create_fixture_secure_fullstack(client_args, server_args,
-                                                grpc_tls_version::TLS1_3);
-}
-
-static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
-                                 const grpc_metadata* /*md*/,
-                                 size_t /*md_count*/,
-                                 grpc_process_auth_metadata_done_cb cb,
-                                 void* user_data) {
-  GPR_ASSERT(state == nullptr);
-  cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
-}
-
-static void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-static void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
-static void chttp2_init_client_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args) {
-  grpc_channel_credentials* ssl_creds =
-      grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_credentials* creds =
-        reinterpret_cast<grpc_ssl_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_arg ssl_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr")}};
-  const grpc_channel_args* new_client_args =
-      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
-  chttp2_init_client_secure_fullstack(f, new_client_args, ssl_creds);
-  grpc_channel_args_destroy(new_client_args);
-}
-
-static int fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-static void chttp2_init_server_simple_ssl_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args) {
-  grpc_slice cert_slice, key_slice;
-  GPR_ASSERT(GRPC_LOG_IF_ERROR(
-      "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
-  GPR_ASSERT(GRPC_LOG_IF_ERROR("load_file",
-                               grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
-  const char* server_cert =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
-  const char* server_key =
-      reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
-  grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
-  grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
-      nullptr, &pem_key_cert_pair, 1, 0, nullptr);
-  if (f != nullptr && ssl_creds != nullptr) {
-    // Set the min and max TLS version.
-    grpc_ssl_server_credentials* creds =
-        reinterpret_cast<grpc_ssl_server_credentials*>(ssl_creds);
-    fullstack_secure_fixture_data* ffd =
-        static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-    creds->set_min_tls_version(ffd->tls_version);
-    creds->set_max_tls_version(ffd->tls_version);
-  }
-  grpc_slice_unref(cert_slice);
-  grpc_slice_unref(key_slice);
-  if (fail_server_auth_check(server_args)) {
-    grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
-                                              nullptr};
-    grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
-  }
-  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
-}
-
 // All test configurations
-
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/simple_ssl_fullstack_tls1_3",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER |
          FEATURE_MASK_DOES_NOT_SUPPORT_CLIENT_HANDSHAKE_COMPLETE_FIRST,
-     "foo.test.google.fr", chttp2_create_fixture_secure_fullstack_tls1_3,
-     chttp2_init_client_simple_ssl_secure_fullstack,
-     chttp2_init_server_simple_ssl_secure_fullstack,
-     chttp2_tear_down_secure_fullstack},
-};
+     "foo.test.google.fr",
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       return std::make_unique<SslTlsFixture>(grpc_tls_version::TLS1_3);
+     }}};
 
 int main(int argc, char** argv) {
   size_t i;
   grpc::testing::TestEnvironment env(&argc, argv);
   grpc_end2end_tests_pre_init();
-  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path, CA_CERT_PATH);
+  GPR_GLOBAL_CONFIG_SET(grpc_default_ssl_roots_file_path,
+                        SslTlsFixture::CaCertPath());
 
   grpc_init();
 
diff --git a/test/core/end2end/fixtures/h2_ssl_tls_common.h b/test/core/end2end/fixtures/h2_ssl_tls_common.h
new file mode 100644
index 0000000..d8fcf98
--- /dev/null
+++ b/test/core/end2end/fixtures/h2_ssl_tls_common.h
@@ -0,0 +1,110 @@
+// Copyright 2023 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GRPC_TEST_CORE_END2END_FIXTURES_H2_SSL_TLS_COMMON_H
+#define GRPC_TEST_CORE_END2END_FIXTURES_H2_SSL_TLS_COMMON_H
+
+#include <string.h>
+
+#include <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+#include <grpc/grpc_security_constants.h>
+#include <grpc/slice.h>
+#include <grpc/status.h>
+#include <grpc/support/log.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/load_file.h"
+#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
+#include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
+
+class SslTlsFixture : public SecureFixture {
+ public:
+  explicit SslTlsFixture(grpc_tls_version tls_version)
+      : tls_version_(tls_version) {}
+
+  static const char* CaCertPath() { return "src/core/tsi/test_creds/ca.pem"; }
+  static const char* ServerCertPath() {
+    return "src/core/tsi/test_creds/server1.pem";
+  }
+  static const char* ServerKeyPath() {
+    return "src/core/tsi/test_creds/server1.key";
+  }
+
+ private:
+  grpc_core::ChannelArgs MutateClientArgs(
+      grpc_core::ChannelArgs args) override {
+    return args.Set(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, "foo.test.google.fr");
+  }
+
+  grpc_channel_credentials* MakeClientCreds(
+      const grpc_core::ChannelArgs&) override {
+    grpc_channel_credentials* ssl_creds =
+        grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
+    if (ssl_creds != nullptr) {
+      // Set the min and max TLS version.
+      grpc_ssl_credentials* creds =
+          reinterpret_cast<grpc_ssl_credentials*>(ssl_creds);
+      creds->set_min_tls_version(tls_version_);
+      creds->set_max_tls_version(tls_version_);
+    }
+    return ssl_creds;
+  }
+
+  grpc_server_credentials* MakeServerCreds(
+      const grpc_core::ChannelArgs& args) override {
+    grpc_slice cert_slice, key_slice;
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "load_file", grpc_load_file(ServerCertPath(), 1, &cert_slice)));
+    GPR_ASSERT(GRPC_LOG_IF_ERROR(
+        "load_file", grpc_load_file(ServerKeyPath(), 1, &key_slice)));
+    const char* server_cert =
+        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(cert_slice);
+    const char* server_key =
+        reinterpret_cast<const char*> GRPC_SLICE_START_PTR(key_slice);
+    grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {server_key, server_cert};
+    grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create(
+        nullptr, &pem_key_cert_pair, 1, 0, nullptr);
+    if (ssl_creds != nullptr) {
+      // Set the min and max TLS version.
+      grpc_ssl_server_credentials* creds =
+          reinterpret_cast<grpc_ssl_server_credentials*>(ssl_creds);
+      creds->set_min_tls_version(tls_version_);
+      creds->set_max_tls_version(tls_version_);
+    }
+    grpc_slice_unref(cert_slice);
+    grpc_slice_unref(key_slice);
+    if (args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)) {
+      grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
+                                                nullptr};
+      grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
+    }
+    return ssl_creds;
+  }
+
+  static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
+                                   const grpc_metadata* /*md*/,
+                                   size_t /*md_count*/,
+                                   grpc_process_auth_metadata_done_cb cb,
+                                   void* user_data) {
+    GPR_ASSERT(state == nullptr);
+    cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
+  }
+
+  grpc_tls_version tls_version_;
+};
+
+#endif  // GRPC_TEST_CORE_END2END_FIXTURES_H2_SSL_TLS_COMMON_H
diff --git a/test/core/end2end/fixtures/h2_tls_certwatch_async_tls1_3.cc b/test/core/end2end/fixtures/h2_tls_certwatch_async_tls1_3.cc
index 03a174f..f3065ae 100644
--- a/test/core/end2end/fixtures/h2_tls_certwatch_async_tls1_3.cc
+++ b/test/core/end2end/fixtures/h2_tls_certwatch_async_tls1_3.cc
@@ -16,48 +16,31 @@
 //
 //
 
-#include <string.h>
-
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/host_port.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/h2_tls_common.h"
-#include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-static grpc_end2end_test_fixture
-chttp2_create_fixture_async_verifier_cert_watcher(const grpc_channel_args*,
-                                                  const grpc_channel_args*) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  SetTlsVersion(ffd, SecurityPrimitives::TlsVersion::V_13);
-  SetCertificateProvider(ffd, SecurityPrimitives::ProviderType::FILE_PROVIDER);
-  SetCertificateVerifier(
-      ffd, SecurityPrimitives::VerifierType::EXTERNAL_ASYNC_VERIFIER);
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  return f;
-}
-
-static grpc_end2end_test_config config = {
+static CoreTestConfiguration config = {
     // client: certificate watcher provider + async external verifier
     // server: certificate watcher provider + async external verifier
     // extra: TLS 1.3
     "chttp2/cert_watcher_provider_async_verifier_tls1_3",
     kH2TLSFeatureMask,
     "foo.test.google.fr",
-    chttp2_create_fixture_async_verifier_cert_watcher,
-    chttp2_init_client,
-    chttp2_init_server,
-    chttp2_tear_down_secure_fullstack,
+    [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+      return std::make_unique<TlsFixture>(
+          SecurityPrimitives::TlsVersion::V_13,
+          SecurityPrimitives::ProviderType::FILE_PROVIDER,
+          SecurityPrimitives::VerifierType::EXTERNAL_ASYNC_VERIFIER);
+    },
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_tls_certwatch_sync_tls1_2.cc b/test/core/end2end/fixtures/h2_tls_certwatch_sync_tls1_2.cc
index 8944b69..e96e67e 100644
--- a/test/core/end2end/fixtures/h2_tls_certwatch_sync_tls1_2.cc
+++ b/test/core/end2end/fixtures/h2_tls_certwatch_sync_tls1_2.cc
@@ -16,48 +16,31 @@
 //
 //
 
-#include <string.h>
-
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/host_port.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/h2_tls_common.h"
-#include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-static grpc_end2end_test_fixture
-chttp2_create_fixture_hostname_verifier_cert_watcher(const grpc_channel_args*,
-                                                     const grpc_channel_args*) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  SetTlsVersion(ffd, SecurityPrimitives::TlsVersion::V_12);
-  SetCertificateProvider(ffd, SecurityPrimitives::ProviderType::FILE_PROVIDER);
-  SetCertificateVerifier(ffd,
-                         SecurityPrimitives::VerifierType::HOSTNAME_VERIFIER);
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  return f;
-}
-
-static grpc_end2end_test_config config = {
+static CoreTestConfiguration config = {
     // client: certificate watcher provider + hostname verifier
     // server: certificate watcher provider + sync external verifier
     // extra: TLS 1.2
     "chttp2/cert_watcher_provider_sync_verifier_tls1_2",
     kH2TLSFeatureMask,
     "foo.test.google.fr",
-    chttp2_create_fixture_hostname_verifier_cert_watcher,
-    chttp2_init_client,
-    chttp2_init_server,
-    chttp2_tear_down_secure_fullstack,
+    [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+      return std::make_unique<TlsFixture>(
+          SecurityPrimitives::TlsVersion::V_12,
+          SecurityPrimitives::ProviderType::FILE_PROVIDER,
+          SecurityPrimitives::VerifierType::HOSTNAME_VERIFIER);
+    },
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_tls_common.h b/test/core/end2end/fixtures/h2_tls_common.h
index 1dd8f57..f4e3821 100644
--- a/test/core/end2end/fixtures/h2_tls_common.h
+++ b/test/core/end2end/fixtures/h2_tls_common.h
@@ -39,6 +39,7 @@
 #include "src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h"
 #include "src/core/lib/slice/slice_internal.h"
 #include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/tls_utils.h"
 
 // For normal TLS connections.
@@ -56,120 +57,6 @@
   enum TlsVersion { V_12 = 0, V_13 = 1 } tls_version;
 };
 
-struct fullstack_secure_fixture_data {
-  ~fullstack_secure_fixture_data() {
-    grpc_tls_certificate_provider_release(client_provider);
-    grpc_tls_certificate_provider_release(server_provider);
-    grpc_tls_certificate_verifier_release(client_verifier);
-    grpc_tls_certificate_verifier_release(server_verifier);
-  }
-  std::string localaddr;
-  grpc_tls_version tls_version;
-  grpc_tls_certificate_provider* client_provider = nullptr;
-  grpc_tls_certificate_provider* server_provider = nullptr;
-  grpc_tls_certificate_verifier* client_verifier = nullptr;
-  grpc_tls_certificate_verifier* server_verifier = nullptr;
-  bool check_call_host = true;
-};
-
-inline void SetTlsVersion(fullstack_secure_fixture_data* ffd,
-                          SecurityPrimitives::TlsVersion tls_version) {
-  switch (tls_version) {
-    case SecurityPrimitives::TlsVersion::V_12: {
-      ffd->tls_version = grpc_tls_version::TLS1_2;
-      break;
-    }
-    case SecurityPrimitives::TlsVersion::V_13: {
-      ffd->tls_version = grpc_tls_version::TLS1_3;
-      break;
-    }
-  }
-}
-
-inline void SetCertificateProvider(
-    fullstack_secure_fixture_data* ffd,
-    SecurityPrimitives::ProviderType provider_type) {
-  switch (provider_type) {
-    case SecurityPrimitives::ProviderType::STATIC_PROVIDER: {
-      grpc_slice root_slice, cert_slice, key_slice;
-      GPR_ASSERT(GRPC_LOG_IF_ERROR(
-          "load_file", grpc_load_file(CA_CERT_PATH, 1, &root_slice)));
-      std::string root_cert =
-          std::string(grpc_core::StringViewFromSlice(root_slice));
-      GPR_ASSERT(GRPC_LOG_IF_ERROR(
-          "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
-      std::string identity_cert =
-          std::string(grpc_core::StringViewFromSlice(cert_slice));
-      GPR_ASSERT(GRPC_LOG_IF_ERROR(
-          "load_file", grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
-      std::string private_key =
-          std::string(grpc_core::StringViewFromSlice(key_slice));
-      grpc_tls_identity_pairs* client_pairs = grpc_tls_identity_pairs_create();
-      grpc_tls_identity_pairs_add_pair(client_pairs, private_key.c_str(),
-                                       identity_cert.c_str());
-      ffd->client_provider = grpc_tls_certificate_provider_static_data_create(
-          root_cert.c_str(), client_pairs);
-      grpc_tls_identity_pairs* server_pairs = grpc_tls_identity_pairs_create();
-      grpc_tls_identity_pairs_add_pair(server_pairs, private_key.c_str(),
-                                       identity_cert.c_str());
-      ffd->server_provider = grpc_tls_certificate_provider_static_data_create(
-          root_cert.c_str(), server_pairs);
-      grpc_slice_unref(root_slice);
-      grpc_slice_unref(cert_slice);
-      grpc_slice_unref(key_slice);
-      break;
-    }
-    case SecurityPrimitives::ProviderType::FILE_PROVIDER: {
-      ffd->client_provider = grpc_tls_certificate_provider_file_watcher_create(
-          SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1);
-      ffd->server_provider = grpc_tls_certificate_provider_file_watcher_create(
-          SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1);
-      break;
-    }
-  }
-}
-
-inline void SetCertificateVerifier(
-    fullstack_secure_fixture_data* ffd,
-    SecurityPrimitives::VerifierType verifier_type) {
-  switch (verifier_type) {
-    case SecurityPrimitives::VerifierType::EXTERNAL_SYNC_VERIFIER: {
-      auto* client_sync_verifier =
-          new grpc_core::testing::SyncExternalVerifier(true);
-      ffd->client_verifier = grpc_tls_certificate_verifier_external_create(
-          client_sync_verifier->base());
-      auto* server_sync_verifier =
-          new grpc_core::testing::SyncExternalVerifier(true);
-      ffd->server_verifier = grpc_tls_certificate_verifier_external_create(
-          server_sync_verifier->base());
-      ffd->check_call_host = false;
-      break;
-    }
-    case SecurityPrimitives::VerifierType::EXTERNAL_ASYNC_VERIFIER: {
-      auto* client_async_verifier =
-          new grpc_core::testing::AsyncExternalVerifier(true);
-      ffd->client_verifier = grpc_tls_certificate_verifier_external_create(
-          client_async_verifier->base());
-      auto* server_async_verifier =
-          new grpc_core::testing::AsyncExternalVerifier(true);
-      ffd->server_verifier = grpc_tls_certificate_verifier_external_create(
-          server_async_verifier->base());
-      ffd->check_call_host = false;
-      break;
-    }
-    case SecurityPrimitives::VerifierType::HOSTNAME_VERIFIER: {
-      ffd->client_verifier = grpc_tls_certificate_verifier_host_name_create();
-      // Hostname verifier couldn't be applied to the server side, so we will
-      // use sync external verifier here.
-      auto* server_async_verifier =
-          new grpc_core::testing::AsyncExternalVerifier(true);
-      ffd->server_verifier = grpc_tls_certificate_verifier_external_create(
-          server_async_verifier->base());
-      break;
-    }
-  }
-}
-
 inline void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
                                  const grpc_metadata* /*md*/,
                                  size_t /*md_count*/,
@@ -179,119 +66,167 @@
   cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
 }
 
-inline void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-inline void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-inline void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
-// Create a TLS channel credential.
-inline grpc_channel_credentials* create_tls_channel_credentials(
-    fullstack_secure_fixture_data* ffd) {
-  grpc_tls_credentials_options* options = grpc_tls_credentials_options_create();
-  grpc_tls_credentials_options_set_verify_server_cert(
-      options, 1 /* = verify server certs */);
-  options->set_min_tls_version(ffd->tls_version);
-  options->set_max_tls_version(ffd->tls_version);
-  // Set credential provider.
-  grpc_tls_credentials_options_set_certificate_provider(options,
-                                                        ffd->client_provider);
-  grpc_tls_credentials_options_watch_root_certs(options);
-  grpc_tls_credentials_options_watch_identity_key_cert_pairs(options);
-  // Set credential verifier.
-  grpc_tls_credentials_options_set_certificate_verifier(options,
-                                                        ffd->client_verifier);
-  grpc_tls_credentials_options_set_check_call_host(options,
-                                                   ffd->check_call_host);
-  // Create TLS channel credentials.
-  grpc_channel_credentials* creds = grpc_tls_credentials_create(options);
-  return creds;
-}
-
-// Create a TLS server credential.
-inline grpc_server_credentials* create_tls_server_credentials(
-    fullstack_secure_fixture_data* ffd) {
-  grpc_tls_credentials_options* options = grpc_tls_credentials_options_create();
-  options->set_min_tls_version(ffd->tls_version);
-  options->set_max_tls_version(ffd->tls_version);
-  // Set credential provider.
-  grpc_tls_credentials_options_set_certificate_provider(options,
-                                                        ffd->server_provider);
-  grpc_tls_credentials_options_watch_root_certs(options);
-  grpc_tls_credentials_options_watch_identity_key_cert_pairs(options);
-  // Set client certificate request type.
-  grpc_tls_credentials_options_set_cert_request_type(
-      options, GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY);
-  // Set credential verifier.
-  grpc_tls_credentials_options_set_certificate_verifier(options,
-                                                        ffd->server_verifier);
-  grpc_server_credentials* creds = grpc_tls_server_credentials_create(options);
-  return creds;
-}
-
-inline void chttp2_init_client(grpc_end2end_test_fixture* f,
-                               const grpc_channel_args* client_args) {
-  grpc_channel_credentials* ssl_creds = create_tls_channel_credentials(
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data));
-  grpc_arg ssl_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr")}};
-  const grpc_channel_args* new_client_args =
-      grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
-  chttp2_init_client_secure_fullstack(f, new_client_args, ssl_creds);
-  grpc_channel_args_destroy(new_client_args);
-}
-
-inline int fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
+class TlsFixture : public SecureFixture {
+ public:
+  TlsFixture(SecurityPrimitives::TlsVersion tls_version,
+             SecurityPrimitives::ProviderType provider_type,
+             SecurityPrimitives::VerifierType verifier_type) {
+    switch (tls_version) {
+      case SecurityPrimitives::TlsVersion::V_12: {
+        tls_version_ = grpc_tls_version::TLS1_2;
+        break;
+      }
+      case SecurityPrimitives::TlsVersion::V_13: {
+        tls_version_ = grpc_tls_version::TLS1_3;
+        break;
+      }
+    }
+    switch (provider_type) {
+      case SecurityPrimitives::ProviderType::STATIC_PROVIDER: {
+        grpc_slice root_slice, cert_slice, key_slice;
+        GPR_ASSERT(GRPC_LOG_IF_ERROR(
+            "load_file", grpc_load_file(CA_CERT_PATH, 1, &root_slice)));
+        std::string root_cert =
+            std::string(grpc_core::StringViewFromSlice(root_slice));
+        GPR_ASSERT(GRPC_LOG_IF_ERROR(
+            "load_file", grpc_load_file(SERVER_CERT_PATH, 1, &cert_slice)));
+        std::string identity_cert =
+            std::string(grpc_core::StringViewFromSlice(cert_slice));
+        GPR_ASSERT(GRPC_LOG_IF_ERROR(
+            "load_file", grpc_load_file(SERVER_KEY_PATH, 1, &key_slice)));
+        std::string private_key =
+            std::string(grpc_core::StringViewFromSlice(key_slice));
+        grpc_tls_identity_pairs* client_pairs =
+            grpc_tls_identity_pairs_create();
+        grpc_tls_identity_pairs_add_pair(client_pairs, private_key.c_str(),
+                                         identity_cert.c_str());
+        client_provider_ = grpc_tls_certificate_provider_static_data_create(
+            root_cert.c_str(), client_pairs);
+        grpc_tls_identity_pairs* server_pairs =
+            grpc_tls_identity_pairs_create();
+        grpc_tls_identity_pairs_add_pair(server_pairs, private_key.c_str(),
+                                         identity_cert.c_str());
+        server_provider_ = grpc_tls_certificate_provider_static_data_create(
+            root_cert.c_str(), server_pairs);
+        grpc_slice_unref(root_slice);
+        grpc_slice_unref(cert_slice);
+        grpc_slice_unref(key_slice);
+        break;
+      }
+      case SecurityPrimitives::ProviderType::FILE_PROVIDER: {
+        client_provider_ = grpc_tls_certificate_provider_file_watcher_create(
+            SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1);
+        server_provider_ = grpc_tls_certificate_provider_file_watcher_create(
+            SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1);
+        break;
+      }
+    }
+    switch (verifier_type) {
+      case SecurityPrimitives::VerifierType::EXTERNAL_SYNC_VERIFIER: {
+        auto* client_sync_verifier =
+            new grpc_core::testing::SyncExternalVerifier(true);
+        client_verifier_ = grpc_tls_certificate_verifier_external_create(
+            client_sync_verifier->base());
+        auto* server_sync_verifier =
+            new grpc_core::testing::SyncExternalVerifier(true);
+        server_verifier_ = grpc_tls_certificate_verifier_external_create(
+            server_sync_verifier->base());
+        check_call_host_ = false;
+        break;
+      }
+      case SecurityPrimitives::VerifierType::EXTERNAL_ASYNC_VERIFIER: {
+        auto* client_async_verifier =
+            new grpc_core::testing::AsyncExternalVerifier(true);
+        client_verifier_ = grpc_tls_certificate_verifier_external_create(
+            client_async_verifier->base());
+        auto* server_async_verifier =
+            new grpc_core::testing::AsyncExternalVerifier(true);
+        server_verifier_ = grpc_tls_certificate_verifier_external_create(
+            server_async_verifier->base());
+        check_call_host_ = false;
+        break;
+      }
+      case SecurityPrimitives::VerifierType::HOSTNAME_VERIFIER: {
+        client_verifier_ = grpc_tls_certificate_verifier_host_name_create();
+        // Hostname verifier couldn't be applied to the server side, so we will
+        // use sync external verifier here.
+        auto* server_async_verifier =
+            new grpc_core::testing::AsyncExternalVerifier(true);
+        server_verifier_ = grpc_tls_certificate_verifier_external_create(
+            server_async_verifier->base());
+        break;
+      }
     }
   }
-  return 0;
-}
-
-inline void chttp2_init_server(grpc_end2end_test_fixture* f,
-                               const grpc_channel_args* server_args) {
-  grpc_server_credentials* ssl_creds = create_tls_server_credentials(
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data));
-  if (fail_server_auth_check(server_args)) {
-    grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
-                                              nullptr};
-    grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
+  ~TlsFixture() override {
+    grpc_tls_certificate_provider_release(client_provider_);
+    grpc_tls_certificate_provider_release(server_provider_);
+    grpc_tls_certificate_verifier_release(client_verifier_);
+    grpc_tls_certificate_verifier_release(server_verifier_);
   }
-  chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
-}
+
+ private:
+  grpc_core::ChannelArgs MutateClientArgs(
+      grpc_core::ChannelArgs args) override {
+    return args.Set(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, "foo.test.google.fr");
+  }
+
+  grpc_channel_credentials* MakeClientCreds(
+      const grpc_core::ChannelArgs&) override {
+    grpc_tls_credentials_options* options =
+        grpc_tls_credentials_options_create();
+    grpc_tls_credentials_options_set_verify_server_cert(
+        options, 1 /* = verify server certs */);
+    options->set_min_tls_version(tls_version_);
+    options->set_max_tls_version(tls_version_);
+    // Set credential provider.
+    grpc_tls_credentials_options_set_certificate_provider(options,
+                                                          client_provider_);
+    grpc_tls_credentials_options_watch_root_certs(options);
+    grpc_tls_credentials_options_watch_identity_key_cert_pairs(options);
+    // Set credential verifier.
+    grpc_tls_credentials_options_set_certificate_verifier(options,
+                                                          client_verifier_);
+    grpc_tls_credentials_options_set_check_call_host(options, check_call_host_);
+    // Create TLS channel credentials.
+    grpc_channel_credentials* creds = grpc_tls_credentials_create(options);
+    return creds;
+  }
+
+  grpc_server_credentials* MakeServerCreds(
+      const grpc_core::ChannelArgs& args) override {
+    grpc_tls_credentials_options* options =
+        grpc_tls_credentials_options_create();
+    options->set_min_tls_version(tls_version_);
+    options->set_max_tls_version(tls_version_);
+    // Set credential provider.
+    grpc_tls_credentials_options_set_certificate_provider(options,
+                                                          server_provider_);
+    grpc_tls_credentials_options_watch_root_certs(options);
+    grpc_tls_credentials_options_watch_identity_key_cert_pairs(options);
+    // Set client certificate request type.
+    grpc_tls_credentials_options_set_cert_request_type(
+        options, GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY);
+    // Set credential verifier.
+    grpc_tls_credentials_options_set_certificate_verifier(options,
+                                                          server_verifier_);
+    grpc_server_credentials* creds =
+        grpc_tls_server_credentials_create(options);
+    if (args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)) {
+      grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
+                                                nullptr};
+      grpc_server_credentials_set_auth_metadata_processor(creds, processor);
+    }
+    return creds;
+  }
+
+  grpc_tls_version tls_version_;
+  grpc_tls_certificate_provider* client_provider_ = nullptr;
+  grpc_tls_certificate_provider* server_provider_ = nullptr;
+  grpc_tls_certificate_verifier* client_verifier_ = nullptr;
+  grpc_tls_certificate_verifier* server_verifier_ = nullptr;
+  bool check_call_host_ = true;
+};
 
 static const uint32_t kH2TLSFeatureMask =
     FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
diff --git a/test/core/end2end/fixtures/h2_tls_simple.cc b/test/core/end2end/fixtures/h2_tls_simple.cc
index 2ed0848..a6906c4 100644
--- a/test/core/end2end/fixtures/h2_tls_simple.cc
+++ b/test/core/end2end/fixtures/h2_tls_simple.cc
@@ -16,48 +16,31 @@
 //
 //
 
-#include <string.h>
-
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/host_port.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/h2_tls_common.h"
-#include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-static grpc_end2end_test_fixture chttp2_create_fixture_simple_fullstack(
-    const grpc_channel_args*, const grpc_channel_args*) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  SetTlsVersion(ffd, SecurityPrimitives::TlsVersion::V_12);
-  SetCertificateProvider(ffd,
-                         SecurityPrimitives::ProviderType::STATIC_PROVIDER);
-  SetCertificateVerifier(
-      ffd, SecurityPrimitives::VerifierType::EXTERNAL_SYNC_VERIFIER);
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  return f;
-}
-
-static grpc_end2end_test_config config = {
+static CoreTestConfiguration config = {
     // client: static data provider + sync external verifier
     // server: static data provider + sync external verifier
     // extra: TLS 1.2
     "chttp2/simple_ssl_fullstack",
     kH2TLSFeatureMask,
     "foo.test.google.fr",
-    chttp2_create_fixture_simple_fullstack,
-    chttp2_init_client,
-    chttp2_init_server,
-    chttp2_tear_down_secure_fullstack,
+    [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+      return std::make_unique<TlsFixture>(
+          SecurityPrimitives::TlsVersion::V_12,
+          SecurityPrimitives::ProviderType::STATIC_PROVIDER,
+          SecurityPrimitives::VerifierType::EXTERNAL_SYNC_VERIFIER);
+    },
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_tls_static_async_tls1_3.cc b/test/core/end2end/fixtures/h2_tls_static_async_tls1_3.cc
index e41b11a..b9e4a9d 100644
--- a/test/core/end2end/fixtures/h2_tls_static_async_tls1_3.cc
+++ b/test/core/end2end/fixtures/h2_tls_static_async_tls1_3.cc
@@ -16,48 +16,31 @@
 //
 //
 
-#include <string.h>
-
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
-#include "src/core/lib/gprpp/host_port.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/h2_tls_common.h"
-#include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-static grpc_end2end_test_fixture chttp2_create_fixture_async_verifier(
-    const grpc_channel_args*, const grpc_channel_args*) {
-  grpc_end2end_test_fixture f;
-  int port = grpc_pick_unused_port_or_die();
-  fullstack_secure_fixture_data* ffd = new fullstack_secure_fixture_data();
-  memset(&f, 0, sizeof(f));
-  ffd->localaddr = grpc_core::JoinHostPort("localhost", port);
-  SetTlsVersion(ffd, SecurityPrimitives::TlsVersion::V_13);
-  SetCertificateProvider(ffd,
-                         SecurityPrimitives::ProviderType::STATIC_PROVIDER);
-  SetCertificateVerifier(
-      ffd, SecurityPrimitives::VerifierType::EXTERNAL_ASYNC_VERIFIER);
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  return f;
-}
-
-static grpc_end2end_test_config config = {
+static CoreTestConfiguration config = {
     // client: static data provider + async external verifier
     // server: static data provider + async external verifier
     // extra: TLS 1.3
     "chttp2/static_provider_async_verifier_tls1_3",
     kH2TLSFeatureMask,
     "foo.test.google.fr",
-    chttp2_create_fixture_async_verifier,
-    chttp2_init_client,
-    chttp2_init_server,
-    chttp2_tear_down_secure_fullstack,
+    [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+      return std::make_unique<TlsFixture>(
+          SecurityPrimitives::TlsVersion::V_13,
+          SecurityPrimitives::ProviderType::STATIC_PROVIDER,
+          SecurityPrimitives::VerifierType::EXTERNAL_ASYNC_VERIFIER);
+    },
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_uds.cc b/test/core/end2end/fixtures/h2_uds.cc
index 77c05d0..0c49dd6 100644
--- a/test/core/end2end/fixtures/h2_uds.cc
+++ b/test/core/end2end/fixtures/h2_uds.cc
@@ -17,93 +17,39 @@
 //
 
 #include <inttypes.h>
-#include <string.h>
 #include <unistd.h>
 
 #include <atomic>
+#include <functional>
 #include <initializer_list>
-#include <string>
-#include <utility>
+#include <memory>
 
 #include "absl/strings/str_format.h"
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
-#include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_fixture_data {
-  std::string localaddr;
-};
-
 static std::atomic<int> unique{1};
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack_base(
-    std::string addr) {
-  fullstack_fixture_data* ffd = new fullstack_fixture_data;
-  ffd->localaddr = std::move(addr);
-
-  grpc_end2end_test_fixture f;
-  memset(&f, 0, sizeof(f));
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
-  const std::string localaddr = absl::StrFormat(
-      "unix:/tmp/grpc_fullstack_test.%d.%" PRId64 ".%" PRId32 ".%d", getpid(),
-      now.tv_sec, now.tv_nsec, unique++);
-  return chttp2_create_fixture_fullstack_base(localaddr);
-}
-
-void chttp2_init_client_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* client_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  grpc_channel_credentials_release(creds);
-}
-
-void chttp2_init_server_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* server_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack_uds",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack, chttp2_init_client_fullstack,
-     chttp2_init_server_fullstack, chttp2_tear_down_fullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
+       return std::make_unique<InsecureFixture>(absl::StrFormat(
+           "unix:/tmp/grpc_fullstack_test.%d.%" PRId64 ".%" PRId32 ".%d",
+           getpid(), now.tv_sec, now.tv_nsec,
+           unique.fetch_add(1, std::memory_order_relaxed)));
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/h2_uds_abstract.cc b/test/core/end2end/fixtures/h2_uds_abstract.cc
index 29148ae..62d066a 100644
--- a/test/core/end2end/fixtures/h2_uds_abstract.cc
+++ b/test/core/end2end/fixtures/h2_uds_abstract.cc
@@ -17,95 +17,39 @@
 //
 
 #include <inttypes.h>
-#include <string.h>
 #include <unistd.h>
 
 #include <atomic>
+#include <functional>
 #include <initializer_list>
-#include <string>
-#include <utility>
+#include <memory>
 
 #include "absl/strings/str_format.h"
 
 #include <grpc/grpc.h>
-#include <grpc/grpc_security.h>
-#include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/test_config.h"
 
-struct fullstack_fixture_data {
-  std::string localaddr;
-};
-
 static std::atomic<int> unique{1};
 
-static grpc_end2end_test_fixture chttp2_create_fixture_fullstack_base(
-    std::string addr) {
-  fullstack_fixture_data* ffd = new fullstack_fixture_data;
-  ffd->localaddr = std::move(addr);
-
-  grpc_end2end_test_fixture f;
-  memset(&f, 0, sizeof(f));
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-static grpc_end2end_test_fixture
-chttp2_create_fixture_fullstack_abstract_namespace(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
-  const std::string localaddr = absl::StrFormat(
-      "unix-abstract:grpc_fullstack_test.%d.%" PRId64 ".%" PRId32 ".%d",
-      getpid(), now.tv_sec, now.tv_nsec, unique++);
-  return chttp2_create_fixture_fullstack_base(localaddr);
-}
-
-void chttp2_init_client_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* client_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  grpc_channel_credentials_release(creds);
-}
-
-void chttp2_init_server_fullstack(grpc_end2end_test_fixture* f,
-                                  const grpc_channel_args* server_args) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_credentials* server_creds =
-      grpc_insecure_server_credentials_create();
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_fixture_data* ffd =
-      static_cast<fullstack_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
+static CoreTestConfiguration configs[] = {
     {"chttp2/fullstack_uds_abstract_namespace",
      FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
          FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL |
          FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
-     nullptr, chttp2_create_fixture_fullstack_abstract_namespace,
-     chttp2_init_client_fullstack, chttp2_init_server_fullstack,
-     chttp2_tear_down_fullstack},
+     nullptr,
+     [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+       gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
+       return std::make_unique<InsecureFixture>(absl::StrFormat(
+           "unix-abstract:grpc_fullstack_test.%d.%" PRId64 ".%" PRId32 ".%d",
+           getpid(), now.tv_sec, now.tv_nsec,
+           unique.fetch_add(1, std::memory_order_relaxed)));
+     }},
 };
 
 int main(int argc, char** argv) {
diff --git a/test/core/end2end/fixtures/inproc.cc b/test/core/end2end/fixtures/inproc.cc
index c8992ba..e17873d 100644
--- a/test/core/end2end/fixtures/inproc.cc
+++ b/test/core/end2end/fixtures/inproc.cc
@@ -18,59 +18,25 @@
 
 #include <string.h>
 
-#include <grpc/grpc.h>
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
+#include <functional>
+#include <memory>
 
-#include "src/core/ext/transport/inproc/inproc_transport.h"
+#include <grpc/grpc.h>
+
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
+#include "test/core/end2end/fixtures/inproc_fixture.h"
 #include "test/core/util/test_config.h"
 
-typedef struct inproc_fixture_data {
-  bool phony;  // reserved for future expansion. Struct can't be empty
-} inproc_fixture_data;
-
-static grpc_end2end_test_fixture inproc_create_fixture(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  inproc_fixture_data* ffd = static_cast<inproc_fixture_data*>(
-      gpr_malloc(sizeof(inproc_fixture_data)));
-  memset(&f, 0, sizeof(f));
-
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-
-  return f;
-}
-
-void inproc_init_client(grpc_end2end_test_fixture* f,
-                        const grpc_channel_args* client_args) {
-  f->client = grpc_inproc_channel_create(f->server, client_args, nullptr);
-  GPR_ASSERT(f->client);
-}
-
-void inproc_init_server(grpc_end2end_test_fixture* f,
-                        const grpc_channel_args* server_args) {
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_start(f->server);
-}
-
-void inproc_tear_down(grpc_end2end_test_fixture* f) {
-  inproc_fixture_data* ffd = static_cast<inproc_fixture_data*>(f->fixture_data);
-  gpr_free(ffd);
-}
-
 // All test configurations
-static grpc_end2end_test_config configs[] = {
-    {"inproc", FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, nullptr,
-     inproc_create_fixture, inproc_init_client, inproc_init_server,
-     inproc_tear_down},
-};
+static CoreTestConfiguration configs[] = {{
+    "inproc",
+    FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER,
+    nullptr,
+    [](const grpc_core::ChannelArgs&, const grpc_core::ChannelArgs&) {
+      return std::make_unique<InprocFixture>();
+    },
+}};
 
 int main(int argc, char** argv) {
   size_t i;
diff --git a/test/core/end2end/fixtures/inproc_fixture.h b/test/core/end2end/fixtures/inproc_fixture.h
new file mode 100644
index 0000000..4acdcdd
--- /dev/null
+++ b/test/core/end2end/fixtures/inproc_fixture.h
@@ -0,0 +1,37 @@
+// Copyright 2023 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GRPC_TEST_CORE_END2END_FIXTURES_INPROC_FIXTURE_H
+#define GRPC_TEST_CORE_END2END_FIXTURES_INPROC_FIXTURE_H
+
+#include <grpc/grpc.h>
+
+#include "src/core/ext/transport/inproc/inproc_transport.h"
+#include "src/core/lib/channel/channel_args.h"
+#include "test/core/end2end/end2end_tests.h"
+
+class InprocFixture : public CoreTestFixture {
+ private:
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& args) override {
+    auto* server = grpc_server_create(args.ToC().get(), nullptr);
+    grpc_server_register_completion_queue(server, cq(), nullptr);
+    grpc_server_start(server);
+    return server;
+  }
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& args) override {
+    return grpc_inproc_channel_create(server(), args.ToC().get(), nullptr);
+  }
+};
+
+#endif  // GRPC_TEST_CORE_END2END_FIXTURES_INPROC_FIXTURE_H
diff --git a/test/core/end2end/fixtures/local_util.cc b/test/core/end2end/fixtures/local_util.cc
index bff6fb6..0dcf965 100644
--- a/test/core/end2end/fixtures/local_util.cc
+++ b/test/core/end2end/fixtures/local_util.cc
@@ -20,48 +20,12 @@
 
 #include <string.h>
 
+#include <utility>
+
 #include <grpc/grpc_security.h>
 #include <grpc/status.h>
 #include <grpc/support/log.h>
 
-grpc_end2end_test_fixture grpc_end2end_local_chttp2_create_fixture_fullstack() {
-  grpc_end2end_test_fixture f;
-  grpc_end2end_local_fullstack_fixture_data* ffd =
-      new grpc_end2end_local_fullstack_fixture_data();
-  memset(&f, 0, sizeof(f));
-  f.fixture_data = ffd;
-  f.cq = grpc_completion_queue_create_for_next(nullptr);
-  return f;
-}
-
-void grpc_end2end_local_chttp2_init_client_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_local_connect_type type) {
-  grpc_channel_credentials* creds = grpc_local_credentials_create(type);
-  grpc_end2end_local_fullstack_fixture_data* ffd =
-      static_cast<grpc_end2end_local_fullstack_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-//
-// Check if server should fail auth check. If it is true, a different metadata
-// processor will be installed that always fails in processing client's
-// metadata.
-//
-static bool fail_server_auth_check(const grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return false;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return true;
-    }
-  }
-  return false;
-}
-
 static void process_auth_failure(void* state, grpc_auth_context* /*ctx*/,
                                  const grpc_metadata* /*md*/,
                                  size_t /*md_count*/,
@@ -71,31 +35,33 @@
   cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
 }
 
-void grpc_end2end_local_chttp2_init_server_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_local_connect_type type) {
-  grpc_server_credentials* creds = grpc_local_server_credentials_create(type);
-  grpc_end2end_local_fullstack_fixture_data* ffd =
-      static_cast<grpc_end2end_local_fullstack_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  if (fail_server_auth_check(server_args)) {
+LocalTestFixture::LocalTestFixture(std::string localaddr,
+                                   grpc_local_connect_type type)
+    : localaddr_(std::move(localaddr)), type_(type) {}
+
+grpc_server* LocalTestFixture::MakeServer(const grpc_core::ChannelArgs& args) {
+  grpc_server_credentials* server_creds =
+      grpc_local_server_credentials_create(type_);
+  auto* server = grpc_server_create(args.ToC().get(), nullptr);
+  grpc_server_register_completion_queue(server, cq(), nullptr);
+  if (args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)) {
     grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
                                               nullptr};
-    grpc_server_credentials_set_auth_metadata_processor(creds, processor);
+    grpc_server_credentials_set_auth_metadata_processor(server_creds,
+                                                        processor);
   }
   GPR_ASSERT(
-      grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(), creds));
-  grpc_server_credentials_release(creds);
-  grpc_server_start(f->server);
+      grpc_server_add_http2_port(server, localaddr_.c_str(), server_creds));
+  grpc_server_credentials_release(server_creds);
+  grpc_server_start(server);
+  return server;
 }
 
-void grpc_end2end_local_chttp2_tear_down_fullstack(
-    grpc_end2end_test_fixture* f) {
-  grpc_end2end_local_fullstack_fixture_data* ffd =
-      static_cast<grpc_end2end_local_fullstack_fixture_data*>(f->fixture_data);
-  delete ffd;
+grpc_channel* LocalTestFixture::MakeClient(const grpc_core::ChannelArgs& args) {
+  grpc_channel_credentials* creds = grpc_local_credentials_create(type_);
+  auto* client =
+      grpc_channel_create(localaddr_.c_str(), creds, args.ToC().get());
+  GPR_ASSERT(client != nullptr);
+  grpc_channel_credentials_release(creds);
+  return client;
 }
diff --git a/test/core/end2end/fixtures/local_util.h b/test/core/end2end/fixtures/local_util.h
index 80dd752..05a89e5 100644
--- a/test/core/end2end/fixtures/local_util.h
+++ b/test/core/end2end/fixtures/local_util.h
@@ -24,24 +24,19 @@
 #include <grpc/grpc.h>
 #include <grpc/grpc_security_constants.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
 
-struct grpc_end2end_local_fullstack_fixture_data {
-  std::string localaddr;
+class LocalTestFixture final : public CoreTestFixture {
+ public:
+  LocalTestFixture(std::string localaddr, grpc_local_connect_type type);
+
+ private:
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& args) override;
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& args) override;
+
+  std::string localaddr_;
+  grpc_local_connect_type type_;
 };
 
-// Utility functions shared by h2_local tests.
-grpc_end2end_test_fixture grpc_end2end_local_chttp2_create_fixture_fullstack();
-
-void grpc_end2end_local_chttp2_init_client_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* client_args,
-    grpc_local_connect_type type);
-
-void grpc_end2end_local_chttp2_init_server_fullstack(
-    grpc_end2end_test_fixture* f, const grpc_channel_args* server_args,
-    grpc_local_connect_type type);
-
-void grpc_end2end_local_chttp2_tear_down_fullstack(
-    grpc_end2end_test_fixture* f);
-
 #endif  // GRPC_TEST_CORE_END2END_FIXTURES_LOCAL_UTIL_H
diff --git a/test/core/end2end/fixtures/secure_fixture.h b/test/core/end2end/fixtures/secure_fixture.h
new file mode 100644
index 0000000..a64f563
--- /dev/null
+++ b/test/core/end2end/fixtures/secure_fixture.h
@@ -0,0 +1,91 @@
+// Copyright 2023 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GRPC_TEST_CORE_END2END_FIXTURES_SECURE_FIXTURE_H
+#define GRPC_TEST_CORE_END2END_FIXTURES_SECURE_FIXTURE_H
+
+#include <string>
+#include <utility>
+
+#include <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+#include <grpc/support/log.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gprpp/host_port.h"
+#include "test/core/end2end/end2end_tests.h"
+#include "test/core/util/port.h"
+
+// Base class for a fixture that just needs to select cred types (or mutate
+// client/server channel args).
+class SecureFixture : public CoreTestFixture {
+ public:
+  explicit SecureFixture(std::string localaddr = grpc_core::JoinHostPort(
+                             "localhost", grpc_pick_unused_port_or_die()))
+      : localaddr_(std::move(localaddr)) {}
+
+ protected:
+  const std::string& localaddr() const { return localaddr_; }
+
+ private:
+  virtual grpc_channel_credentials* MakeClientCreds(
+      const grpc_core::ChannelArgs& args) = 0;
+  virtual grpc_server_credentials* MakeServerCreds(
+      const grpc_core::ChannelArgs& args) = 0;
+  virtual grpc_core::ChannelArgs MutateClientArgs(grpc_core::ChannelArgs args) {
+    return args;
+  }
+  virtual grpc_core::ChannelArgs MutateServerArgs(grpc_core::ChannelArgs args) {
+    return args;
+  }
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& in_args) override {
+    auto args = MutateServerArgs(in_args);
+    auto* creds = MakeServerCreds(args);
+    auto* server = grpc_server_create(args.ToC().get(), nullptr);
+    grpc_server_register_completion_queue(server, cq(), nullptr);
+    GPR_ASSERT(grpc_server_add_http2_port(server, localaddr_.c_str(), creds));
+    grpc_server_credentials_release(creds);
+    grpc_server_start(server);
+    return server;
+  }
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& in_args) override {
+    auto args = MutateClientArgs(in_args);
+    auto* creds = MakeClientCreds(args);
+    auto* client =
+        grpc_channel_create(localaddr_.c_str(), creds, args.ToC().get());
+    GPR_ASSERT(client != nullptr);
+    grpc_channel_credentials_release(creds);
+    return client;
+  }
+
+  std::string localaddr_;
+};
+
+// Fixture that uses insecure credentials.
+class InsecureFixture : public SecureFixture {
+ public:
+  using SecureFixture::SecureFixture;
+
+ private:
+  grpc_channel_credentials* MakeClientCreds(
+      const grpc_core::ChannelArgs&) override {
+    return grpc_insecure_credentials_create();
+  }
+  grpc_server_credentials* MakeServerCreds(
+      const grpc_core::ChannelArgs&) override {
+    return grpc_insecure_server_credentials_create();
+  }
+};
+
+#endif  // GRPC_TEST_CORE_END2END_FIXTURES_SECURE_FIXTURE_H
diff --git a/test/core/end2end/fixtures/sockpair_fixture.h b/test/core/end2end/fixtures/sockpair_fixture.h
new file mode 100644
index 0000000..d1618ce
--- /dev/null
+++ b/test/core/end2end/fixtures/sockpair_fixture.h
@@ -0,0 +1,108 @@
+// Copyright 2023 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GRPC_TEST_CORE_END2END_FIXTURES_SOCKPAIR_FIXTURE_H
+#define GRPC_TEST_CORE_END2END_FIXTURES_SOCKPAIR_FIXTURE_H
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+
+#include <grpc/grpc.h>
+#include <grpc/status.h>
+#include <grpc/support/log.h>
+
+#include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/channel/channel_args_preconditioning.h"
+#include "src/core/lib/channel/channelz.h"
+#include "src/core/lib/config/core_configuration.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/iomgr/endpoint.h"
+#include "src/core/lib/iomgr/endpoint_pair.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/exec_ctx.h"
+#include "src/core/lib/surface/channel.h"
+#include "src/core/lib/surface/channel_stack_type.h"
+#include "src/core/lib/surface/completion_queue.h"
+#include "src/core/lib/surface/server.h"
+#include "src/core/lib/transport/transport.h"
+#include "src/core/lib/transport/transport_fwd.h"
+#include "test/core/end2end/end2end_tests.h"
+
+class SockpairFixture : public CoreTestFixture {
+ public:
+  explicit SockpairFixture(const grpc_core::ChannelArgs& ep_args)
+      : ep_(grpc_iomgr_create_endpoint_pair("fixture", ep_args.ToC().get())) {}
+
+ private:
+  virtual grpc_core::ChannelArgs MutateClientArgs(grpc_core::ChannelArgs args) {
+    return args;
+  }
+  virtual grpc_core::ChannelArgs MutateServerArgs(grpc_core::ChannelArgs args) {
+    return args;
+  }
+  grpc_server* MakeServer(const grpc_core::ChannelArgs& in_args) override {
+    auto args = MutateServerArgs(in_args);
+    grpc_core::ExecCtx exec_ctx;
+    grpc_transport* transport;
+    auto* server = grpc_server_create(args.ToC().get(), nullptr);
+    grpc_server_register_completion_queue(server, cq(), nullptr);
+    grpc_server_start(server);
+    auto server_channel_args = grpc_core::CoreConfiguration::Get()
+                                   .channel_args_preconditioning()
+                                   .PreconditionChannelArgs(args.ToC().get());
+    transport =
+        grpc_create_chttp2_transport(server_channel_args, ep_.server, false);
+    grpc_endpoint_add_to_pollset(ep_.server, grpc_cq_pollset(cq()));
+    grpc_core::Server* core_server = grpc_core::Server::FromC(server);
+    grpc_error_handle error = core_server->SetupTransport(
+        transport, nullptr, core_server->channel_args(), nullptr);
+    if (error.ok()) {
+      grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
+    } else {
+      grpc_transport_destroy(transport);
+    }
+    return server;
+  }
+  grpc_channel* MakeClient(const grpc_core::ChannelArgs& in_args) override {
+    grpc_core::ExecCtx exec_ctx;
+    auto args = grpc_core::CoreConfiguration::Get()
+                    .channel_args_preconditioning()
+                    .PreconditionChannelArgs(
+                        MutateClientArgs(in_args)
+                            .Set(GRPC_ARG_DEFAULT_AUTHORITY, "test-authority")
+                            .ToC()
+                            .get());
+    grpc_transport* transport;
+    transport = grpc_create_chttp2_transport(args, ep_.client, true);
+    auto channel = grpc_core::Channel::Create(
+        "socketpair-target", args, GRPC_CLIENT_DIRECT_CHANNEL, transport);
+    grpc_channel* client;
+    if (channel.ok()) {
+      client = channel->release()->c_ptr();
+      grpc_chttp2_transport_start_reading(transport, nullptr, nullptr, nullptr);
+    } else {
+      client = grpc_lame_client_channel_create(
+          nullptr, static_cast<grpc_status_code>(channel.status().code()),
+          "lame channel");
+      grpc_transport_destroy(transport);
+    }
+    GPR_ASSERT(client);
+    return client;
+  }
+
+  grpc_endpoint_pair ep_;
+};
+
+#endif  // GRPC_TEST_CORE_END2END_FIXTURES_SOCKPAIR_FIXTURE_H
diff --git a/test/core/end2end/generate_tests.bzl b/test/core/end2end/generate_tests.bzl
index f833fbc..8e58d0f 100755
--- a/test/core/end2end/generate_tests.bzl
+++ b/test/core/end2end/generate_tests.bzl
@@ -447,7 +447,7 @@
             ":ssl_test_data",
             ":http_proxy",
             ":proxy",
-            ":local_util",
+            ":fixture_support",
             "//test/core/util:test_lb_policies",
             "//:grpc_authorization_provider",
             "//:grpc_http_filters",
diff --git a/test/core/end2end/goaway_server_test.cc b/test/core/end2end/goaway_server_test.cc
index 1a5d8c6..9b5c00d 100644
--- a/test/core/end2end/goaway_server_test.cc
+++ b/test/core/end2end/goaway_server_test.cc
@@ -60,8 +60,6 @@
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 static gpr_mu g_mu;
 static int g_resolve_port = -1;
 
@@ -283,9 +281,9 @@
   op->flags = GRPC_INITIAL_METADATA_WAIT_FOR_READY;
   op->reserved = nullptr;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(call1, ops,
-                                                   (size_t)(op - ops),
-                                                   tag(0x101), nullptr));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(call1, ops, (size_t)(op - ops),
+                                   grpc_core::CqVerifier::tag(0x101), nullptr));
   // and receive status to probe termination
   memset(ops, 0, sizeof(ops));
   op = ops;
@@ -296,9 +294,9 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(call1, ops,
-                                                   (size_t)(op - ops),
-                                                   tag(0x102), nullptr));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(call1, ops, (size_t)(op - ops),
+                                   grpc_core::CqVerifier::tag(0x102), nullptr));
 
   // bring a server up on the first port
   grpc_server* server1 = grpc_server_create(nullptr, nullptr);
@@ -314,20 +312,21 @@
   grpc_call* server_call1;
   GPR_ASSERT(GRPC_CALL_OK ==
              grpc_server_request_call(server1, &server_call1, &request_details1,
-                                      &request_metadata1, cq, cq, tag(0x301)));
+                                      &request_metadata1, cq, cq,
+                                      grpc_core::CqVerifier::tag(0x301)));
 
   set_resolve_port(port1);
 
   // first call should now start
-  cqv.Expect(tag(0x101), true);
-  cqv.Expect(tag(0x301), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0x101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0x301), true);
   cqv.Verify();
 
   GPR_ASSERT(GRPC_CHANNEL_READY ==
              grpc_channel_check_connectivity_state(chan, 0));
   grpc_channel_watch_connectivity_state(chan, GRPC_CHANNEL_READY,
                                         gpr_inf_future(GPR_CLOCK_REALTIME), cq,
-                                        tag(0x9999));
+                                        grpc_core::CqVerifier::tag(0x9999));
 
   // listen for close on the server call to probe for finishing
   memset(ops, 0, sizeof(ops));
@@ -336,15 +335,16 @@
   op->data.recv_close_on_server.cancelled = &was_cancelled1;
   op->flags = 0;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(server_call1, ops,
-                                                   (size_t)(op - ops),
-                                                   tag(0x302), nullptr));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(server_call1, ops, (size_t)(op - ops),
+                                   grpc_core::CqVerifier::tag(0x302), nullptr));
 
   // shutdown first server:
   // we should see a connectivity change and then nothing
   set_resolve_port(-1);
-  grpc_server_shutdown_and_notify(server1, cq, tag(0xdead1));
-  cqv.Expect(tag(0x9999), true);
+  grpc_server_shutdown_and_notify(server1, cq,
+                                  grpc_core::CqVerifier::tag(0xdead1));
+  cqv.Expect(grpc_core::CqVerifier::tag(0x9999), true);
   cqv.Verify();
   cqv.VerifyEmpty();
 
@@ -361,9 +361,9 @@
   op->flags = GRPC_INITIAL_METADATA_WAIT_FOR_READY;
   op->reserved = nullptr;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(call2, ops,
-                                                   (size_t)(op - ops),
-                                                   tag(0x201), nullptr));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(call2, ops, (size_t)(op - ops),
+                                   grpc_core::CqVerifier::tag(0x201), nullptr));
   // and receive status to probe termination
   memset(ops, 0, sizeof(ops));
   op = ops;
@@ -374,9 +374,9 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(call2, ops,
-                                                   (size_t)(op - ops),
-                                                   tag(0x202), nullptr));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(call2, ops, (size_t)(op - ops),
+                                   grpc_core::CqVerifier::tag(0x202), nullptr));
 
   // and bring up second server
   set_resolve_port(port2);
@@ -393,11 +393,12 @@
   grpc_call* server_call2;
   GPR_ASSERT(GRPC_CALL_OK ==
              grpc_server_request_call(server2, &server_call2, &request_details2,
-                                      &request_metadata2, cq, cq, tag(0x401)));
+                                      &request_metadata2, cq, cq,
+                                      grpc_core::CqVerifier::tag(0x401)));
 
   // second call should now start
-  cqv.Expect(tag(0x201), true);
-  cqv.Expect(tag(0x401), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0x201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0x401), true);
   cqv.Verify();
 
   // listen for close on the server call to probe for finishing
@@ -407,24 +408,25 @@
   op->data.recv_close_on_server.cancelled = &was_cancelled2;
   op->flags = 0;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(server_call2, ops,
-                                                   (size_t)(op - ops),
-                                                   tag(0x402), nullptr));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(server_call2, ops, (size_t)(op - ops),
+                                   grpc_core::CqVerifier::tag(0x402), nullptr));
 
   // shutdown second server: we should see nothing
-  grpc_server_shutdown_and_notify(server2, cq, tag(0xdead2));
+  grpc_server_shutdown_and_notify(server2, cq,
+                                  grpc_core::CqVerifier::tag(0xdead2));
   cqv.VerifyEmpty();
 
   grpc_call_cancel(call1, nullptr);
   grpc_call_cancel(call2, nullptr);
 
   // now everything else should finish
-  cqv.Expect(tag(0x102), true);
-  cqv.Expect(tag(0x202), true);
-  cqv.Expect(tag(0x302), true);
-  cqv.Expect(tag(0x402), true);
-  cqv.Expect(tag(0xdead1), true);
-  cqv.Expect(tag(0xdead2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0x102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0x202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0x302), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0x402), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0xdead1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0xdead2), true);
   cqv.Verify();
 
   grpc_call_unref(call1);
diff --git a/test/core/end2end/h2_ssl_cert_test.cc b/test/core/end2end/h2_ssl_cert_test.cc
index dc0fada..7ce39c8 100644
--- a/test/core/end2end/h2_ssl_cert_test.cc
+++ b/test/core/end2end/h2_ssl_cert_test.cc
@@ -19,6 +19,10 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+#include <string>
+
 #include <gtest/gtest.h>
 #include <openssl/crypto.h>
 
@@ -30,13 +34,14 @@
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/tmpfile.h"
 #include "src/core/lib/gprpp/crash.h"
+#include "src/core/lib/gprpp/global_config_generic.h"
 #include "src/core/lib/gprpp/host_port.h"
 #include "src/core/lib/security/credentials/credentials.h"
 #include "src/core/lib/security/security_connector/ssl_utils_config.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/data/ssl_test_data.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/port.h"
+#include "test/core/end2end/fixtures/secure_fixture.h"
 #include "test/core/util/test_config.h"
 
 static std::string test_server1_key_id;
@@ -71,157 +76,100 @@
   cb(user_data, nullptr, 0, nullptr, 0, GRPC_STATUS_UNAUTHENTICATED, nullptr);
 }
 
-static void chttp2_init_client_secure_fullstack(
-    grpc_end2end_test_fixture* f, grpc_channel_args* client_args,
-    grpc_channel_credentials* creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  f->client = grpc_channel_create(ffd->localaddr.c_str(), creds, client_args);
-  GPR_ASSERT(f->client != nullptr);
-  grpc_channel_credentials_release(creds);
-}
-
-static void chttp2_init_server_secure_fullstack(
-    grpc_end2end_test_fixture* f, grpc_channel_args* server_args,
-    grpc_server_credentials* server_creds) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  GPR_ASSERT(grpc_server_add_http2_port(f->server, ffd->localaddr.c_str(),
-                                        server_creds));
-  grpc_server_credentials_release(server_creds);
-  grpc_server_start(f->server);
-}
-
-void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture* f) {
-  fullstack_secure_fixture_data* ffd =
-      static_cast<fullstack_secure_fixture_data*>(f->fixture_data);
-  delete ffd;
-}
-
-static int fail_server_auth_check(grpc_channel_args* server_args) {
-  size_t i;
-  if (server_args == nullptr) return 0;
-  for (i = 0; i < server_args->num_args; i++) {
-    if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
-        0) {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-#define SERVER_INIT_NAME(REQUEST_TYPE) \
-  chttp2_init_server_simple_ssl_secure_fullstack_##REQUEST_TYPE
-
-#define SERVER_INIT(REQUEST_TYPE)                                           \
-  static void SERVER_INIT_NAME(REQUEST_TYPE)(                               \
-      grpc_end2end_test_fixture * f, grpc_channel_args * server_args) {     \
-    grpc_ssl_pem_key_cert_pair pem_cert_key_pair;                           \
-    if (!test_server1_key_id.empty()) {                                     \
-      pem_cert_key_pair.private_key = test_server1_key_id.c_str();          \
-      pem_cert_key_pair.cert_chain = test_server1_cert;                     \
-    } else {                                                                \
-      pem_cert_key_pair.private_key = test_server1_key;                     \
-      pem_cert_key_pair.cert_chain = test_server1_cert;                     \
-    }                                                                       \
-    grpc_server_credentials* ssl_creds =                                    \
-        grpc_ssl_server_credentials_create_ex(                              \
-            test_root_cert, &pem_cert_key_pair, 1, REQUEST_TYPE, NULL);     \
-    if (fail_server_auth_check(server_args)) {                              \
-      grpc_auth_metadata_processor processor = {process_auth_failure, NULL, \
-                                                NULL};                      \
-      grpc_server_credentials_set_auth_metadata_processor(ssl_creds,        \
-                                                          processor);       \
-    }                                                                       \
-    chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);         \
-  }
-
-SERVER_INIT(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE)
-SERVER_INIT(GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY)
-SERVER_INIT(GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY)
-SERVER_INIT(GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_BUT_DONT_VERIFY)
-SERVER_INIT(GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY)
-
-#define CLIENT_INIT_NAME(cert_type) \
-  chttp2_init_client_simple_ssl_secure_fullstack_##cert_type
-
 typedef enum { NONE, SELF_SIGNED, SIGNED, BAD_CERT_PAIR } certtype;
 
-#define CLIENT_INIT(cert_type)                                               \
-  static void CLIENT_INIT_NAME(cert_type)(grpc_end2end_test_fixture * f,     \
-                                          grpc_channel_args * client_args) { \
-    grpc_channel_credentials* ssl_creds = NULL;                              \
-    grpc_ssl_pem_key_cert_pair self_signed_client_key_cert_pair = {          \
-        test_self_signed_client_key, test_self_signed_client_cert};          \
-    grpc_ssl_pem_key_cert_pair signed_client_key_cert_pair = {               \
-        test_signed_client_key, test_signed_client_cert};                    \
-    grpc_ssl_pem_key_cert_pair bad_client_key_cert_pair = {                  \
-        test_self_signed_client_key, test_signed_client_cert};               \
-    grpc_ssl_pem_key_cert_pair* key_cert_pair = NULL;                        \
-    switch (cert_type) {                                                     \
-      case SELF_SIGNED:                                                      \
-        key_cert_pair = &self_signed_client_key_cert_pair;                   \
-        break;                                                               \
-      case SIGNED:                                                           \
-        key_cert_pair = &signed_client_key_cert_pair;                        \
-        break;                                                               \
-      case BAD_CERT_PAIR:                                                    \
-        key_cert_pair = &bad_client_key_cert_pair;                           \
-        break;                                                               \
-      default:                                                               \
-        break;                                                               \
-    }                                                                        \
-    ssl_creds = grpc_ssl_credentials_create(test_root_cert, key_cert_pair,   \
-                                            NULL, NULL);                     \
-    grpc_arg ssl_name_override = {                                           \
-        GRPC_ARG_STRING,                                                     \
-        const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),                \
-        {const_cast<char*>("foo.test.google.fr")}};                          \
-    grpc_channel_args* new_client_args =                                     \
-        grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);  \
-    chttp2_init_client_secure_fullstack(f, new_client_args, ssl_creds);      \
-    {                                                                        \
-      grpc_core::ExecCtx exec_ctx;                                           \
-      grpc_channel_args_destroy(new_client_args);                            \
-    }                                                                        \
+class TestFixture : public SecureFixture {
+ public:
+  TestFixture(grpc_ssl_client_certificate_request_type request_type,
+              certtype cert_type)
+      : request_type_(request_type), cert_type_(cert_type) {}
+
+  static auto MakeFactory(grpc_ssl_client_certificate_request_type request_type,
+                          certtype cert_type) {
+    return [request_type, cert_type](const grpc_core::ChannelArgs&,
+                                     const grpc_core::ChannelArgs&) {
+      return std::make_unique<TestFixture>(request_type, cert_type);
+    };
   }
 
-CLIENT_INIT(NONE)
-CLIENT_INIT(SELF_SIGNED)
-CLIENT_INIT(SIGNED)
-CLIENT_INIT(BAD_CERT_PAIR)
+ private:
+  grpc_core::ChannelArgs MutateClientArgs(
+      grpc_core::ChannelArgs args) override {
+    return args.Set(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, "foo.test.google.fr");
+  }
+
+  grpc_server_credentials* MakeServerCreds(
+      const grpc_core::ChannelArgs& args) override {
+    grpc_ssl_pem_key_cert_pair pem_cert_key_pair;
+    if (!test_server1_key_id.empty()) {
+      pem_cert_key_pair.private_key = test_server1_key_id.c_str();
+      pem_cert_key_pair.cert_chain = test_server1_cert;
+    } else {
+      pem_cert_key_pair.private_key = test_server1_key;
+      pem_cert_key_pair.cert_chain = test_server1_cert;
+    }
+    grpc_server_credentials* ssl_creds = grpc_ssl_server_credentials_create_ex(
+        test_root_cert, &pem_cert_key_pair, 1, request_type_, nullptr);
+    if (args.Contains(FAIL_AUTH_CHECK_SERVER_ARG_NAME)) {
+      grpc_auth_metadata_processor processor = {process_auth_failure, nullptr,
+                                                nullptr};
+      grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
+    }
+    return ssl_creds;
+  }
+
+  grpc_channel_credentials* MakeClientCreds(
+      const grpc_core::ChannelArgs&) override {
+    grpc_ssl_pem_key_cert_pair self_signed_client_key_cert_pair = {
+        test_self_signed_client_key, test_self_signed_client_cert};
+    grpc_ssl_pem_key_cert_pair signed_client_key_cert_pair = {
+        test_signed_client_key, test_signed_client_cert};
+    grpc_ssl_pem_key_cert_pair bad_client_key_cert_pair = {
+        test_self_signed_client_key, test_signed_client_cert};
+    grpc_ssl_pem_key_cert_pair* key_cert_pair = nullptr;
+    switch (cert_type_) {
+      case SELF_SIGNED:
+        key_cert_pair = &self_signed_client_key_cert_pair;
+        break;
+      case SIGNED:
+        key_cert_pair = &signed_client_key_cert_pair;
+        break;
+      case BAD_CERT_PAIR:
+        key_cert_pair = &bad_client_key_cert_pair;
+        break;
+      default:
+        break;
+    }
+    return grpc_ssl_credentials_create(test_root_cert, key_cert_pair, nullptr,
+                                       nullptr);
+  }
+
+  grpc_ssl_client_certificate_request_type request_type_;
+  certtype cert_type_;
+};
 
 #define TEST_NAME(enum_name, cert_type, result) \
   "chttp2/ssl_" #enum_name "_" #cert_type "_" #result "_"
 
 typedef enum { SUCCESS, FAIL } test_result;
 
-#define SSL_TEST(request_type, cert_type, result)     \
-  {                                                   \
-    {TEST_NAME(request_type, cert_type, result),      \
-     FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |       \
-         FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS | \
-         FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL,        \
-     "foo.test.google.fr",                            \
-     chttp2_create_fixture_secure_fullstack,          \
-     CLIENT_INIT_NAME(cert_type),                     \
-     SERVER_INIT_NAME(request_type),                  \
-     chttp2_tear_down_secure_fullstack},              \
-        result                                        \
+#define SSL_TEST(request_type, cert_type, result)                              \
+  {                                                                            \
+    {TEST_NAME(request_type, cert_type, result),                               \
+     FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |                                \
+         FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS |                          \
+         FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL,                                 \
+     "foo.test.google.fr", TestFixture::MakeFactory(request_type, cert_type)}, \
+        result                                                                 \
   }
 
 // All test configurations
-typedef struct grpc_end2end_test_config_wrapper {
-  grpc_end2end_test_config config;
+struct CoreTestConfigWrapper {
+  CoreTestConfiguration config;
   test_result result;
-} grpc_end2end_test_config_wrapper;
+};
 
-static grpc_end2end_test_config_wrapper configs[] = {
+static CoreTestConfigWrapper configs[] = {
     SSL_TEST(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE, NONE, SUCCESS),
     SSL_TEST(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE, SELF_SIGNED, SUCCESS),
     SSL_TEST(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE, SIGNED, SUCCESS),
@@ -261,57 +209,19 @@
              BAD_CERT_PAIR, FAIL),
 };
 
-static void* tag(intptr_t t) { return (void*)t; }
-
-static gpr_timespec n_seconds_time(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_time(void) { return n_seconds_time(5); }
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_time(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-// Shuts down the server.
-// Side effect - Also shuts down and drains the completion queue.
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_client(f);
-  shutdown_server(f);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_fixture f,
+static void simple_request_body(CoreTestFixture* f,
                                 test_result expected_result) {
   grpc_call* c;
-  gpr_timespec deadline = five_seconds_time();
-  grpc_core::CqVerifier cqv(f.cq);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_call_error error;
 
   grpc_slice host = grpc_slice_from_static_string("foo.test.google.fr:1234");
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), &host,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               &host, deadline, nullptr);
   GPR_ASSERT(c);
 
   memset(ops, 0, sizeof(ops));
@@ -321,37 +231,34 @@
   op->flags = GRPC_INITIAL_METADATA_WAIT_FOR_READY;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  CQ_EXPECT_COMPLETION(cqv, tag(1), expected_result == SUCCESS);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), expected_result == SUCCESS);
   cqv.Verify();
 
   grpc_call_unref(c);
 }
 
-class H2SslCertTest
-    : public ::testing::TestWithParam<grpc_end2end_test_config_wrapper> {
+class H2SslCertTest : public ::testing::TestWithParam<CoreTestConfigWrapper> {
  protected:
   H2SslCertTest() {
     gpr_log(GPR_INFO, "SSL_CERT_tests/%s", GetParam().config.name);
   }
   void SetUp() override {
-    fixture_ = GetParam().config.create_fixture(nullptr, nullptr);
-    GetParam().config.init_server(&fixture_, nullptr);
-    GetParam().config.init_client(&fixture_, nullptr);
+    fixture_ = GetParam().config.create_fixture(grpc_core::ChannelArgs(),
+                                                grpc_core::ChannelArgs());
+    fixture_->InitServer(grpc_core::ChannelArgs());
+    fixture_->InitClient(grpc_core::ChannelArgs());
   }
-  void TearDown() override {
-    end_test(&fixture_);
-    GetParam().config.tear_down_data(&fixture_);
-  }
+  void TearDown() override { fixture_.reset(); }
 
-  grpc_end2end_test_fixture fixture_;
+  std::unique_ptr<CoreTestFixture> fixture_;
 };
 
 TEST_P(H2SslCertTest, SimpleRequestBody) {
-  simple_request_body(fixture_, GetParam().result);
+  simple_request_body(fixture_.get(), GetParam().result);
 }
 
 #ifndef OPENSSL_IS_BORINGSSL
@@ -360,7 +267,7 @@
   test_server1_key_id.clear();
   test_server1_key_id.append("engine:libengine_passthrough:");
   test_server1_key_id.append(test_server1_key);
-  simple_request_body(fixture_, GetParam().result);
+  simple_request_body(fixture_.get(), GetParam().result);
 }
 #endif
 #endif
diff --git a/test/core/end2end/h2_ssl_session_reuse_test.cc b/test/core/end2end/h2_ssl_session_reuse_test.cc
index 7a12b86..0f9784e 100644
--- a/test/core/end2end/h2_ssl_session_reuse_test.cc
+++ b/test/core/end2end/h2_ssl_session_reuse_test.cc
@@ -16,7 +16,6 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
 #include <string>
@@ -54,8 +53,6 @@
 namespace testing {
 namespace {
 
-void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 gpr_timespec five_seconds_time() { return grpc_timeout_seconds_to_deadline(5); }
 
 grpc_server* server_create(grpc_completion_queue* cq, const char* server_addr) {
@@ -186,15 +183,16 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   grpc_call* s;
   error = grpc_server_request_call(server, &s, &call_details,
-                                   &request_metadata_recv, cq, cq, tag(101));
+                                   &request_metadata_recv, cq, cq,
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   grpc_auth_context* auth = grpc_call_auth_context(s);
@@ -227,12 +225,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   grpc_metadata_array_destroy(&initial_metadata_recv);
@@ -273,12 +271,13 @@
                  cq, grpc_timeout_milliseconds_to_deadline(100), nullptr)
                  .type == GRPC_QUEUE_TIMEOUT);
 
-  grpc_server_shutdown_and_notify(server, cq, tag(1000));
+  grpc_server_shutdown_and_notify(server, cq, grpc_core::CqVerifier::tag(1000));
   grpc_event ev;
   do {
     ev = grpc_completion_queue_next(cq, grpc_timeout_seconds_to_deadline(5),
                                     nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
+  } while (ev.type != GRPC_OP_COMPLETE ||
+           ev.tag != grpc_core::CqVerifier::tag(1000));
   grpc_server_destroy(server);
 
   grpc_completion_queue_shutdown(cq);
diff --git a/test/core/end2end/inproc_callback_test.cc b/test/core/end2end/inproc_callback_test.cc
deleted file mode 100644
index 90d816d..0000000
--- a/test/core/end2end/inproc_callback_test.cc
+++ /dev/null
@@ -1,505 +0,0 @@
-//
-//
-// Copyright 2018 gRPC authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-//
-
-#include <inttypes.h>
-#include <string.h>
-
-#include <initializer_list>
-
-#include "absl/strings/str_format.h"
-
-#include <grpc/grpc.h>
-#include <grpc/impl/propagation_bits.h>
-#include <grpc/slice.h>
-#include <grpc/status.h>
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
-#include <grpc/support/sync.h>
-#include <grpc/support/time.h>
-
-#include "src/core/ext/transport/inproc/inproc_transport.h"
-#include "src/core/lib/gprpp/crash.h"
-#include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/test_config.h"
-
-typedef struct inproc_fixture_data {
-  bool phony;  // reserved for future expansion. Struct can't be empty
-} inproc_fixture_data;
-
-namespace {
-template <typename F>
-class CQDeletingCallback : public grpc_completion_queue_functor {
- public:
-  explicit CQDeletingCallback(F f) : func_(f) {
-    functor_run = &CQDeletingCallback::Run;
-    inlineable = false;
-  }
-  ~CQDeletingCallback() {}
-  static void Run(grpc_completion_queue_functor* cb, int ok) {
-    auto* callback = static_cast<CQDeletingCallback*>(cb);
-    callback->func_(static_cast<bool>(ok));
-    delete callback;
-  }
-
- private:
-  F func_;
-};
-
-template <typename F>
-grpc_completion_queue_functor* NewDeletingCallback(F f) {
-  return new CQDeletingCallback<F>(f);
-}
-
-class ShutdownCallback : public grpc_completion_queue_functor {
- public:
-  ShutdownCallback() : done_(false) {
-    functor_run = &ShutdownCallback::StaticRun;
-    inlineable = false;
-    gpr_mu_init(&mu_);
-    gpr_cv_init(&cv_);
-  }
-  ~ShutdownCallback() {
-    gpr_mu_destroy(&mu_);
-    gpr_cv_destroy(&cv_);
-  }
-  static void StaticRun(grpc_completion_queue_functor* cb, int ok) {
-    auto* callback = static_cast<ShutdownCallback*>(cb);
-    callback->Run(static_cast<bool>(ok));
-  }
-  void Run(bool /*ok*/) {
-    gpr_log(GPR_DEBUG, "CQ shutdown notification invoked");
-    gpr_mu_lock(&mu_);
-    done_ = true;
-    gpr_cv_broadcast(&cv_);
-    gpr_mu_unlock(&mu_);
-  }
-  // The Wait function waits for a specified amount of
-  // time for the completion of the shutdown and returns
-  // whether it was successfully shut down
-  bool Wait(gpr_timespec deadline) {
-    gpr_mu_lock(&mu_);
-    while (!done_ && !gpr_cv_wait(&cv_, &mu_, deadline)) {
-    }
-    bool ret = done_;
-    gpr_mu_unlock(&mu_);
-    return ret;
-  }
-
- private:
-  bool done_;
-  gpr_mu mu_;
-  gpr_cv cv_;
-};
-
-ShutdownCallback* g_shutdown_callback;
-}  // namespace
-
-// The following global structure is the tag collection. It holds
-// all information related to tags expected and tags received
-// during the execution, with each callback setting a tag.
-// The tag sets are implemented and checked using arrays and
-// linear lookups (rather than maps) so that this test doesn't
-// need the C++ standard library.
-static gpr_mu tags_mu;
-static gpr_cv tags_cv;
-const size_t kAvailableTags = 4;
-bool tags[kAvailableTags];
-bool tags_valid[kAvailableTags];
-bool tags_expected[kAvailableTags];
-bool tags_needed[kAvailableTags];
-
-// Mark that a tag is expected; this function must be executed in the
-// main thread only while there are no other threads altering the
-// expectation set (e.g., by calling expect_tag or verify_tags)
-static void expect_tag(intptr_t tag, bool ok) {
-  size_t idx = static_cast<size_t>(tag);
-  GPR_ASSERT(idx < kAvailableTags);
-  tags_needed[idx] = true;
-  tags_expected[idx] = ok;
-}
-
-// Check that the expected tags have reached, within a certain
-// deadline. This must also be executed only on the main thread while
-// there are no other threads altering the expectation set (e.g., by
-// calling expect_tag or verify_tags). The tag verifier doesn't have
-// to drive the CQ at all (unlike the next-based end2end tests)
-// because the tags will get set when the callbacks are executed,
-// which happens when a particular batch related to a callback is
-// complete.
-static void verify_tags(gpr_timespec deadline) {
-  bool done = false;
-
-  gpr_mu_lock(&tags_mu);
-  while (!done) {
-    done = gpr_time_cmp(gpr_now(GPR_CLOCK_MONOTONIC), deadline) > 0;
-    for (size_t i = 0; i < kAvailableTags; i++) {
-      if (tags_needed[i]) {
-        if (tags_valid[i]) {
-          gpr_log(GPR_DEBUG, "Verifying tag %d", static_cast<int>(i));
-          if (tags[i] != tags_expected[i]) {
-            grpc_core::Crash(absl::StrFormat(
-                "Got wrong result (%d instead of %d) for tag %d", tags[i],
-                tags_expected[i], static_cast<int>(i)));
-          }
-          tags_valid[i] = false;
-          tags_needed[i] = false;
-        } else if (done) {
-          grpc_core::Crash(
-              absl::StrFormat("Didn't get tag %d", static_cast<int>(i)));
-        }
-      }
-    }
-    bool empty = true;
-    for (size_t i = 0; i < kAvailableTags; i++) {
-      if (tags_needed[i]) {
-        empty = false;
-      }
-    }
-    done = done || empty;
-    if (done) {
-      for (size_t i = 0; i < kAvailableTags; i++) {
-        if (tags_valid[i]) {
-          grpc_core::Crash(
-              absl::StrFormat("Got unexpected tag %d and result %d",
-                              static_cast<int>(i), tags[i]));
-        }
-        tags_valid[i] = false;
-      }
-    } else {
-      gpr_cv_wait(&tags_cv, &tags_mu, deadline);
-    }
-  }
-  gpr_mu_unlock(&tags_mu);
-}
-
-// This function creates a callback functor that emits the
-// desired tag into the global tag set
-static grpc_completion_queue_functor* tag(intptr_t t) {
-  auto func = [t](bool ok) {
-    gpr_mu_lock(&tags_mu);
-    gpr_log(GPR_DEBUG, "Completing operation %" PRIdPTR, t);
-    bool was_empty = true;
-    for (size_t i = 0; i < kAvailableTags; i++) {
-      if (tags_valid[i]) {
-        was_empty = false;
-      }
-    }
-    size_t idx = static_cast<size_t>(t);
-    tags[idx] = ok;
-    tags_valid[idx] = true;
-    if (was_empty) {
-      gpr_cv_signal(&tags_cv);
-    }
-    gpr_mu_unlock(&tags_mu);
-  };
-  auto cb = NewDeletingCallback(func);
-  return cb;
-}
-
-static grpc_end2end_test_fixture inproc_create_fixture(
-    const grpc_channel_args* /*client_args*/,
-    const grpc_channel_args* /*server_args*/) {
-  grpc_end2end_test_fixture f;
-  inproc_fixture_data* ffd = static_cast<inproc_fixture_data*>(
-      gpr_malloc(sizeof(inproc_fixture_data)));
-  memset(&f, 0, sizeof(f));
-
-  f.fixture_data = ffd;
-  g_shutdown_callback = new ShutdownCallback();
-  f.cq =
-      grpc_completion_queue_create_for_callback(g_shutdown_callback, nullptr);
-
-  return f;
-}
-
-void inproc_init_client(grpc_end2end_test_fixture* f,
-                        const grpc_channel_args* client_args) {
-  f->client = grpc_inproc_channel_create(f->server, client_args, nullptr);
-  GPR_ASSERT(f->client);
-}
-
-void inproc_init_server(grpc_end2end_test_fixture* f,
-                        const grpc_channel_args* server_args) {
-  if (f->server) {
-    grpc_server_destroy(f->server);
-  }
-  f->server = grpc_server_create(server_args, nullptr);
-  grpc_server_register_completion_queue(f->server, f->cq, nullptr);
-  grpc_server_start(f->server);
-}
-
-void inproc_tear_down(grpc_end2end_test_fixture* f) {
-  inproc_fixture_data* ffd = static_cast<inproc_fixture_data*>(f->fixture_data);
-  gpr_free(ffd);
-}
-
-static grpc_end2end_test_fixture begin_test(
-    grpc_end2end_test_config config, const char* test_name,
-    const grpc_channel_args* client_args,
-    const grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
-  gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
-  return f;
-}
-
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now() { return n_seconds_from_now(5); }
-
-static void drain_cq(grpc_completion_queue* /*cq*/) {
-  // Wait for the shutdown callback to arrive, or fail the test
-  GPR_ASSERT(g_shutdown_callback->Wait(five_seconds_from_now()));
-  gpr_log(GPR_DEBUG, "CQ shutdown wait complete");
-  delete g_shutdown_callback;
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1));
-  expect_tag(1, true);
-  verify_tags(five_seconds_from_now());
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /* config */,
-                                grpc_end2end_test_fixture f) {
-  grpc_call* c;
-  grpc_call* s;
-  grpc_op ops[6];
-  grpc_op* op;
-  grpc_metadata_array initial_metadata_recv;
-  grpc_metadata_array trailing_metadata_recv;
-  grpc_metadata_array request_metadata_recv;
-  grpc_call_details call_details;
-  grpc_status_code status;
-  const char* error_string;
-  grpc_call_error error;
-  grpc_slice details;
-  int was_cancelled = 2;
-  char* peer;
-  gpr_timespec deadline = five_seconds_from_now();
-
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
-  GPR_ASSERT(c);
-
-  peer = grpc_call_get_peer(c);
-  GPR_ASSERT(peer != nullptr);
-  gpr_log(GPR_DEBUG, "client_peer_before_call=%s", peer);
-  gpr_free(peer);
-
-  grpc_metadata_array_init(&initial_metadata_recv);
-  grpc_metadata_array_init(&trailing_metadata_recv);
-  grpc_metadata_array_init(&request_metadata_recv);
-  grpc_call_details_init(&call_details);
-
-  // Create a basic client unary request batch (no payload)
-  memset(ops, 0, sizeof(ops));
-  op = ops;
-  op->op = GRPC_OP_SEND_INITIAL_METADATA;
-  op->data.send_initial_metadata.count = 0;
-  op->flags = 0;
-  op->reserved = nullptr;
-  op++;
-  op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
-  op->flags = 0;
-  op->reserved = nullptr;
-  op++;
-  op->op = GRPC_OP_RECV_INITIAL_METADATA;
-  op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv;
-  op->flags = 0;
-  op->reserved = nullptr;
-  op++;
-  op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
-  op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv;
-  op->data.recv_status_on_client.status = &status;
-  op->data.recv_status_on_client.status_details = &details;
-  op->data.recv_status_on_client.error_string = &error_string;
-  op->flags = 0;
-  op->reserved = nullptr;
-  op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
-  GPR_ASSERT(GRPC_CALL_OK == error);
-
-  // Register a call at the server-side to match the incoming client call
-  error = grpc_server_request_call(f.server, &s, &call_details,
-                                   &request_metadata_recv, f.cq, f.cq, tag(2));
-  GPR_ASSERT(GRPC_CALL_OK == error);
-
-  // We expect that the server call creation callback (and no others) will
-  // execute now since no other batch should be complete.
-  expect_tag(2, true);
-  verify_tags(deadline);
-
-  peer = grpc_call_get_peer(s);
-  GPR_ASSERT(peer != nullptr);
-  gpr_log(GPR_DEBUG, "server_peer=%s", peer);
-  gpr_free(peer);
-  peer = grpc_call_get_peer(c);
-  GPR_ASSERT(peer != nullptr);
-  gpr_log(GPR_DEBUG, "client_peer=%s", peer);
-  gpr_free(peer);
-
-  // Create the server response batch (no payload)
-  memset(ops, 0, sizeof(ops));
-  op = ops;
-  op->op = GRPC_OP_SEND_INITIAL_METADATA;
-  op->data.send_initial_metadata.count = 0;
-  op->flags = 0;
-  op->reserved = nullptr;
-  op++;
-  op->op = GRPC_OP_SEND_STATUS_FROM_SERVER;
-  op->data.send_status_from_server.trailing_metadata_count = 0;
-  op->data.send_status_from_server.status = GRPC_STATUS_UNIMPLEMENTED;
-  grpc_slice status_details = grpc_slice_from_static_string("xyz");
-  op->data.send_status_from_server.status_details = &status_details;
-  op->flags = 0;
-  op->reserved = nullptr;
-  op++;
-  op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
-  op->data.recv_close_on_server.cancelled = &was_cancelled;
-  op->flags = 0;
-  op->reserved = nullptr;
-  op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
-  GPR_ASSERT(GRPC_CALL_OK == error);
-
-  // Both the client request and server response batches should get complete
-  // now and we should see that their callbacks have been executed
-  expect_tag(3, true);
-  expect_tag(1, true);
-  verify_tags(deadline);
-
-  GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
-  GPR_ASSERT(0 == grpc_slice_str_cmp(details, "xyz"));
-  // the following sanity check makes sure that the requested error string is
-  // correctly populated by the core. It looks for certain substrings that are
-  // not likely to change much. Some parts of the error, like time created,
-  // obviously are not checked.
-  GPR_ASSERT(nullptr != strstr(error_string, "xyz"));
-  GPR_ASSERT(nullptr != strstr(error_string, "Error received from peer"));
-  GPR_ASSERT(nullptr != strstr(error_string, "grpc_message"));
-  GPR_ASSERT(nullptr != strstr(error_string, "grpc_status"));
-  GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/foo"));
-  GPR_ASSERT(was_cancelled == 0);
-
-  grpc_slice_unref(details);
-  gpr_free(static_cast<void*>(const_cast<char*>(error_string)));
-  grpc_metadata_array_destroy(&initial_metadata_recv);
-  grpc_metadata_array_destroy(&trailing_metadata_recv);
-  grpc_metadata_array_destroy(&request_metadata_recv);
-  grpc_call_details_destroy(&call_details);
-
-  grpc_call_unref(c);
-  grpc_call_unref(s);
-}
-
-static void test_invoke_simple_request(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
-
-  f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
-  simple_request_body(config, f);
-  end_test(&f);
-  config.tear_down_data(&f);
-}
-
-static void test_invoke_10_simple_requests(grpc_end2end_test_config config) {
-  int i;
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_invoke_10_simple_requests", nullptr, nullptr);
-  for (i = 0; i < 10; i++) {
-    simple_request_body(config, f);
-    gpr_log(GPR_INFO, "Running test: Passed simple request %d", i);
-  }
-  end_test(&f);
-  config.tear_down_data(&f);
-}
-
-static void test_invoke_many_simple_requests(grpc_end2end_test_config config) {
-  int i;
-  const int many = 1000;
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_invoke_many_simple_requests", nullptr, nullptr);
-  gpr_timespec t1 = gpr_now(GPR_CLOCK_MONOTONIC);
-  for (i = 0; i < many; i++) {
-    simple_request_body(config, f);
-  }
-  double us =
-      gpr_timespec_to_micros(gpr_time_sub(gpr_now(GPR_CLOCK_MONOTONIC), t1)) /
-      many;
-  gpr_log(GPR_INFO, "Time per ping %f us", us);
-  end_test(&f);
-  config.tear_down_data(&f);
-}
-
-static void simple_request(grpc_end2end_test_config config) {
-  int i;
-  for (i = 0; i < 10; i++) {
-    test_invoke_simple_request(config);
-  }
-  test_invoke_10_simple_requests(config);
-  test_invoke_many_simple_requests(config);
-}
-
-static void simple_request_pre_init() {
-  gpr_mu_init(&tags_mu);
-  gpr_cv_init(&tags_cv);
-}
-
-// All test configurations
-static grpc_end2end_test_config configs[] = {
-    {"inproc-callback", FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER, nullptr,
-     inproc_create_fixture, inproc_init_client, inproc_init_server,
-     inproc_tear_down},
-};
-
-int main(int argc, char** argv) {
-  grpc::testing::TestEnvironment env(&argc, argv);
-  grpc_init();
-
-  simple_request_pre_init();
-  simple_request(configs[0]);
-
-  grpc_shutdown();
-
-  return 0;
-}
diff --git a/test/core/end2end/invalid_call_argument_test.cc b/test/core/end2end/invalid_call_argument_test.cc
index f51de74..7977aa3 100644
--- a/test/core/end2end/invalid_call_argument_test.cc
+++ b/test/core/end2end/invalid_call_argument_test.cc
@@ -19,7 +19,6 @@
 #include <grpc/support/port_platform.h>
 
 #include <limits.h>
-#include <stdint.h>
 #include <string.h>
 
 #include <initializer_list>
@@ -40,8 +39,6 @@
 #include "test/core/util/port.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 struct test_state {
   int is_client;
   grpc_channel* chan;
@@ -111,16 +108,17 @@
     op->flags = GRPC_INITIAL_METADATA_WAIT_FOR_READY;
     op->reserved = nullptr;
     op++;
-    GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(g_state.call, g_state.ops,
-                                                     (size_t)(op - g_state.ops),
-                                                     tag(1), nullptr));
     GPR_ASSERT(GRPC_CALL_OK ==
-               grpc_server_request_call(g_state.server, &g_state.server_call,
-                                        &g_state.call_details,
-                                        &g_state.server_initial_metadata_recv,
-                                        g_state.cq, g_state.cq, tag(101)));
-    g_state.cqv->Expect(tag(101), true);
-    g_state.cqv->Expect(tag(1), true);
+               grpc_call_start_batch(g_state.call, g_state.ops,
+                                     (size_t)(op - g_state.ops),
+                                     grpc_core::CqVerifier::tag(1), nullptr));
+    GPR_ASSERT(GRPC_CALL_OK ==
+               grpc_server_request_call(
+                   g_state.server, &g_state.server_call, &g_state.call_details,
+                   &g_state.server_initial_metadata_recv, g_state.cq,
+                   g_state.cq, grpc_core::CqVerifier::tag(101)));
+    g_state.cqv->Expect(grpc_core::CqVerifier::tag(101), true);
+    g_state.cqv->Expect(grpc_core::CqVerifier::tag(1), true);
     g_state.cqv->Verify();
   }
 }
@@ -134,12 +132,14 @@
 
   if (!g_state.is_client) {
     grpc_call_unref(g_state.server_call);
-    grpc_server_shutdown_and_notify(g_state.server, g_state.cq, tag(1000));
+    grpc_server_shutdown_and_notify(g_state.server, g_state.cq,
+                                    grpc_core::CqVerifier::tag(1000));
     grpc_event ev;
     do {
       ev = grpc_completion_queue_next(
           g_state.cq, grpc_timeout_seconds_to_deadline(5), nullptr);
-    } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
+    } while (ev.type != GRPC_OP_COMPLETE ||
+             ev.tag != grpc_core::CqVerifier::tag(1000));
     grpc_server_destroy(g_state.server);
     grpc_call_details_destroy(&g_state.call_details);
     grpc_metadata_array_destroy(&g_state.server_initial_metadata_recv);
@@ -157,7 +157,8 @@
 
   prepare_test(1);
   GPR_ASSERT(GRPC_CALL_ERROR ==
-             grpc_call_start_batch(g_state.call, nullptr, 0, nullptr, tag(1)));
+             grpc_call_start_batch(g_state.call, nullptr, 0, nullptr,
+                                   grpc_core::CqVerifier::tag(1)));
   cleanup_test();
 }
 
@@ -171,12 +172,12 @@
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
   op->data.send_initial_metadata.count = 0;
   op->flags = 0;
-  op->reserved = tag(2);
+  op->reserved = grpc_core::CqVerifier::tag(2);
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -192,10 +193,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(g_state.call, g_state.ops,
-                                                   (size_t)(op - g_state.ops),
-                                                   tag(1), nullptr));
-  g_state.cqv->Expect(tag(1), false);
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(g_state.call, g_state.ops,
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
+  g_state.cqv->Expect(grpc_core::CqVerifier::tag(1), false);
   g_state.cqv->Verify();
 
   op = g_state.ops;
@@ -206,8 +208,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -225,8 +227,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_INVALID_METADATA ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -249,8 +251,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_INVALID_MESSAGE ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -275,14 +277,15 @@
   op->reserved = nullptr;
   op++;
   op->op = GRPC_OP_SEND_MESSAGE;
-  op->data.send_message.send_message = static_cast<grpc_byte_buffer*>(tag(2));
+  op->data.send_message.send_message =
+      static_cast<grpc_byte_buffer*>(grpc_core::CqVerifier::tag(2));
   op->flags = 0;
   op->reserved = nullptr;
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   grpc_byte_buffer_destroy(request_payload);
   cleanup_test();
 }
@@ -304,8 +307,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_NOT_ON_CLIENT ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -321,10 +324,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(g_state.call, g_state.ops,
-                                                   (size_t)(op - g_state.ops),
-                                                   tag(1), nullptr));
-  g_state.cqv->Expect(tag(1), false);
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(g_state.call, g_state.ops,
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
+  g_state.cqv->Expect(grpc_core::CqVerifier::tag(1), false);
   g_state.cqv->Verify();
   op = g_state.ops;
   op->op = GRPC_OP_RECV_INITIAL_METADATA;
@@ -335,8 +339,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -354,8 +358,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_INVALID_FLAGS ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -378,8 +382,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -397,8 +401,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_NOT_ON_CLIENT ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -417,10 +421,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(g_state.call, g_state.ops,
-                                                   (size_t)(op - g_state.ops),
-                                                   tag(1), nullptr));
-  g_state.cqv->Expect(tag(1), true);
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(g_state.call, g_state.ops,
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
+  g_state.cqv->Expect(grpc_core::CqVerifier::tag(1), true);
   g_state.cqv->Verify();
 
   op = g_state.ops;
@@ -433,8 +438,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -451,8 +456,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_NOT_ON_SERVER ==
              grpc_call_start_batch(g_state.server_call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(2),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(2), nullptr));
   cleanup_test();
 }
 
@@ -473,8 +478,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_NOT_ON_SERVER ==
              grpc_call_start_batch(g_state.server_call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(2),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(2), nullptr));
   cleanup_test();
 }
 
@@ -495,8 +500,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_INVALID_FLAGS ==
              grpc_call_start_batch(g_state.server_call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(2),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(2), nullptr));
   cleanup_test();
 }
 
@@ -518,8 +523,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_INVALID_METADATA ==
              grpc_call_start_batch(g_state.server_call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(2),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(2), nullptr));
   cleanup_test();
 }
 
@@ -547,8 +552,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS ==
              grpc_call_start_batch(g_state.server_call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(2),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(2), nullptr));
   cleanup_test();
 }
 
@@ -566,8 +571,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_INVALID_FLAGS ==
              grpc_call_start_batch(g_state.server_call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(2),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(2), nullptr));
   cleanup_test();
 }
 
@@ -590,8 +595,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS ==
              grpc_call_start_batch(g_state.server_call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(2),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(2), nullptr));
   cleanup_test();
 }
 
@@ -613,8 +618,8 @@
   op++;
   GPR_ASSERT(GRPC_CALL_ERROR_INVALID_METADATA ==
              grpc_call_start_batch(g_state.call, g_state.ops,
-                                   (size_t)(op - g_state.ops), tag(1),
-                                   nullptr));
+                                   (size_t)(op - g_state.ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
   cleanup_test();
 }
 
@@ -636,8 +641,8 @@
     op++;
     GPR_ASSERT(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS ==
                grpc_call_start_batch(g_state.call, g_state.ops,
-                                     (size_t)(op - g_state.ops), tag(1),
-                                     nullptr));
+                                     (size_t)(op - g_state.ops),
+                                     grpc_core::CqVerifier::tag(1), nullptr));
   }
 
   cleanup_test();
diff --git a/test/core/end2end/no_server_test.cc b/test/core/end2end/no_server_test.cc
index b9e3c08..60f940c 100644
--- a/test/core/end2end/no_server_test.cc
+++ b/test/core/end2end/no_server_test.cc
@@ -16,7 +16,6 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
 #include <grpc/grpc.h>
@@ -33,8 +32,6 @@
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 void run_test(bool wait_for_ready) {
   gpr_log(GPR_INFO, "TEST: wait_for_ready=%d", wait_for_ready);
 
@@ -78,9 +75,9 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(call, ops,
-                                                   (size_t)(op - ops), tag(1),
-                                                   nullptr));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_call_start_batch(call, ops, (size_t)(op - ops),
+                                   grpc_core::CqVerifier::tag(1), nullptr));
 
   {
     grpc_core::ExecCtx exec_ctx;
@@ -88,7 +85,7 @@
   }
 
   // verify that all tags get completed
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   gpr_log(GPR_INFO, "call status: %d", status);
diff --git a/test/core/end2end/tests/authority_not_supported.cc b/test/core/end2end/tests/authority_not_supported.cc
index d06189d..db34001 100644
--- a/test/core/end2end/tests/authority_not_supported.cc
+++ b/test/core/end2end/tests/authority_not_supported.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -27,68 +29,24 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Request/response with metadata and payload.
-static void test_with_authority_header(grpc_end2end_test_config config) {
+static void test_with_authority_header(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
@@ -100,9 +58,8 @@
                              {grpc_slice_from_static_string("key2"),
                               grpc_slice_from_static_string("val2"),
                               {{nullptr, nullptr, nullptr, nullptr}}}};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_with_authority_header", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "test_with_authority_header", nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -113,10 +70,10 @@
   grpc_slice details;
 
   grpc_slice host = grpc_slice_from_static_string("foo.test.google.fr");
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), &host,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               &host, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -156,11 +113,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_CANCELLED);
@@ -173,12 +130,9 @@
 
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(response_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void authority_not_supported(grpc_end2end_test_config config) {
+void authority_not_supported(const CoreTestConfiguration& config) {
   if (config.feature_mask & FEATURE_MASK_SUPPORTS_AUTHORITY_HEADER) {
     return;
   }
diff --git a/test/core/end2end/tests/bad_hostname.cc b/test/core/end2end/tests/bad_hostname.cc
index ff606e4..77b24be 100644
--- a/test/core/end2end/tests/bad_hostname.cc
+++ b/test/core/end2end/tests/bad_hostname.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -26,69 +28,25 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_client(&f, client_args);
-  config.init_server(&f, server_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_fixture f) {
+static void simple_request_body(CoreTestFixture* f) {
   grpc_call* c;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -100,10 +58,10 @@
   grpc_slice details;
 
   grpc_slice host = grpc_slice_from_static_string("slartibartfast.local");
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), &host,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               &host, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -134,11 +92,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_INTERNAL);
@@ -152,16 +110,12 @@
   grpc_call_unref(c);
 }
 
-static void test_invoke_simple_request(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
-
-  f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
-  simple_request_body(f);
-  end_test(&f);
-  config.tear_down_data(&f);
+static void test_invoke_simple_request(const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
+  simple_request_body(f.get());
 }
 
-void bad_hostname(grpc_end2end_test_config config) {
+void bad_hostname(const CoreTestConfiguration& config) {
   if (config.feature_mask & FEATURE_MASK_SUPPORTS_HOSTNAME_VERIFICATION) {
     test_invoke_simple_request(config);
   }
diff --git a/test/core/end2end/tests/bad_ping.cc b/test/core/end2end/tests/bad_ping.cc
index 6900678..d7e7f0c 100644
--- a/test/core/end2end/tests/bad_ping.cc
+++ b/test/core/end2end/tests/bad_ping.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -27,7 +29,7 @@
 #include <grpc/support/time.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/time.h"
 #include "src/core/lib/surface/channel.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
@@ -35,60 +37,23 @@
 
 #define MAX_PING_STRIKES 2
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Send more pings than server allows to trigger server's GOAWAY.
-static void test_bad_ping(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = config.create_fixture(nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
-  grpc_arg client_a[] = {
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA), 0),
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_HTTP2_BDP_PROBE), 0)};
-  grpc_arg server_a[] = {
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(
-              GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS),
-          300000 /* 5 minutes */),
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_HTTP2_MAX_PING_STRIKES), MAX_PING_STRIKES),
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_HTTP2_BDP_PROBE), 0)};
-  grpc_channel_args client_args = {GPR_ARRAY_SIZE(client_a), client_a};
-  grpc_channel_args server_args = {GPR_ARRAY_SIZE(server_a), server_a};
+static void test_bad_ping(const CoreTestConfiguration& config) {
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
+  grpc_core::CqVerifier cqv(f->cq());
+  auto client_args = grpc_core::ChannelArgs()
+                         .Set(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA, 0)
+                         .Set(GRPC_ARG_HTTP2_BDP_PROBE, 0);
+  auto server_args =
+      grpc_core::ChannelArgs()
+          .Set(GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS,
+               grpc_core::Duration::Minutes(5).millis())
+          .Set(GRPC_ARG_HTTP2_MAX_PING_STRIKES, MAX_PING_STRIKES)
+          .Set(GRPC_ARG_HTTP2_BDP_PROBE, 0);
 
-  config.init_client(&f, &client_args);
-  config.init_server(&f, &server_args);
+  f->InitClient(client_args);
+  f->InitServer(server_args);
 
   grpc_call* c;
   grpc_call* s;
@@ -104,9 +69,9 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -138,15 +103,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Send too many pings to the server to trigger the punishment:
@@ -156,10 +121,11 @@
   // needed here.
   int i;
   for (i = 1; i <= MAX_PING_STRIKES + 2; i++) {
-    grpc_channel_ping(f.client, f.cq, tag(200 + i), nullptr);
-    cqv.Expect(tag(200 + i), true);
+    grpc_channel_ping(f->client(), f->cq(), grpc_core::CqVerifier::tag(200 + i),
+                      nullptr);
+    cqv.Expect(grpc_core::CqVerifier::tag(200 + i), true);
     if (i == MAX_PING_STRIKES + 2) {
-      cqv.Expect(tag(1), true);
+      cqv.Expect(grpc_core::CqVerifier::tag(1), true);
     }
     cqv.Verify();
   }
@@ -184,15 +150,16 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
-  cqv.Expect(tag(0xdead), true);
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(0xdead));
+  cqv.Expect(grpc_core::CqVerifier::tag(0xdead), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -209,37 +176,29 @@
   grpc_metadata_array_destroy(&request_metadata_recv);
   grpc_call_details_destroy(&call_details);
   grpc_call_unref(c);
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 // Try sending more pings than server allows, but server should be fine because
 // max_pings_without_data should limit pings sent out on wire.
-static void test_pings_without_data(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = config.create_fixture(nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+static void test_pings_without_data(const CoreTestConfiguration& config) {
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
+  grpc_core::CqVerifier cqv(f->cq());
   // Only allow MAX_PING_STRIKES pings without data (DATA/HEADERS/WINDOW_UPDATE)
   // so that the transport will throttle the excess pings.
-  grpc_arg client_a[] = {
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA),
-          MAX_PING_STRIKES),
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_HTTP2_BDP_PROBE), 0)};
-  grpc_arg server_a[] = {
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(
-              GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS),
-          300000 /* 5 minutes */),
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_HTTP2_MAX_PING_STRIKES), MAX_PING_STRIKES),
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_HTTP2_BDP_PROBE), 0)};
-  grpc_channel_args client_args = {GPR_ARRAY_SIZE(client_a), client_a};
-  grpc_channel_args server_args = {GPR_ARRAY_SIZE(server_a), server_a};
+  auto client_args =
+      grpc_core::ChannelArgs()
+          .Set(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA, MAX_PING_STRIKES)
+          .Set(GRPC_ARG_HTTP2_BDP_PROBE, 0);
+  auto server_args =
+      grpc_core::ChannelArgs()
+          .Set(GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS,
+               grpc_core::Duration::Minutes(5).millis())
+          .Set(GRPC_ARG_HTTP2_MAX_PING_STRIKES, MAX_PING_STRIKES)
+          .Set(GRPC_ARG_HTTP2_BDP_PROBE, 0);
 
-  config.init_client(&f, &client_args);
-  config.init_server(&f, &server_args);
+  f->InitClient(client_args);
+  f->InitServer(server_args);
 
   grpc_call* c;
   grpc_call* s;
@@ -255,9 +214,9 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -289,15 +248,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Send too many pings to the server similar to the previous test case.
@@ -305,9 +264,10 @@
   // MAX_PING_STRIKES will actually be sent and the rpc will still succeed.
   int i;
   for (i = 1; i <= MAX_PING_STRIKES + 2; i++) {
-    grpc_channel_ping(f.client, f.cq, tag(200 + i), nullptr);
+    grpc_channel_ping(f->client(), f->cq(), grpc_core::CqVerifier::tag(200 + i),
+                      nullptr);
     if (i <= MAX_PING_STRIKES) {
-      cqv.Expect(tag(200 + i), true);
+      cqv.Expect(grpc_core::CqVerifier::tag(200 + i), true);
     }
     cqv.Verify();
   }
@@ -332,21 +292,22 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   // Client call should return.
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
-  cqv.Expect(tag(0xdead), true);
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(0xdead));
+  cqv.Expect(grpc_core::CqVerifier::tag(0xdead), true);
 
   // Also expect the previously blocked pings to complete with an error
-  cqv.Expect(tag(200 + MAX_PING_STRIKES + 1), false);
-  cqv.Expect(tag(200 + MAX_PING_STRIKES + 2), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(200 + MAX_PING_STRIKES + 1), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(200 + MAX_PING_STRIKES + 2), false);
 
   cqv.Verify();
 
@@ -362,11 +323,9 @@
   grpc_metadata_array_destroy(&request_metadata_recv);
   grpc_call_details_destroy(&call_details);
   grpc_call_unref(c);
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void bad_ping(grpc_end2end_test_config config) {
+void bad_ping(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION);
   test_bad_ping(config);
   test_pings_without_data(config);
diff --git a/test/core/end2end/tests/binary_metadata.cc b/test/core/end2end/tests/binary_metadata.cc
index 3d2dfb2..ec21297 100644
--- a/test/core/end2end/tests/binary_metadata.cc
+++ b/test/core/end2end/tests/binary_metadata.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,13 +36,9 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
   client_args =
       const_cast<grpc_channel_args*>(grpc_core::CoreConfiguration::Get()
@@ -54,59 +52,18 @@
                                          .PreconditionChannelArgs(server_args)
                                          .ToC()
                                          .release());
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   grpc_channel_args_destroy(client_args);
   grpc_channel_args_destroy(server_args);
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Request/response with metadata and payload.
 static void test_request_response_with_metadata_and_payload(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice =
@@ -135,10 +92,9 @@
        grpc_slice_from_static_string(
            "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"),
        {{nullptr, nullptr, nullptr, nullptr}}}};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_request_response_with_metadata_and_payload",
-                 nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "test_request_response_with_metadata_and_payload",
+                      nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -152,10 +108,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -197,15 +153,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -221,11 +177,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -263,12 +219,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -322,12 +278,9 @@
   grpc_byte_buffer_destroy(response_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
   grpc_byte_buffer_destroy(response_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void binary_metadata(grpc_end2end_test_config config) {
+void binary_metadata(const CoreTestConfiguration& config) {
   test_request_response_with_metadata_and_payload(config);
 }
 
diff --git a/test/core/end2end/tests/call_creds.cc b/test/core/end2end/tests/call_creds.cc
index 158aad0..eb5ab4b 100644
--- a/test/core/end2end/tests/call_creds.cc
+++ b/test/core/end2end/tests/call_creds.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/grpc_security.h>
@@ -28,6 +30,7 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/security/credentials/credentials.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
@@ -44,76 +47,24 @@
 
 typedef enum { NONE, OVERRIDE, DESTROY, FAIL } override_mode;
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            bool use_secure_call_creds,
-                                            int fail_server_auth_check) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    bool use_secure_call_creds, int fail_server_auth_check) {
   gpr_log(GPR_INFO, "Running test: %s%s/%s", test_name,
           use_secure_call_creds ? "_with_secure_call_creds"
                                 : "_with_insecure_call_creds",
           config.name);
-  f = config.create_fixture(nullptr, nullptr);
-  config.init_client(&f, nullptr);
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
+  f->InitClient(grpc_core::ChannelArgs());
+  grpc_core::ChannelArgs server_args;
   if (fail_server_auth_check) {
-    grpc_arg fail_auth_arg = {
-        GRPC_ARG_STRING,
-        const_cast<char*>(FAIL_AUTH_CHECK_SERVER_ARG_NAME),
-        {nullptr}};
-    grpc_channel_args args;
-    args.num_args = 1;
-    args.args = &fail_auth_arg;
-    config.init_server(&f, &args);
-  } else {
-    config.init_server(&f, nullptr);
+    server_args = server_args.Set(FAIL_AUTH_CHECK_SERVER_ARG_NAME, true);
   }
+  f->InitServer(server_args);
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 static void print_auth_context(int is_client, const grpc_auth_context* ctx) {
   const grpc_auth_property* p;
   grpc_auth_property_iterator it;
@@ -132,8 +83,8 @@
 }
 
 static void request_response_with_payload_and_call_creds(
-    const char* test_name, grpc_end2end_test_config config, override_mode mode,
-    bool use_secure_call_creds) {
+    const char* test_name, const CoreTestConfiguration& config,
+    override_mode mode, bool use_secure_call_creds) {
   grpc_call* c = nullptr;
   grpc_call* s = nullptr;
   grpc_slice request_payload_slice =
@@ -144,7 +95,7 @@
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
   grpc_byte_buffer* response_payload =
       grpc_raw_byte_buffer_create(&response_payload_slice, 1);
-  grpc_end2end_test_fixture f;
+
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -161,13 +112,13 @@
   grpc_auth_context* server_auth_context = nullptr;
   grpc_auth_context* client_auth_context = nullptr;
 
-  f = begin_test(config, test_name, use_secure_call_creds, 0);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, test_name, use_secure_call_creds, 0);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
   if (use_secure_call_creds) {
     creds =
@@ -239,22 +190,22 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   if (mode == FAIL) {
     // Expect the call to fail since the channel credentials did not satisfy the
     // minimum security level requirements.
-    cqv.Expect(tag(1), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(1), true);
     cqv.Verify();
     GPR_ASSERT(status == GRPC_STATUS_UNAUTHENTICATED);
   } else {
-    error =
-        grpc_server_request_call(f.server, &s, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101));
+    error = grpc_server_request_call(f->server(), &s, &call_details,
+                                     &request_metadata_recv, f->cq(), f->cq(),
+                                     grpc_core::CqVerifier::tag(101));
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv.Expect(tag(101), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(101), true);
     cqv.Verify();
     server_auth_context = grpc_call_auth_context(s);
     GPR_ASSERT(server_auth_context != nullptr);
@@ -282,10 +233,10 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
+                                  grpc_core::CqVerifier::tag(102), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
-    cqv.Expect(tag(102), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(102), true);
     cqv.Verify();
 
     memset(ops, 0, sizeof(ops));
@@ -309,11 +260,11 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(103), nullptr);
+                                  grpc_core::CqVerifier::tag(103), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
-    cqv.Expect(tag(103), true);
-    cqv.Expect(tag(1), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(1), true);
     cqv.Verify();
 
     GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -388,46 +339,43 @@
   grpc_byte_buffer_destroy(response_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
   grpc_byte_buffer_destroy(response_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 static void test_request_response_with_payload_and_call_creds(
-    grpc_end2end_test_config config, bool use_secure_call_creds) {
+    const CoreTestConfiguration& config, bool use_secure_call_creds) {
   request_response_with_payload_and_call_creds(
       "test_request_response_with_payload_and_call_creds", config, NONE,
       use_secure_call_creds);
 }
 
 static void test_request_response_with_payload_and_overridden_call_creds(
-    grpc_end2end_test_config config, bool use_secure_call_creds) {
+    const CoreTestConfiguration& config, bool use_secure_call_creds) {
   request_response_with_payload_and_call_creds(
       "test_request_response_with_payload_and_overridden_call_creds", config,
       OVERRIDE, use_secure_call_creds);
 }
 
 static void test_request_response_with_payload_and_deleted_call_creds(
-    grpc_end2end_test_config config, bool use_secure_call_creds) {
+    const CoreTestConfiguration& config, bool use_secure_call_creds) {
   request_response_with_payload_and_call_creds(
       "test_request_response_with_payload_and_deleted_call_creds", config,
       DESTROY, use_secure_call_creds);
 }
 
 static void test_request_response_with_payload_fail_to_send_call_creds(
-    grpc_end2end_test_config config, bool use_secure_call_creds) {
+    const CoreTestConfiguration& config, bool use_secure_call_creds) {
   request_response_with_payload_and_call_creds(
       "test_request_response_with_payload_fail_to_send_call_creds", config,
       FAIL, use_secure_call_creds);
 }
 
 static void test_request_with_server_rejecting_client_creds(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_op ops[6];
   grpc_op* op;
   grpc_call* c;
-  grpc_end2end_test_fixture f;
-  gpr_timespec deadline = five_seconds_from_now();
+
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
   grpc_metadata_array initial_metadata_recv;
   grpc_metadata_array trailing_metadata_recv;
   grpc_metadata_array request_metadata_recv;
@@ -442,13 +390,13 @@
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
   grpc_call_credentials* creds;
 
-  f = begin_test(config, "test_request_with_server_rejecting_client_creds",
-                 false, 1);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "test_request_with_server_rejecting_client_creds",
+                      false, 1);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   creds = grpc_md_only_test_credentials_create(fake_md_key, fake_md_value);
@@ -494,11 +442,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(error == GRPC_CALL_OK);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNAUTHENTICATED);
@@ -513,12 +461,9 @@
   grpc_slice_unref(details);
 
   grpc_call_unref(c);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void call_creds(grpc_end2end_test_config config) {
+void call_creds(const CoreTestConfiguration& config) {
   // Test fixtures that support call credentials with a minimum security level
   // of GRPC_PRIVACY_AND_INTEGRITY
   if (config.feature_mask & FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS) {
diff --git a/test/core/end2end/tests/call_host_override.cc b/test/core/end2end/tests/call_host_override.cc
index 2192f3a..c947d02 100644
--- a/test/core/end2end/tests/call_host_override.cc
+++ b/test/core/end2end/tests/call_host_override.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -32,75 +34,24 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  grpc_arg fake_security_name_override = {
-      GRPC_ARG_STRING,
-      const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
-      {const_cast<char*>("foo.test.google.fr:1234")}};
-  grpc_channel_args* new_client_args = grpc_channel_args_copy_and_add(
-      client_args, &fake_security_name_override, 1);
-  config.init_client(&f, new_client_args);
-  grpc_channel_args_destroy(new_client_args);
-  config.init_server(&f, server_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(
+      grpc_core::ChannelArgs::FromC(client_args)
+          .Set(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, "foo.test.google.fr:1234"));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void test_invoke_simple_request(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
+static void test_invoke_simple_request(const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -113,9 +64,9 @@
   int was_cancelled = 2;
   char* peer;
 
-  gpr_timespec deadline = five_seconds_from_now();
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
   c = grpc_channel_create_call(
-      f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
       grpc_slice_from_static_string("/foo"),
       get_host_override_slice("foo.test.google.fr:1234", config), deadline,
       nullptr);
@@ -154,15 +105,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(error == GRPC_CALL_OK);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(error == GRPC_CALL_OK);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -194,12 +145,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(error == GRPC_CALL_OK);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -217,12 +168,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void call_host_override(grpc_end2end_test_config config) {
+void call_host_override(const CoreTestConfiguration& config) {
   test_invoke_simple_request(config);
 }
 
diff --git a/test/core/end2end/tests/cancel_after_accept.cc b/test/core/end2end/tests/cancel_after_accept.cc
index c440c15..49fb414 100644
--- a/test/core/end2end/tests/cancel_after_accept.cc
+++ b/test/core/end2end/tests/cancel_after_accept.cc
@@ -16,9 +16,10 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include <grpc/byte_buffer.h>
@@ -37,68 +38,22 @@
 #include "test/core/end2end/tests/cancel_test_helpers.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            cancellation_mode mode,
-                                            bool use_service_config,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    cancellation_mode mode, bool use_service_config,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "%s", std::string(80, '*').c_str());
   gpr_log(GPR_INFO, "Running test: %s/%s/%s/%s", test_name, config.name,
           mode.name, use_service_config ? "service_config" : "client_api");
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Cancel after accept, no payload
-static void test_cancel_after_accept(grpc_end2end_test_config config,
+static void test_cancel_after_accept(const CoreTestConfiguration& config,
                                      cancellation_mode mode,
                                      bool use_service_config) {
   grpc_op ops[6];
@@ -142,14 +97,15 @@
     args = grpc_channel_args_copy_and_add(args, &arg, 1);
   }
 
-  grpc_end2end_test_fixture f = begin_test(config, "cancel_after_accept", mode,
-                                           use_service_config, args, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "cancel_after_accept", mode, use_service_config,
+                      args, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
 
   gpr_timespec deadline = use_service_config
                               ? gpr_inf_future(GPR_CLOCK_MONOTONIC)
-                              : five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+                              : grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -188,14 +144,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error = grpc_server_request_call(f.server, &s, &call_details,
-                                   &request_metadata_recv, f.cq, f.cq, tag(2));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(2));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -220,14 +177,14 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c, nullptr));
 
-  cqv.Expect(tag(3), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == mode.expect_status || status == GRPC_STATUS_INTERNAL);
@@ -251,12 +208,9 @@
     grpc_core::ExecCtx exec_ctx;
     grpc_channel_args_destroy(args);
   }
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void cancel_after_accept(grpc_end2end_test_config config) {
+void cancel_after_accept(const CoreTestConfiguration& config) {
   unsigned i;
 
   for (i = 0; i < GPR_ARRAY_SIZE(cancellation_modes); i++) {
diff --git a/test/core/end2end/tests/cancel_after_client_done.cc b/test/core/end2end/tests/cancel_after_client_done.cc
index af3d8ef..a1c2fb7 100644
--- a/test/core/end2end/tests/cancel_after_client_done.cc
+++ b/test/core/end2end/tests/cancel_after_client_done.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -27,81 +29,36 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gpr/useful.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/tests/cancel_test_helpers.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            cancellation_mode mode,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    cancellation_mode mode, grpc_channel_args* client_args,
+    grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s/%s", test_name, config.name,
           mode.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Cancel after accept with a writes closed, no payload
 static void test_cancel_after_accept_and_writes_closed(
-    grpc_end2end_test_config config, cancellation_mode mode) {
+    const CoreTestConfiguration& config, cancellation_mode mode) {
   grpc_op ops[6];
   grpc_op* op;
   grpc_call* c;
   grpc_call* s;
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_cancel_after_accept_and_writes_closed", mode,
-                 nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "test_cancel_after_accept_and_writes_closed",
+                      mode, nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_metadata_array initial_metadata_recv;
   grpc_metadata_array trailing_metadata_recv;
   grpc_metadata_array request_metadata_recv;
@@ -121,10 +78,10 @@
       grpc_raw_byte_buffer_create(&response_payload_slice, 1);
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -165,14 +122,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error = grpc_server_request_call(f.server, &s, &call_details,
-                                   &request_metadata_recv, f.cq, f.cq, tag(2));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(2));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -197,14 +155,14 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c, nullptr));
 
-  cqv.Expect(tag(3), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == mode.expect_status || status == GRPC_STATUS_INTERNAL);
@@ -223,12 +181,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void cancel_after_client_done(grpc_end2end_test_config config) {
+void cancel_after_client_done(const CoreTestConfiguration& config) {
   unsigned i;
 
   for (i = 0; i < GPR_ARRAY_SIZE(cancellation_modes); i++) {
diff --git a/test/core/end2end/tests/cancel_after_invoke.cc b/test/core/end2end/tests/cancel_after_invoke.cc
index 036c18a..7def372 100644
--- a/test/core/end2end/tests/cancel_after_invoke.cc
+++ b/test/core/end2end/tests/cancel_after_invoke.cc
@@ -20,6 +20,9 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -28,79 +31,35 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gpr/useful.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/tests/cancel_test_helpers.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            cancellation_mode mode,
-                                            size_t test_ops,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    cancellation_mode mode, size_t test_ops, grpc_channel_args* client_args,
+    grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s/%s [%" PRIdPTR " ops]", test_name,
           config.name, mode.name, test_ops);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev = grpc_completion_queue_next(
-      f->cq, grpc_timeout_seconds_to_deadline(5), nullptr);
-  GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
-  GPR_ASSERT(ev.tag == tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Cancel after invoke, no payload
-static void test_cancel_after_invoke(grpc_end2end_test_config config,
+static void test_cancel_after_invoke(const CoreTestConfiguration& config,
                                      cancellation_mode mode, size_t test_ops) {
   grpc_op ops[6];
   grpc_op* op;
   grpc_call* c;
-  grpc_end2end_test_fixture f = begin_test(config, "test_cancel_after_invoke",
-                                           mode, test_ops, nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "test_cancel_after_invoke", mode, test_ops,
+                      nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_metadata_array initial_metadata_recv;
   grpc_metadata_array trailing_metadata_recv;
   grpc_metadata_array request_metadata_recv;
@@ -114,10 +73,10 @@
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -158,12 +117,13 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, test_ops, tag(1), nullptr);
+  error = grpc_call_start_batch(c, ops, test_ops, grpc_core::CqVerifier::tag(1),
+                                nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c, nullptr));
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == mode.expect_status || status == GRPC_STATUS_INTERNAL);
@@ -178,12 +138,9 @@
   grpc_slice_unref(details);
 
   grpc_call_unref(c);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void cancel_after_invoke(grpc_end2end_test_config config) {
+void cancel_after_invoke(const CoreTestConfiguration& config) {
   unsigned i, j;
 
   for (j = 3; j < 6; j++) {
diff --git a/test/core/end2end/tests/cancel_after_round_trip.cc b/test/core/end2end/tests/cancel_after_round_trip.cc
index f0307b6..0bd4e94 100644
--- a/test/core/end2end/tests/cancel_after_round_trip.cc
+++ b/test/core/end2end/tests/cancel_after_round_trip.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -35,67 +37,21 @@
 #include "test/core/end2end/tests/cancel_test_helpers.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            cancellation_mode mode,
-                                            bool use_service_config,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    cancellation_mode mode, bool use_service_config,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s/%s/%s", test_name, config.name,
           mode.name, use_service_config ? "service_config" : "client_api");
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Cancel after accept, no payload
-static void test_cancel_after_round_trip(grpc_end2end_test_config config,
+static void test_cancel_after_round_trip(const CoreTestConfiguration& config,
                                          cancellation_mode mode,
                                          bool use_service_config) {
   grpc_op ops[6];
@@ -140,15 +96,15 @@
     args = grpc_channel_args_copy_and_add(args, &arg, 1);
   }
 
-  grpc_end2end_test_fixture f =
-      begin_test(config, "cancel_after_round_trip", mode, use_service_config,
-                 args, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "cancel_after_round_trip", mode,
+                      use_service_config, args, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
 
   gpr_timespec deadline = use_service_config
                               ? gpr_inf_future(GPR_CLOCK_MONOTONIC)
-                              : five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+                              : grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -180,15 +136,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -208,12 +164,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   grpc_byte_buffer_destroy(request_payload_recv);
@@ -235,8 +191,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c, nullptr));
@@ -253,12 +209,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(2), true);
-  cqv.Expect(tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
   cqv.Verify();
 
   GPR_ASSERT(status == mode.expect_status || status == GRPC_STATUS_INTERNAL);
@@ -283,12 +239,9 @@
     grpc_core::ExecCtx exec_ctx;
     grpc_channel_args_destroy(args);
   }
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void cancel_after_round_trip(grpc_end2end_test_config config) {
+void cancel_after_round_trip(const CoreTestConfiguration& config) {
   unsigned i;
 
   for (i = 0; i < GPR_ARRAY_SIZE(cancellation_modes); i++) {
diff --git a/test/core/end2end/tests/cancel_before_invoke.cc b/test/core/end2end/tests/cancel_before_invoke.cc
index f5d8580..2ce710f 100644
--- a/test/core/end2end/tests/cancel_before_invoke.cc
+++ b/test/core/end2end/tests/cancel_before_invoke.cc
@@ -20,6 +20,9 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -28,77 +31,32 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            size_t num_ops,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name, size_t num_ops,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s [%" PRIdPTR " ops]", test_name,
           config.name, num_ops);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Cancel before invoke
-static void test_cancel_before_invoke(grpc_end2end_test_config config,
+static void test_cancel_before_invoke(const CoreTestConfiguration& config,
                                       size_t test_ops) {
   grpc_op ops[6];
   grpc_op* op;
   grpc_call* c;
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "cancel_before_invoke", test_ops, nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_metadata_array initial_metadata_recv;
   grpc_metadata_array trailing_metadata_recv;
   grpc_metadata_array request_metadata_recv;
@@ -112,10 +70,10 @@
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK == grpc_call_cancel(c, nullptr));
@@ -158,14 +116,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, test_ops, tag(1), nullptr);
+  error = grpc_call_start_batch(c, ops, test_ops, grpc_core::CqVerifier::tag(1),
+                                nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Filter based stack tracks this as a failed op, promise based stack tracks
   // it as a successful one with a failed request. The latter probably makes
   // more sense, but since we can't tell from outside which case we have we
   // accept either.
-  cqv.Expect(tag(1), grpc_core::CqVerifier::AnyStatus());
+  cqv.Expect(grpc_core::CqVerifier::tag(1), grpc_core::CqVerifier::AnyStatus());
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_CANCELLED);
@@ -180,12 +139,9 @@
   grpc_slice_unref(details);
 
   grpc_call_unref(c);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void cancel_before_invoke(grpc_end2end_test_config config) {
+void cancel_before_invoke(const CoreTestConfiguration& config) {
   size_t i;
   for (i = 1; i <= 6; i++) {
     test_cancel_before_invoke(config, i);
diff --git a/test/core/end2end/tests/cancel_in_a_vacuum.cc b/test/core/end2end/tests/cancel_in_a_vacuum.cc
index be363f0..04ed84a 100644
--- a/test/core/end2end/tests/cancel_in_a_vacuum.cc
+++ b/test/core/end2end/tests/cancel_in_a_vacuum.cc
@@ -16,7 +16,8 @@
 //
 //
 
-#include <stdint.h>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -24,91 +25,44 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gpr/useful.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/tests/cancel_test_helpers.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            cancellation_mode mode,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    cancellation_mode mode, grpc_channel_args* client_args,
+    grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s/%s", test_name, config.name,
           mode.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Cancel and do nothing
-static void test_cancel_in_a_vacuum(grpc_end2end_test_config config,
+static void test_cancel_in_a_vacuum(const CoreTestConfiguration& config,
                                     cancellation_mode mode) {
   grpc_call* c;
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_cancel_in_a_vacuum", mode, nullptr, nullptr);
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c, nullptr));
 
   grpc_call_unref(c);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void cancel_in_a_vacuum(grpc_end2end_test_config config) {
+void cancel_in_a_vacuum(const CoreTestConfiguration& config) {
   unsigned i;
 
   for (i = 0; i < GPR_ARRAY_SIZE(cancellation_modes); i++) {
diff --git a/test/core/end2end/tests/cancel_with_status.cc b/test/core/end2end/tests/cancel_with_status.cc
index 75dca6a..7ea7d15 100644
--- a/test/core/end2end/tests/cancel_with_status.cc
+++ b/test/core/end2end/tests/cancel_with_status.cc
@@ -20,7 +20,8 @@
 #include <stdio.h>
 #include <string.h>
 
-#include <string>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -31,73 +32,27 @@
 #include <grpc/support/string_util.h>
 #include <grpc/support/time.h>
 
-#include "src/core/lib/surface/event_string.h"
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            size_t num_ops,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name, size_t num_ops,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s [%" PRIdPTR " ops]", test_name,
           config.name, num_ops);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev = grpc_completion_queue_next(
-      f->cq, grpc_timeout_seconds_to_deadline(5), nullptr);
-  gpr_log(GPR_DEBUG, "shutdown event: %s", grpc_event_string(&ev).c_str());
-  GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
-  GPR_ASSERT(ev.tag == tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_client(f);
-  shutdown_server(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture f, size_t num_ops) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f, size_t num_ops) {
   grpc_call* c;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -108,10 +63,10 @@
 
   gpr_log(GPR_DEBUG, "test with %" PRIuPTR " ops", num_ops);
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -141,7 +96,8 @@
   op->reserved = nullptr;
   op++;
   GPR_ASSERT(num_ops <= (size_t)(op - ops));
-  error = grpc_call_start_batch(c, ops, num_ops, tag(1), nullptr);
+  error = grpc_call_start_batch(c, ops, num_ops, grpc_core::CqVerifier::tag(1),
+                                nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   char* dynamic_string = gpr_strdup("xyz");
@@ -151,7 +107,7 @@
   // string, test this guarantee.
   gpr_free(dynamic_string);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -164,18 +120,14 @@
   grpc_call_unref(c);
 }
 
-static void test_invoke_simple_request(grpc_end2end_test_config config,
+static void test_invoke_simple_request(const CoreTestConfiguration& config,
                                        size_t num_ops) {
-  grpc_end2end_test_fixture f;
-
-  f = begin_test(config, "test_invoke_simple_request", num_ops, nullptr,
-                 nullptr);
-  simple_request_body(config, f, num_ops);
-  end_test(&f);
-  config.tear_down_data(&f);
+  auto f = begin_test(config, "test_invoke_simple_request", num_ops, nullptr,
+                      nullptr);
+  simple_request_body(config, f.get(), num_ops);
 }
 
-void cancel_with_status(grpc_end2end_test_config config) {
+void cancel_with_status(const CoreTestConfiguration& config) {
   size_t i;
   for (i = 1; i <= 4; i++) {
     test_invoke_simple_request(config, i);
diff --git a/test/core/end2end/tests/channelz.cc b/test/core/end2end/tests/channelz.cc
index 0049c19..8227832 100644
--- a/test/core/end2end/tests/channelz.cc
+++ b/test/core/end2end/tests/channelz.cc
@@ -20,6 +20,8 @@
 
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include <grpc/grpc.h>
@@ -37,68 +39,22 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void run_one_request(grpc_end2end_test_config /*config*/,
-                            grpc_end2end_test_fixture f,
-                            bool request_is_success) {
+static void run_one_request(const CoreTestConfiguration& /*config*/,
+                            CoreTestFixture* f, bool request_is_success) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -110,10 +66,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -145,15 +101,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -177,12 +133,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(details, "xyz"));
@@ -198,9 +154,7 @@
   grpc_call_unref(s);
 }
 
-static void test_channelz(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
-
+static void test_channelz(const CoreTestConfiguration& config) {
   grpc_arg arg[] = {
       grpc_channel_arg_integer_create(
           const_cast<char*>(GRPC_ARG_MAX_CHANNEL_TRACE_EVENT_MEMORY_PER_NODE),
@@ -209,13 +163,13 @@
           const_cast<char*>(GRPC_ARG_ENABLE_CHANNELZ), true)};
   grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
 
-  f = begin_test(config, "test_channelz", &args, &args);
+  auto f = begin_test(config, "test_channelz", &args, &args);
   grpc_core::channelz::ChannelNode* channelz_channel =
-      grpc_channel_get_channelz_node(f.client);
+      grpc_channel_get_channelz_node(f->client());
   GPR_ASSERT(channelz_channel != nullptr);
 
   grpc_core::channelz::ServerNode* channelz_server =
-      grpc_core::Server::FromC(f.server)->channelz_node();
+      grpc_core::Server::FromC(f->server())->channelz_node();
   GPR_ASSERT(channelz_server != nullptr);
 
   std::string json = channelz_channel->RenderJsonString();
@@ -225,14 +179,14 @@
   GPR_ASSERT(json.find("\"callsSucceeded\"") == json.npos);
 
   // one successful request
-  run_one_request(config, f, true);
+  run_one_request(config, f.get(), true);
 
   json = channelz_channel->RenderJsonString();
   GPR_ASSERT(json.find("\"callsStarted\":\"1\"") != json.npos);
   GPR_ASSERT(json.find("\"callsSucceeded\":\"1\"") != json.npos);
 
   // one failed request
-  run_one_request(config, f, false);
+  run_one_request(config, f.get(), false);
 
   json = channelz_channel->RenderJsonString();
   GPR_ASSERT(json.find("\"callsStarted\":\"2\"") != json.npos);
@@ -254,14 +208,10 @@
 
   json = channelz_server->RenderServerSockets(0, 100);
   GPR_ASSERT(json.find("\"end\":true") != json.npos);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-static void test_channelz_with_channel_trace(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
-
+static void test_channelz_with_channel_trace(
+    const CoreTestConfiguration& config) {
   grpc_arg arg[] = {
       grpc_channel_arg_integer_create(
           const_cast<char*>(GRPC_ARG_MAX_CHANNEL_TRACE_EVENT_MEMORY_PER_NODE),
@@ -270,16 +220,16 @@
           const_cast<char*>(GRPC_ARG_ENABLE_CHANNELZ), true)};
   grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
 
-  f = begin_test(config, "test_channelz_with_channel_trace", &args, &args);
+  auto f = begin_test(config, "test_channelz_with_channel_trace", &args, &args);
   grpc_core::channelz::ChannelNode* channelz_channel =
-      grpc_channel_get_channelz_node(f.client);
+      grpc_channel_get_channelz_node(f->client());
   GPR_ASSERT(channelz_channel != nullptr);
 
   grpc_core::channelz::ServerNode* channelz_server =
-      grpc_core::Server::FromC(f.server)->channelz_node();
+      grpc_core::Server::FromC(f->server())->channelz_node();
   GPR_ASSERT(channelz_server != nullptr);
 
-  run_one_request(config, f, true);
+  run_one_request(config, f.get(), true);
 
   std::string json = channelz_channel->RenderJsonString();
   GPR_ASSERT(json.find("\"trace\"") != json.npos);
@@ -290,14 +240,9 @@
   GPR_ASSERT(json.find("\"trace\"") != json.npos);
   GPR_ASSERT(json.find("\"description\":\"Server created\"") != json.npos);
   GPR_ASSERT(json.find("\"severity\":\"CT_INFO\"") != json.npos);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-static void test_channelz_disabled(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
-
+static void test_channelz_disabled(const CoreTestConfiguration& config) {
   grpc_arg arg[] = {
       grpc_channel_arg_integer_create(
           const_cast<char*>(GRPC_ARG_MAX_CHANNEL_TRACE_EVENT_MEMORY_PER_NODE),
@@ -306,18 +251,16 @@
           const_cast<char*>(GRPC_ARG_ENABLE_CHANNELZ), false)};
   grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
 
-  f = begin_test(config, "test_channelz_disabled", &args, &args);
+  auto f = begin_test(config, "test_channelz_disabled", &args, &args);
   grpc_core::channelz::ChannelNode* channelz_channel =
-      grpc_channel_get_channelz_node(f.client);
+      grpc_channel_get_channelz_node(f->client());
   GPR_ASSERT(channelz_channel == nullptr);
   // one successful request
-  run_one_request(config, f, true);
+  run_one_request(config, f.get(), true);
   GPR_ASSERT(channelz_channel == nullptr);
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void channelz(grpc_end2end_test_config config) {
+void channelz(const CoreTestConfiguration& config) {
   test_channelz(config);
   test_channelz_with_channel_trace(config);
   test_channelz_disabled(config);
diff --git a/test/core/end2end/tests/client_streaming.cc b/test/core/end2end/tests/client_streaming.cc
index 227a539..705a494 100644
--- a/test/core/end2end/tests/client_streaming.cc
+++ b/test/core/end2end/tests/client_streaming.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -27,77 +29,32 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Client streaming test where the client sends a bunch of messages and the
 // server reads them. After reading some messages, the server sends the status.
 // Client writes fail after that due to the end of stream and the client
 // subsequently requests and receives the status.
-static void test_client_streaming(grpc_end2end_test_config config,
+static void test_client_streaming(const CoreTestConfiguration& config,
                                   int messages) {
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_client_streaming", nullptr, nullptr);
+  auto f = begin_test(config, "test_client_streaming", nullptr, nullptr);
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -113,10 +70,10 @@
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -136,15 +93,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(100));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(100));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(100), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(100), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -154,12 +111,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(101),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(101), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(101), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   // Client writes bunch of messages and server reads them
@@ -173,7 +130,7 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
-                                  tag(103), nullptr);
+                                  grpc_core::CqVerifier::tag(103), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
     grpc_byte_buffer_destroy(request_payload);
 
@@ -185,10 +142,10 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
+                                  grpc_core::CqVerifier::tag(102), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv.Expect(tag(102), true);
-    cqv.Expect(tag(103), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(103), true);
     cqv.Verify();
     GPR_ASSERT(byte_buffer_eq_string(request_payload_recv, "hello world"));
     grpc_byte_buffer_destroy(request_payload_recv);
@@ -205,10 +162,10 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   cqv.Verify();
   // Do an empty verify to make sure that the client receives the status
   cqv.VerifyEmpty();
@@ -222,11 +179,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
   grpc_byte_buffer_destroy(request_payload);
-  cqv.Expect(tag(103), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), false);
   cqv.Verify();
 
   // Client sends close and requests status
@@ -243,10 +200,10 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
   cqv.Verify();
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
   GPR_ASSERT(0 == grpc_slice_str_cmp(details, "xyz"));
@@ -261,12 +218,9 @@
   grpc_metadata_array_destroy(&request_metadata_recv);
   grpc_call_details_destroy(&call_details);
   grpc_slice_unref(details);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void client_streaming(grpc_end2end_test_config config) {
+void client_streaming(const CoreTestConfiguration& config) {
   for (int i = 0; i < 10; i++) {
     test_client_streaming(config, i);
   }
diff --git a/test/core/end2end/tests/compressed_payload.cc b/test/core/end2end/tests/compressed_payload.cc
index be94933..7a72fe0 100644
--- a/test/core/end2end/tests/compressed_payload.cc
+++ b/test/core/end2end/tests/compressed_payload.cc
@@ -19,7 +19,9 @@
 #include <stdint.h>
 #include <string.h>
 
+#include <functional>
 #include <initializer_list>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_format.h"
@@ -42,66 +44,22 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(
-    grpc_end2end_test_config config, const char* test_name,
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
     const grpc_channel_args* client_args, const grpc_channel_args* server_args,
     bool decompress_in_core) {
-  grpc_end2end_test_fixture f;
   gpr_log(GPR_INFO, "Running test: %s%s/%s", test_name,
           decompress_in_core ? "" : "_with_decompression_disabled",
           config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 static void request_for_disabled_algorithm(
-    grpc_end2end_test_config config, const char* test_name,
+    const CoreTestConfiguration& config, const char* test_name,
     uint32_t send_flags_bitmask,
     grpc_compression_algorithm algorithm_to_disable,
     grpc_compression_algorithm requested_client_compression_algorithm,
@@ -113,7 +71,7 @@
   grpc_byte_buffer* request_payload;
   grpc_core::ChannelArgs client_args;
   grpc_core::ChannelArgs server_args;
-  grpc_end2end_test_fixture f;
+
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -150,14 +108,14 @@
         server_args.Set(GRPC_ARG_ENABLE_PER_MESSAGE_DECOMPRESSION, false);
   }
 
-  f = begin_test(config, test_name, client_args.ToC().get(),
-                 server_args.ToC().get(), decompress_in_core);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, test_name, client_args.ToC().get(),
+                      server_args.ToC().get(), decompress_in_core);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -165,9 +123,9 @@
   grpc_metadata_array_init(&request_metadata_recv);
   grpc_call_details_init(&call_details);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -203,12 +161,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(101), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   op = ops;
@@ -222,11 +180,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), false);
 
   op = ops;
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
@@ -234,11 +192,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
   cqv.Verify();
 
   // call was cancelled (closed) ...
@@ -266,12 +224,10 @@
   grpc_slice_unref(request_payload_slice);
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 static void request_with_payload_template_inner(
-    grpc_end2end_test_config config, const char* test_name,
+    const CoreTestConfiguration& config, const char* test_name,
     uint32_t client_send_flags_bitmask,
     grpc_compression_algorithm default_client_channel_compression_algorithm,
     grpc_compression_algorithm default_server_channel_compression_algorithm,
@@ -286,7 +242,7 @@
   grpc_byte_buffer* request_payload = nullptr;
   grpc_core::ChannelArgs client_args;
   grpc_core::ChannelArgs server_args;
-  grpc_end2end_test_fixture f;
+
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -325,14 +281,14 @@
     server_args =
         server_args.Set(GRPC_ARG_ENABLE_PER_MESSAGE_DECOMPRESSION, false);
   }
-  f = begin_test(config, test_name, client_args.ToC().get(),
-                 server_args.ToC().get(), decompress_in_core);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, test_name, client_args.ToC().get(),
+                      server_args.ToC().get(), decompress_in_core);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -349,10 +305,10 @@
     op->flags = client_send_flags_bitmask;
     op->reserved = nullptr;
     op++;
-    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                  nullptr);
+    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                  grpc_core::CqVerifier::tag(2), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv.Expect(tag(2), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   }
   memset(ops, 0, sizeof(ops));
   op = ops;
@@ -378,15 +334,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(100));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(100));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(100), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(100), true);
   cqv.Verify();
 
   GPR_ASSERT(grpc_core::BitCount(
@@ -418,8 +374,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(101),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(101), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
   for (int i = 0; i < 2; i++) {
     response_payload = grpc_raw_byte_buffer_create(&response_payload_slice, 1);
@@ -434,9 +390,9 @@
       op->reserved = nullptr;
       op++;
       error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
-                                    tag(2), nullptr);
+                                    grpc_core::CqVerifier::tag(2), nullptr);
       GPR_ASSERT(GRPC_CALL_OK == error);
-      cqv.Expect(tag(2), true);
+      cqv.Expect(grpc_core::CqVerifier::tag(2), true);
     }
 
     memset(ops, 0, sizeof(ops));
@@ -447,10 +403,10 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
+                                  grpc_core::CqVerifier::tag(102), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
-    cqv.Expect(tag(102), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(102), true);
     cqv.Verify();
 
     GPR_ASSERT(request_payload_recv->type == GRPC_BB_RAW);
@@ -467,7 +423,7 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(103), nullptr);
+                                  grpc_core::CqVerifier::tag(103), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
     memset(ops, 0, sizeof(ops));
@@ -477,12 +433,12 @@
     op->flags = 0;
     op->reserved = nullptr;
     op++;
-    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                  nullptr);
+    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                  grpc_core::CqVerifier::tag(3), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
-    cqv.Expect(tag(103), true);
-    cqv.Expect(tag(3), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(3), true);
     cqv.Verify();
 
     GPR_ASSERT(response_payload_recv->type == GRPC_BB_RAW);
@@ -513,8 +469,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(4), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -527,14 +483,14 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
-  cqv.Expect(tag(4), true);
-  cqv.Expect(tag(101), true);
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -550,13 +506,10 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 static void request_with_payload_template(
-    grpc_end2end_test_config config, const char* test_name,
+    const CoreTestConfiguration& config, const char* test_name,
     uint32_t client_send_flags_bitmask,
     grpc_compression_algorithm default_client_channel_compression_algorithm,
     grpc_compression_algorithm default_server_channel_compression_algorithm,
@@ -582,7 +535,7 @@
 }
 
 static void test_invoke_request_with_exceptionally_uncompressed_payload(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   request_with_payload_template(
       config, "test_invoke_request_with_exceptionally_uncompressed_payload",
       GRPC_WRITE_NO_COMPRESS, GRPC_COMPRESS_GZIP, GRPC_COMPRESS_GZIP,
@@ -591,7 +544,7 @@
 }
 
 static void test_invoke_request_with_uncompressed_payload(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   request_with_payload_template(
       config, "test_invoke_request_with_uncompressed_payload", 0,
       GRPC_COMPRESS_NONE, GRPC_COMPRESS_NONE, GRPC_COMPRESS_NONE,
@@ -600,7 +553,7 @@
 }
 
 static void test_invoke_request_with_compressed_payload(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   request_with_payload_template(
       config, "test_invoke_request_with_compressed_payload", 0,
       GRPC_COMPRESS_GZIP, GRPC_COMPRESS_GZIP, GRPC_COMPRESS_GZIP,
@@ -609,7 +562,7 @@
 }
 
 static void test_invoke_request_with_send_message_before_initial_metadata(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   request_with_payload_template(
       config, "test_invoke_request_with_compressed_payload", 0,
       GRPC_COMPRESS_GZIP, GRPC_COMPRESS_GZIP, GRPC_COMPRESS_GZIP,
@@ -618,7 +571,7 @@
 }
 
 static void test_invoke_request_with_server_level(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   request_with_payload_template(
       config, "test_invoke_request_with_server_level", 0, GRPC_COMPRESS_NONE,
       GRPC_COMPRESS_NONE, GRPC_COMPRESS_NONE, GRPC_COMPRESS_NONE /* ignored */,
@@ -626,7 +579,7 @@
 }
 
 static void test_invoke_request_with_compressed_payload_md_override(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_metadata gzip_compression_override;
   grpc_metadata identity_compression_override;
 
@@ -666,7 +619,7 @@
 }
 
 static void test_invoke_request_with_disabled_algorithm(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   request_for_disabled_algorithm(config,
                                  "test_invoke_request_with_disabled_algorithm",
                                  0, GRPC_COMPRESS_GZIP, GRPC_COMPRESS_GZIP,
@@ -677,7 +630,7 @@
                                  GRPC_STATUS_UNIMPLEMENTED, nullptr, true);
 }
 
-void compressed_payload(grpc_end2end_test_config config) {
+void compressed_payload(const CoreTestConfiguration& config) {
   test_invoke_request_with_exceptionally_uncompressed_payload(config);
   test_invoke_request_with_uncompressed_payload(config);
   test_invoke_request_with_compressed_payload(config);
diff --git a/test/core/end2end/tests/connectivity.cc b/test/core/end2end/tests/connectivity.cc
index 831b7ed..02c8922 100644
--- a/test/core/end2end/tests/connectivity.cc
+++ b/test/core/end2end/tests/connectivity.cc
@@ -16,7 +16,8 @@
 //
 //
 
-#include <stdint.h>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
@@ -30,8 +31,6 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 typedef struct {
   gpr_event started;
   grpc_channel* channel;
@@ -57,26 +56,26 @@
   ev = grpc_completion_queue_next(ce->cq, gpr_inf_future(GPR_CLOCK_MONOTONIC),
                                   nullptr);
   GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
-  GPR_ASSERT(ev.tag == tag(1));
+  GPR_ASSERT(ev.tag == grpc_core::CqVerifier::tag(1));
   GPR_ASSERT(ev.success == 0);
 }
 
-static void test_connectivity(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = config.create_fixture(nullptr, nullptr);
+static void test_connectivity(const CoreTestConfiguration& config) {
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
   grpc_connectivity_state state;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   child_events ce;
 
   auto client_args = grpc_core::ChannelArgs()
                          .Set(GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS, 1000)
                          .Set(GRPC_ARG_MAX_RECONNECT_BACKOFF_MS, 1000)
-                         .Set(GRPC_ARG_MIN_RECONNECT_BACKOFF_MS, 5000)
-                         .ToC();
+                         .Set(GRPC_ARG_MIN_RECONNECT_BACKOFF_MS, 5000);
 
-  config.init_client(&f, client_args.get());
+  f->InitClient(client_args);
 
-  ce.channel = f.client;
-  ce.cq = f.cq;
+  ce.channel = f->client();
+  ce.cq = f->cq();
   gpr_event_init(&ce.started);
   grpc_core::Thread thd("grpc_connectivity", child_thread, &ce);
   thd.Start();
@@ -84,49 +83,50 @@
   gpr_event_wait(&ce.started, gpr_inf_future(GPR_CLOCK_MONOTONIC));
 
   // channels should start life in IDLE, and stay there
-  GPR_ASSERT(grpc_channel_check_connectivity_state(f.client, 0) ==
+  GPR_ASSERT(grpc_channel_check_connectivity_state(f->client(), 0) ==
              GRPC_CHANNEL_IDLE);
   gpr_sleep_until(grpc_timeout_milliseconds_to_deadline(100));
-  GPR_ASSERT(grpc_channel_check_connectivity_state(f.client, 0) ==
+  GPR_ASSERT(grpc_channel_check_connectivity_state(f->client(), 0) ==
              GRPC_CHANNEL_IDLE);
 
   // start watching for a change
   gpr_log(GPR_DEBUG, "watching");
-  grpc_channel_watch_connectivity_state(
-      f.client, GRPC_CHANNEL_IDLE, gpr_now(GPR_CLOCK_MONOTONIC), f.cq, tag(1));
+  grpc_channel_watch_connectivity_state(f->client(), GRPC_CHANNEL_IDLE,
+                                        gpr_now(GPR_CLOCK_MONOTONIC), f->cq(),
+                                        grpc_core::CqVerifier::tag(1));
 
   // eventually the child thread completion should trigger
   thd.Join();
 
   // check that we're still in idle, and start connecting
-  GPR_ASSERT(grpc_channel_check_connectivity_state(f.client, 1) ==
+  GPR_ASSERT(grpc_channel_check_connectivity_state(f->client(), 1) ==
              GRPC_CHANNEL_IDLE);
   // start watching for a change
-  grpc_channel_watch_connectivity_state(f.client, GRPC_CHANNEL_IDLE,
+  grpc_channel_watch_connectivity_state(f->client(), GRPC_CHANNEL_IDLE,
                                         grpc_timeout_seconds_to_deadline(10),
-                                        f.cq, tag(2));
+                                        f->cq(), grpc_core::CqVerifier::tag(2));
 
   // and now the watch should trigger
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
-  state = grpc_channel_check_connectivity_state(f.client, 0);
+  state = grpc_channel_check_connectivity_state(f->client(), 0);
   GPR_ASSERT(state == GRPC_CHANNEL_TRANSIENT_FAILURE ||
              state == GRPC_CHANNEL_CONNECTING);
 
   // quickly followed by a transition to TRANSIENT_FAILURE
-  grpc_channel_watch_connectivity_state(f.client, GRPC_CHANNEL_CONNECTING,
+  grpc_channel_watch_connectivity_state(f->client(), GRPC_CHANNEL_CONNECTING,
                                         grpc_timeout_seconds_to_deadline(10),
-                                        f.cq, tag(3));
-  cqv.Expect(tag(3), true);
+                                        f->cq(), grpc_core::CqVerifier::tag(3));
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
   cqv.Verify();
-  state = grpc_channel_check_connectivity_state(f.client, 0);
+  state = grpc_channel_check_connectivity_state(f->client(), 0);
   GPR_ASSERT(state == GRPC_CHANNEL_TRANSIENT_FAILURE ||
              state == GRPC_CHANNEL_CONNECTING);
 
   gpr_log(GPR_DEBUG, "*** STARTING SERVER ***");
 
   // now let's bring up a server to connect to
-  config.init_server(&f, nullptr);
+  f->InitServer(grpc_core::ChannelArgs());
 
   gpr_log(GPR_DEBUG, "*** STARTED SERVER ***");
 
@@ -134,10 +134,11 @@
   // READY is reached
   while (state != GRPC_CHANNEL_READY) {
     grpc_channel_watch_connectivity_state(
-        f.client, state, grpc_timeout_seconds_to_deadline(10), f.cq, tag(4));
-    cqv.Expect(tag(4), true);
+        f->client(), state, grpc_timeout_seconds_to_deadline(10), f->cq(),
+        grpc_core::CqVerifier::tag(4));
+    cqv.Expect(grpc_core::CqVerifier::tag(4), true);
     cqv.Verify(grpc_core::Duration::Seconds(20));
-    state = grpc_channel_check_connectivity_state(f.client, 0);
+    state = grpc_channel_check_connectivity_state(f->client(), 0);
     GPR_ASSERT(state == GRPC_CHANNEL_READY ||
                state == GRPC_CHANNEL_CONNECTING ||
                state == GRPC_CHANNEL_TRANSIENT_FAILURE);
@@ -147,29 +148,19 @@
   // we should go immediately to TRANSIENT_FAILURE
   gpr_log(GPR_DEBUG, "*** SHUTTING DOWN SERVER ***");
 
-  grpc_channel_watch_connectivity_state(f.client, GRPC_CHANNEL_READY,
+  grpc_channel_watch_connectivity_state(f->client(), GRPC_CHANNEL_READY,
                                         grpc_timeout_seconds_to_deadline(10),
-                                        f.cq, tag(5));
+                                        f->cq(), grpc_core::CqVerifier::tag(5));
 
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(0xdead));
 
-  cqv.Expect(tag(5), true);
-  cqv.Expect(tag(0xdead), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(5), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0xdead), true);
   cqv.Verify();
-  state = grpc_channel_check_connectivity_state(f.client, 0);
+  state = grpc_channel_check_connectivity_state(f->client(), 0);
   GPR_ASSERT(state == GRPC_CHANNEL_TRANSIENT_FAILURE ||
              state == GRPC_CHANNEL_CONNECTING || state == GRPC_CHANNEL_IDLE);
-
-  // cleanup server
-  grpc_server_destroy(f.server);
-
-  gpr_log(GPR_DEBUG, "*** SHUTDOWN SERVER ***");
-
-  grpc_channel_destroy(f.client);
-  grpc_completion_queue_shutdown(f.cq);
-  grpc_completion_queue_destroy(f.cq);
-
-  config.tear_down_data(&f);
 }
 
 static void cb_watch_connectivity(grpc_completion_queue_functor* functor,
@@ -193,16 +184,17 @@
 }
 
 static void test_watch_connectivity_cq_callback(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   CallbackContext cb_ctx(cb_watch_connectivity);
   CallbackContext cb_shutdown_ctx(cb_shutdown);
   grpc_completion_queue* cq;
-  grpc_end2end_test_fixture f = config.create_fixture(nullptr, nullptr);
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
 
-  config.init_client(&f, nullptr);
+  f->InitClient(grpc_core::ChannelArgs());
 
   // start connecting
-  grpc_channel_check_connectivity_state(f.client, 1);
+  grpc_channel_check_connectivity_state(f->client(), 1);
 
   // create the cq callback
   cq = grpc_completion_queue_create_for_callback(&cb_shutdown_ctx.functor,
@@ -210,7 +202,7 @@
 
   // start watching for any change, cb is immediately called
   // and no dead lock should be raised
-  grpc_channel_watch_connectivity_state(f.client, GRPC_CHANNEL_IDLE,
+  grpc_channel_watch_connectivity_state(f->client(), GRPC_CHANNEL_IDLE,
                                         grpc_timeout_seconds_to_deadline(3), cq,
                                         &cb_ctx.functor);
 
@@ -224,18 +216,10 @@
   grpc_completion_queue_shutdown(cq);
   gpr_event_wait(&cb_shutdown_ctx.finished,
                  gpr_inf_future(GPR_CLOCK_MONOTONIC));
-
-  // cleanup
-  grpc_channel_destroy(f.client);
   grpc_completion_queue_destroy(cq);
-
-  // cq is not used in this test
-  grpc_completion_queue_destroy(f.cq);
-
-  config.tear_down_data(&f);
 }
 
-void connectivity(grpc_end2end_test_config config) {
+void connectivity(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION);
   test_connectivity(config);
   test_watch_connectivity_cq_callback(config);
diff --git a/test/core/end2end/tests/default_host.cc b/test/core/end2end/tests/default_host.cc
index 066ffd0..28f3c77 100644
--- a/test/core/end2end/tests/default_host.cc
+++ b/test/core/end2end/tests/default_host.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -27,72 +29,27 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_client(&f, client_args);
-  config.init_server(&f, server_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void test_invoke_simple_request(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
+static void test_invoke_simple_request(const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -105,10 +62,10 @@
   int was_cancelled = 2;
   char* peer;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   peer = grpc_call_get_peer(c);
@@ -144,15 +101,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(error == GRPC_CALL_OK);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(error == GRPC_CALL_OK);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -184,12 +141,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(error == GRPC_CALL_OK);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -213,12 +170,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void default_host(grpc_end2end_test_config config) {
+void default_host(const CoreTestConfiguration& config) {
   test_invoke_simple_request(config);
 }
 
diff --git a/test/core/end2end/tests/disappearing_server.cc b/test/core/end2end/tests/disappearing_server.cc
index 2c866fa..f8783c2 100644
--- a/test/core/end2end/tests/disappearing_server.cc
+++ b/test/core/end2end/tests/disappearing_server.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -26,51 +28,14 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void do_request_and_shutdown_server(grpc_end2end_test_config /*config*/,
-                                           grpc_end2end_test_fixture* f,
-                                           grpc_core::CqVerifier& cqv) {
+static void do_request_and_shutdown_server(
+    const CoreTestConfiguration& /*config*/, CoreTestFixture* f,
+    grpc_core::CqVerifier& cqv) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -84,9 +49,9 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f->client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                               f->cq, grpc_slice_from_static_string("/foo"),
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
@@ -118,20 +83,21 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f->server, &s, &call_details,
-                               &request_metadata_recv, f->cq, f->cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // should be able to shut down the server early
   // - and still complete the request
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(1000));
 
   memset(ops, 0, sizeof(ops));
   op = ops;
@@ -153,13 +119,13 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
-  cqv.Expect(tag(1000), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1000), true);
   cqv.Verify();
   // Please refer https://github.com/grpc/grpc/issues/21221 for additional
   // details.
@@ -185,28 +151,26 @@
   grpc_call_unref(s);
 }
 
-static void disappearing_server_test(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = config.create_fixture(nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+static void disappearing_server_test(const CoreTestConfiguration& config) {
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
+  grpc_core::CqVerifier cqv(f->cq());
 
   gpr_log(GPR_INFO, "Running test: %s/%s", "disappearing_server_test",
           config.name);
 
-  config.init_client(&f, nullptr);
-  config.init_server(&f, nullptr);
+  f->InitClient(grpc_core::ChannelArgs());
+  f->InitServer(grpc_core::ChannelArgs());
 
-  do_request_and_shutdown_server(config, &f, cqv);
+  do_request_and_shutdown_server(config, f.get(), cqv);
 
   // now destroy and recreate the server
-  config.init_server(&f, nullptr);
+  f->InitServer(grpc_core::ChannelArgs());
 
-  do_request_and_shutdown_server(config, &f, cqv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
+  do_request_and_shutdown_server(config, f.get(), cqv);
 }
 
-void disappearing_server(grpc_end2end_test_config config) {
+void disappearing_server(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION);
 #ifndef GPR_WINDOWS  // b/148110727 for more details
   disappearing_server_test(config);
diff --git a/test/core/end2end/tests/empty_batch.cc b/test/core/end2end/tests/empty_batch.cc
index 192d114..de459b7 100644
--- a/test/core/end2end/tests/empty_batch.cc
+++ b/test/core/end2end/tests/empty_batch.cc
@@ -16,7 +16,8 @@
 //
 //
 
-#include <stdint.h>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -24,97 +25,50 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void empty_batch_body(grpc_end2end_test_config /*config*/,
-                             grpc_end2end_test_fixture f) {
+static void empty_batch_body(const CoreTestConfiguration& /*config*/,
+                             CoreTestFixture* f) {
   grpc_call* c;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_call_error error;
   grpc_op* op = nullptr;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
-  error = grpc_call_start_batch(c, op, 0, tag(1), nullptr);
+  error =
+      grpc_call_start_batch(c, op, 0, grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   grpc_call_unref(c);
 }
 
-static void test_invoke_empty_body(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
-
-  f = begin_test(config, "test_invoke_empty_body", nullptr, nullptr);
-  empty_batch_body(config, f);
-  end_test(&f);
-  config.tear_down_data(&f);
+static void test_invoke_empty_body(const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "test_invoke_empty_body", nullptr, nullptr);
+  empty_batch_body(config, f.get());
 }
 
-void empty_batch(grpc_end2end_test_config config) {
+void empty_batch(const CoreTestConfiguration& config) {
   test_invoke_empty_body(config);
 }
 
diff --git a/test/core/end2end/tests/filter_causes_close.cc b/test/core/end2end/tests/filter_causes_close.cc
index e8bdbea..0ad69dc 100644
--- a/test/core/end2end/tests/filter_causes_close.cc
+++ b/test/core/end2end/tests/filter_causes_close.cc
@@ -19,6 +19,9 @@
 #include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include "absl/status/status.h"
 
 #include <grpc/byte_buffer.h>
@@ -29,6 +32,7 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/channel_fwd.h"
 #include "src/core/lib/channel/channel_stack.h"
 #include "src/core/lib/channel/channel_stack_builder.h"
@@ -43,73 +47,27 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Simple request via a server filter that always closes the stream.
-static void test_request(grpc_end2end_test_config config) {
+static void test_request(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
-  grpc_end2end_test_fixture f =
-      begin_test(config, "filter_causes_close", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "filter_causes_close", nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -121,10 +79,10 @@
   grpc_call_error error;
   grpc_slice details;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -161,22 +119,24 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_PERMISSION_DENIED);
   GPR_ASSERT(0 ==
              grpc_slice_str_cmp(details, "Failure that's not preventable."));
 
+  f->ShutdownServer();
+
   grpc_slice_unref(details);
   grpc_metadata_array_destroy(&initial_metadata_recv);
   grpc_metadata_array_destroy(&trailing_metadata_recv);
@@ -187,9 +147,6 @@
 
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 //******************************************************************************
@@ -262,7 +219,7 @@
 // Registration
 //
 
-void filter_causes_close(grpc_end2end_test_config config) {
+void filter_causes_close(const CoreTestConfiguration& config) {
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
         grpc_core::BuildCoreConfiguration(builder);
diff --git a/test/core/end2end/tests/filter_context.cc b/test/core/end2end/tests/filter_context.cc
index dc894f0..d59bec9 100644
--- a/test/core/end2end/tests/filter_context.cc
+++ b/test/core/end2end/tests/filter_context.cc
@@ -17,11 +17,12 @@
 //
 
 #include <limits.h>
-#include <stdint.h>
 #include <string.h>
 
 #include <algorithm>
+#include <functional>
 #include <initializer_list>
+#include <memory>
 #include <vector>
 
 #include "absl/status/status.h"
@@ -34,6 +35,7 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/channel_fwd.h"
 #include "src/core/lib/channel/channel_stack.h"
 #include "src/core/lib/channel/channel_stack_builder.h"
@@ -49,74 +51,28 @@
 
 enum { TIMEOUT = 200000 };
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Simple request to test that filters see a consistent view of the
 // call context.
-static void test_request(grpc_end2end_test_config config) {
+static void test_request(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
-  grpc_end2end_test_fixture f =
-      begin_test(config, "filter_context", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "filter_context", nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -129,10 +85,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -169,16 +125,16 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -201,12 +157,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -223,9 +179,6 @@
 
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 //******************************************************************************
@@ -288,7 +241,7 @@
 // Registration
 //
 
-void filter_context(grpc_end2end_test_config config) {
+void filter_context(const CoreTestConfiguration& config) {
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
         grpc_core::BuildCoreConfiguration(builder);
diff --git a/test/core/end2end/tests/filter_init_fails.cc b/test/core/end2end/tests/filter_init_fails.cc
index 7665b72..ddae01f 100644
--- a/test/core/end2end/tests/filter_init_fails.cc
+++ b/test/core/end2end/tests/filter_init_fails.cc
@@ -17,10 +17,11 @@
 //
 
 #include <limits.h>
-#include <stdint.h>
 #include <string.h>
 
 #include <algorithm>
+#include <functional>
+#include <memory>
 #include <vector>
 
 #include "absl/status/status.h"
@@ -33,6 +34,7 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/channel_fwd.h"
 #include "src/core/lib/channel/channel_stack.h"
 #include "src/core/lib/channel/channel_stack_builder.h"
@@ -52,74 +54,28 @@
 static bool g_enable_client_subchannel_filter = false;
 static bool g_channel_filter_init_failure = false;
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Simple request via a SERVER_CHANNEL filter that always fails to
 // initialize the call.
-static void test_server_channel_filter(grpc_end2end_test_config config) {
+static void test_server_channel_filter(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
-  grpc_end2end_test_fixture f =
-      begin_test(config, "filter_init_fails", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "filter_init_fails", nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -131,10 +87,10 @@
   grpc_call_error error;
   grpc_slice details;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -171,16 +127,16 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   if (g_channel_filter_init_failure) {
@@ -195,6 +151,8 @@
     GPR_ASSERT(0 == grpc_slice_str_cmp(details, "access denied"));
   }
 
+  f->ShutdownServer();
+
   grpc_slice_unref(details);
   grpc_metadata_array_destroy(&initial_metadata_recv);
   grpc_metadata_array_destroy(&trailing_metadata_recv);
@@ -205,23 +163,19 @@
 
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 // Simple request via a CLIENT_CHANNEL or CLIENT_DIRECT_CHANNEL filter
 // that always fails to initialize the call.
-static void test_client_channel_filter(grpc_end2end_test_config config) {
+static void test_client_channel_filter(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
-  gpr_timespec deadline = five_seconds_from_now();
-  grpc_end2end_test_fixture f =
-      begin_test(config, "filter_init_fails", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  auto f = begin_test(config, "filter_init_fails", nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -233,9 +187,9 @@
   grpc_call_error error;
   grpc_slice details;
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -272,11 +226,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   if (g_channel_filter_init_failure) {
@@ -286,6 +240,8 @@
     GPR_ASSERT(0 == grpc_slice_str_cmp(details, "access denied"));
   }
 
+  f->ShutdownServer();
+
   grpc_slice_unref(details);
   grpc_metadata_array_destroy(&initial_metadata_recv);
   grpc_metadata_array_destroy(&trailing_metadata_recv);
@@ -296,23 +252,19 @@
 
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 // Simple request via a CLIENT_SUBCHANNEL filter that always fails to
 // initialize the call.
-static void test_client_subchannel_filter(grpc_end2end_test_config config) {
+static void test_client_subchannel_filter(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
-  gpr_timespec deadline = five_seconds_from_now();
-  grpc_end2end_test_fixture f =
-      begin_test(config, "filter_init_fails", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  auto f = begin_test(config, "filter_init_fails", nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -324,9 +276,9 @@
   grpc_call_error error;
   grpc_slice details;
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -364,11 +316,11 @@
   op->reserved = nullptr;
   op++;
 
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   if (g_channel_filter_init_failure) {
@@ -386,16 +338,16 @@
   grpc_slice_unref(details);
   details = grpc_empty_slice();
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   if (g_channel_filter_init_failure) {
@@ -415,9 +367,6 @@
 
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 //******************************************************************************
@@ -460,7 +409,7 @@
 // Registration
 //
 
-static void filter_init_fails_internal(grpc_end2end_test_config config) {
+static void filter_init_fails_internal(const CoreTestConfiguration& config) {
   gpr_log(GPR_INFO, "Testing SERVER_CHANNEL filter.");
   g_enable_server_channel_filter = true;
   test_server_channel_filter(config);
@@ -485,7 +434,7 @@
   }
 }
 
-void filter_init_fails(grpc_end2end_test_config config) {
+void filter_init_fails(const CoreTestConfiguration& config) {
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
         grpc_core::BuildCoreConfiguration(builder);
@@ -512,7 +461,7 @@
         register_stage(GRPC_CLIENT_DIRECT_CHANNEL,
                        &g_enable_client_channel_filter);
       },
-      [config] { filter_init_fails_internal(config); });
+      [&config] { filter_init_fails_internal(config); });
 }
 
 void filter_init_fails_pre_init(void) {}
diff --git a/test/core/end2end/tests/filter_latency.cc b/test/core/end2end/tests/filter_latency.cc
index 6bfdaa2..b04121d 100644
--- a/test/core/end2end/tests/filter_latency.cc
+++ b/test/core/end2end/tests/filter_latency.cc
@@ -17,10 +17,11 @@
 //
 
 #include <limits.h>
-#include <stdint.h>
 #include <string.h>
 
 #include <algorithm>
+#include <functional>
+#include <memory>
 #include <vector>
 
 #include "absl/status/status.h"
@@ -34,6 +35,7 @@
 #include <grpc/support/sync.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/channel_fwd.h"
 #include "src/core/lib/channel/channel_stack.h"
 #include "src/core/lib/channel/channel_stack_builder.h"
@@ -51,73 +53,27 @@
 static gpr_timespec g_client_latency;
 static gpr_timespec g_server_latency;
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Simple request via a server filter that saves the reported latency value.
-static void test_request(grpc_end2end_test_config config) {
+static void test_request(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
-  grpc_end2end_test_fixture f =
-      begin_test(config, "filter_latency", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "filter_latency", nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -136,10 +92,10 @@
   gpr_mu_unlock(&g_mu);
   const gpr_timespec start_time = gpr_now(GPR_CLOCK_REALTIME);
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -176,16 +132,16 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -208,12 +164,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -231,9 +187,6 @@
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
 
-  end_test(&f);
-  config.tear_down_data(&f);
-
   const gpr_timespec end_time = gpr_now(GPR_CLOCK_REALTIME);
   const gpr_timespec max_latency = gpr_time_sub(end_time, start_time);
 
@@ -307,7 +260,7 @@
 // Registration
 //
 
-void filter_latency(grpc_end2end_test_config config) {
+void filter_latency(const CoreTestConfiguration& config) {
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
         grpc_core::BuildCoreConfiguration(builder);
diff --git a/test/core/end2end/tests/filter_status_code.cc b/test/core/end2end/tests/filter_status_code.cc
index bc79cb1..89e8f65 100644
--- a/test/core/end2end/tests/filter_status_code.cc
+++ b/test/core/end2end/tests/filter_status_code.cc
@@ -25,10 +25,11 @@
 //
 
 #include <limits.h>
-#include <stdint.h>
 #include <string.h>
 
 #include <algorithm>
+#include <functional>
+#include <memory>
 #include <vector>
 
 #include "absl/status/status.h"
@@ -41,6 +42,7 @@
 #include <grpc/support/sync.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/channel/channel_fwd.h"
 #include "src/core/lib/channel/channel_stack.h"
 #include "src/core/lib/channel/channel_stack_builder.h"
@@ -65,72 +67,26 @@
 static grpc_status_code g_client_status_code;
 static grpc_status_code g_server_status_code;
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Simple request via a server filter that saves the reported status code.
-static void test_request(grpc_end2end_test_config config) {
+static void test_request(const CoreTestConfiguration& config) {
   g_client_code_recv = false;
   g_server_code_recv = false;
 
   grpc_call* c;
   grpc_call* s;
-  grpc_end2end_test_fixture f =
-      begin_test(config, "filter_status_code", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "filter_status_code", nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -149,10 +105,10 @@
   g_server_status_code = GRPC_STATUS_OK;
   gpr_mu_unlock(&g_mu);
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
   gpr_mu_lock(&g_mu);
   g_client_call_stack = grpc_call_get_call_stack(c);
@@ -187,16 +143,16 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   gpr_mu_lock(&g_mu);
@@ -223,12 +179,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -243,9 +199,6 @@
   grpc_call_unref(s);
   grpc_call_unref(c);
 
-  end_test(&f);
-  config.tear_down_data(&f);
-
   // Perform checks after test tear-down
   // Guards against the case that there's outstanding channel-related work on a
   // call prior to verification
@@ -367,7 +320,7 @@
 // Registration
 //
 
-void filter_status_code(grpc_end2end_test_config config) {
+void filter_status_code(const CoreTestConfiguration& config) {
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
         grpc_core::BuildCoreConfiguration(builder);
@@ -390,7 +343,7 @@
         register_stage(GRPC_CLIENT_DIRECT_CHANNEL, &test_client_filter);
         register_stage(GRPC_SERVER_CHANNEL, &test_server_filter);
       },
-      [config] { test_request(config); });
+      [&config] { test_request(config); });
 }
 
 void filter_status_code_pre_init(void) {
diff --git a/test/core/end2end/tests/filtered_metadata.cc b/test/core/end2end/tests/filtered_metadata.cc
index 8a91342..025e5aa 100644
--- a/test/core/end2end/tests/filtered_metadata.cc
+++ b/test/core/end2end/tests/filtered_metadata.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -26,69 +28,25 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Request/response with metadata which should be filtered
 static void test_request_response_with_metadata_to_be_filtered(
-    grpc_end2end_test_config config, const char* filtered_md_key,
+    const CoreTestConfiguration& config, const char* filtered_md_key,
     const char* filter_md_value) {
   grpc_call* c;
   grpc_call* s;
@@ -104,10 +62,10 @@
                              {grpc_slice_from_static_string(filtered_md_key),
                               grpc_slice_from_static_string(filter_md_value),
                               {{nullptr, nullptr, nullptr, nullptr}}}};
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_request_response_with_metadata_to_be_filtered",
                  nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -119,10 +77,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -154,15 +112,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -173,11 +131,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -195,12 +153,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -222,12 +180,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void filtered_metadata(grpc_end2end_test_config config) {
+void filtered_metadata(const CoreTestConfiguration& config) {
   test_request_response_with_metadata_to_be_filtered(config, "content-length",
                                                      "45");
 }
diff --git a/test/core/end2end/tests/graceful_server_shutdown.cc b/test/core/end2end/tests/graceful_server_shutdown.cc
index ba094ff..494dd3d 100644
--- a/test/core/end2end/tests/graceful_server_shutdown.cc
+++ b/test/core/end2end/tests/graceful_server_shutdown.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -26,68 +28,30 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 static void test_early_server_shutdown_finishes_inflight_calls(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_early_server_shutdown_finishes_inflight_calls",
                  nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -99,10 +63,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = n_seconds_from_now(10);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(10);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -134,19 +98,20 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // shutdown and destroy the server
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(0xdead));
   cqv.VerifyEmpty();
 
   memset(ops, 0, sizeof(ops));
@@ -169,13 +134,13 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(0xdead), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(0xdead), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -191,12 +156,9 @@
   grpc_call_details_destroy(&call_details);
 
   grpc_call_unref(c);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void graceful_server_shutdown(grpc_end2end_test_config config) {
+void graceful_server_shutdown(const CoreTestConfiguration& config) {
   test_early_server_shutdown_finishes_inflight_calls(config);
 }
 
diff --git a/test/core/end2end/tests/grpc_authz.cc b/test/core/end2end/tests/grpc_authz.cc
index 90479de..667990b 100644
--- a/test/core/end2end/tests/grpc_authz.cc
+++ b/test/core/end2end/tests/grpc_authz.cc
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <stdint.h>
 #include <string.h>
 
 #include <functional>
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -41,63 +41,19 @@
 #include "test/core/util/test_config.h"
 #include "test/core/util/tls_utils.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "%s", std::string(80, '*').c_str());
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void test_allow_authorized_request(grpc_end2end_test_fixture f) {
+static void test_allow_authorized_request(CoreTestFixture* f) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -112,12 +68,12 @@
   grpc_slice details = grpc_empty_slice();
   int was_cancelled = 2;
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -149,15 +105,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -180,12 +136,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
   GPR_ASSERT(GRPC_STATUS_OK == status);
   GPR_ASSERT(0 == grpc_slice_str_cmp(details, "xyz"));
@@ -201,7 +157,7 @@
   grpc_call_unref(s);
 }
 
-static void test_deny_unauthorized_request(grpc_end2end_test_fixture f) {
+static void test_deny_unauthorized_request(CoreTestFixture* f) {
   grpc_call* c;
   grpc_op ops[6];
   grpc_op* op;
@@ -212,12 +168,12 @@
   grpc_call_error error;
   grpc_slice details = grpc_empty_slice();
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -247,10 +203,10 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(GRPC_STATUS_PERMISSION_DENIED == status);
@@ -266,7 +222,7 @@
 }
 
 static void test_static_init_allow_authorized_request(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   const char* authz_policy =
       "{"
       "  \"name\": \"authz\","
@@ -294,18 +250,14 @@
   };
   grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_static_init_allow_authorized_request", nullptr,
-                 &server_args);
+  auto f = begin_test(config, "test_static_init_allow_authorized_request",
+                      nullptr, &server_args);
   grpc_authorization_policy_provider_release(provider);
-  test_allow_authorized_request(f);
-
-  end_test(&f);
-  config.tear_down_data(&f);
+  test_allow_authorized_request(f.get());
 }
 
 static void test_static_init_deny_unauthorized_request(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   const char* authz_policy =
       "{"
       "  \"name\": \"authz\","
@@ -343,18 +295,14 @@
   };
   grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_static_init_deny_unauthorized_request", nullptr,
-                 &server_args);
+  auto f = begin_test(config, "test_static_init_deny_unauthorized_request",
+                      nullptr, &server_args);
   grpc_authorization_policy_provider_release(provider);
-  test_deny_unauthorized_request(f);
-
-  end_test(&f);
-  config.tear_down_data(&f);
+  test_deny_unauthorized_request(f.get());
 }
 
 static void test_static_init_deny_request_no_match_in_policy(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   const char* authz_policy =
       "{"
       "  \"name\": \"authz\","
@@ -382,18 +330,15 @@
   };
   grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_static_init_deny_request_no_match_in_policy",
                  nullptr, &server_args);
   grpc_authorization_policy_provider_release(provider);
-  test_deny_unauthorized_request(f);
-
-  end_test(&f);
-  config.tear_down_data(&f);
+  test_deny_unauthorized_request(f.get());
 }
 
 static void test_file_watcher_init_allow_authorized_request(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   const char* authz_policy =
       "{"
       "  \"name\": \"authz\","
@@ -423,18 +368,14 @@
   };
   grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_file_watcher_init_allow_authorized_request",
-                 nullptr, &server_args);
+  auto f = begin_test(config, "test_file_watcher_init_allow_authorized_request",
+                      nullptr, &server_args);
   grpc_authorization_policy_provider_release(provider);
-  test_allow_authorized_request(f);
-
-  end_test(&f);
-  config.tear_down_data(&f);
+  test_allow_authorized_request(f.get());
 }
 
 static void test_file_watcher_init_deny_unauthorized_request(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   const char* authz_policy =
       "{"
       "  \"name\": \"authz\","
@@ -474,18 +415,15 @@
   };
   grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_file_watcher_init_deny_unauthorized_request",
                  nullptr, &server_args);
   grpc_authorization_policy_provider_release(provider);
-  test_deny_unauthorized_request(f);
-
-  end_test(&f);
-  config.tear_down_data(&f);
+  test_deny_unauthorized_request(f.get());
 }
 
 static void test_file_watcher_init_deny_request_no_match_in_policy(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   const char* authz_policy =
       "{"
       "  \"name\": \"authz\","
@@ -515,18 +453,15 @@
   };
   grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f = begin_test(
-      config, "test_file_watcher_init_deny_request_no_match_in_policy", nullptr,
-      &server_args);
+  auto f = begin_test(config,
+                      "test_file_watcher_init_deny_request_no_match_in_policy",
+                      nullptr, &server_args);
   grpc_authorization_policy_provider_release(provider);
-  test_deny_unauthorized_request(f);
-
-  end_test(&f);
-  config.tear_down_data(&f);
+  test_deny_unauthorized_request(f.get());
 }
 
 static void test_file_watcher_valid_policy_reload(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   const char* authz_policy =
       "{"
       "  \"name\": \"authz\","
@@ -556,10 +491,10 @@
   };
   grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f = begin_test(
-      config, "test_file_watcher_valid_policy_reload", nullptr, &server_args);
+  auto f = begin_test(config, "test_file_watcher_valid_policy_reload", nullptr,
+                      &server_args);
   grpc_authorization_policy_provider_release(provider);
-  test_allow_authorized_request(f);
+  test_allow_authorized_request(f.get());
   gpr_event on_reload_done;
   gpr_event_init(&on_reload_done);
   std::function<void(bool contents_changed, absl::Status status)> callback =
@@ -600,16 +535,13 @@
   GPR_ASSERT(
       reinterpret_cast<void*>(1) ==
       gpr_event_wait(&on_reload_done, gpr_inf_future(GPR_CLOCK_MONOTONIC)));
-  test_deny_unauthorized_request(f);
+  test_deny_unauthorized_request(f.get());
   dynamic_cast<grpc_core::FileWatcherAuthorizationPolicyProvider*>(provider)
       ->SetCallbackForTesting(nullptr);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 static void test_file_watcher_invalid_policy_skip_reload(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   const char* authz_policy =
       "{"
       "  \"name\": \"authz\","
@@ -639,11 +571,10 @@
   };
   grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_file_watcher_invalid_policy_skip_reload",
-                 nullptr, &server_args);
+  auto f = begin_test(config, "test_file_watcher_invalid_policy_skip_reload",
+                      nullptr, &server_args);
   grpc_authorization_policy_provider_release(provider);
-  test_allow_authorized_request(f);
+  test_allow_authorized_request(f.get());
   gpr_event on_reload_done;
   gpr_event_init(&on_reload_done);
   std::function<void(bool contents_changed, absl::Status status)> callback =
@@ -662,16 +593,13 @@
   GPR_ASSERT(
       reinterpret_cast<void*>(1) ==
       gpr_event_wait(&on_reload_done, gpr_inf_future(GPR_CLOCK_MONOTONIC)));
-  test_allow_authorized_request(f);
+  test_allow_authorized_request(f.get());
   dynamic_cast<grpc_core::FileWatcherAuthorizationPolicyProvider*>(provider)
       ->SetCallbackForTesting(nullptr);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 static void test_file_watcher_recovers_from_failure(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   const char* authz_policy =
       "{"
       "  \"name\": \"authz\","
@@ -701,10 +629,10 @@
   };
   grpc_channel_args server_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f = begin_test(
-      config, "test_file_watcher_recovers_from_failure", nullptr, &server_args);
+  auto f = begin_test(config, "test_file_watcher_recovers_from_failure",
+                      nullptr, &server_args);
   grpc_authorization_policy_provider_release(provider);
-  test_allow_authorized_request(f);
+  test_allow_authorized_request(f.get());
   gpr_event on_first_reload_done;
   gpr_event_init(&on_first_reload_done);
   std::function<void(bool contents_changed, absl::Status status)> callback1 =
@@ -723,7 +651,7 @@
   GPR_ASSERT(reinterpret_cast<void*>(1) ==
              gpr_event_wait(&on_first_reload_done,
                             gpr_inf_future(GPR_CLOCK_MONOTONIC)));
-  test_allow_authorized_request(f);
+  test_allow_authorized_request(f.get());
   gpr_event on_second_reload_done;
   gpr_event_init(&on_second_reload_done);
   std::function<void(bool contents_changed, absl::Status status)> callback2 =
@@ -765,15 +693,12 @@
   GPR_ASSERT(reinterpret_cast<void*>(1) ==
              gpr_event_wait(&on_second_reload_done,
                             gpr_inf_future(GPR_CLOCK_MONOTONIC)));
-  test_deny_unauthorized_request(f);
+  test_deny_unauthorized_request(f.get());
   dynamic_cast<grpc_core::FileWatcherAuthorizationPolicyProvider*>(provider)
       ->SetCallbackForTesting(nullptr);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void grpc_authz(grpc_end2end_test_config config) {
+void grpc_authz(const CoreTestConfiguration& config) {
   test_static_init_allow_authorized_request(config);
   test_static_init_deny_unauthorized_request(config);
   test_static_init_deny_request_no_match_in_policy(config);
diff --git a/test/core/end2end/tests/high_initial_seqno.cc b/test/core/end2end/tests/high_initial_seqno.cc
index 913b5d4..88eb8db 100644
--- a/test/core/end2end/tests/high_initial_seqno.cc
+++ b/test/core/end2end/tests/high_initial_seqno.cc
@@ -16,9 +16,10 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_cat.h"
@@ -30,71 +31,27 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture f) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -106,10 +63,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -140,15 +97,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -171,12 +128,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -200,10 +157,10 @@
   cqv.VerifyEmpty();
 }
 
-static void test_invoke_10_simple_requests(grpc_end2end_test_config config,
+static void test_invoke_10_simple_requests(const CoreTestConfiguration& config,
                                            int initial_sequence_number) {
   int i;
-  grpc_end2end_test_fixture f;
+
   grpc_arg client_arg;
   grpc_channel_args client_args;
 
@@ -216,16 +173,14 @@
 
   std::string name = absl::StrCat("test_invoke_requests first_seqno=",
                                   initial_sequence_number);
-  f = begin_test(config, name.c_str(), &client_args, nullptr);
+  auto f = begin_test(config, name.c_str(), &client_args, nullptr);
   for (i = 0; i < 10; i++) {
-    simple_request_body(config, f);
+    simple_request_body(config, f.get());
     gpr_log(GPR_INFO, "Running test: Passed simple request %d", i);
   }
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void high_initial_seqno(grpc_end2end_test_config config) {
+void high_initial_seqno(const CoreTestConfiguration& config) {
   test_invoke_10_simple_requests(config, 16777213);
   if (config.feature_mask & FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION) {
     test_invoke_10_simple_requests(config, 2147483645);
diff --git a/test/core/end2end/tests/hpack_size.cc b/test/core/end2end/tests/hpack_size.cc
index 1db461e..11cb650 100644
--- a/test/core/end2end/tests/hpack_size.cc
+++ b/test/core/end2end/tests/hpack_size.cc
@@ -16,11 +16,12 @@
 //
 //
 
-#include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 
+#include <functional>
 #include <initializer_list>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_format.h"
@@ -32,13 +33,11 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/time.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/test_config.h"
-
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
 
 const char* hobbits[][2] = {
     {"Adaldrida", "Brandybuck"}, {"Adamanta", "Took"},
@@ -180,65 +179,22 @@
 const char* dragons[] = {"Ancalagon", "Glaurung", "Scatha",
                          "Smaug the Magnificent"};
 
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture f, size_t index) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f, size_t index) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -262,9 +218,10 @@
   extra_metadata[2].value =
       grpc_slice_from_static_string(dragons[index % GPR_ARRAY_SIZE(dragons)]);
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               gpr_inf_future(GPR_CLOCK_MONOTONIC), nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, gpr_inf_future(GPR_CLOCK_MONOTONIC),
+                               nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -296,15 +253,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify(grpc_core::Duration::Seconds(120));
 
   memset(ops, 0, sizeof(ops));
@@ -327,12 +284,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify(grpc_core::Duration::Seconds(120));
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -350,10 +307,10 @@
   grpc_call_unref(s);
 }
 
-static void test_size(grpc_end2end_test_config config, int encode_size,
+static void test_size(const CoreTestConfiguration& config, int encode_size,
                       int decode_size) {
   size_t i;
-  grpc_end2end_test_fixture f;
+
   grpc_arg server_arg;
   grpc_channel_args server_args;
   grpc_arg client_arg;
@@ -373,17 +330,15 @@
 
   std::string name =
       absl::StrFormat("test_size:e=%d:d=%d", encode_size, decode_size);
-  f = begin_test(config, name.c_str(),
-                 encode_size != 4096 ? &client_args : nullptr,
-                 decode_size != 4096 ? &server_args : nullptr);
+  auto f = begin_test(config, name.c_str(),
+                      encode_size != 4096 ? &client_args : nullptr,
+                      decode_size != 4096 ? &server_args : nullptr);
   for (i = 0; i < 4 * GPR_ARRAY_SIZE(hobbits); i++) {
-    simple_request_body(config, f, i);
+    simple_request_body(config, f.get(), i);
   }
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void hpack_size(grpc_end2end_test_config config) {
+void hpack_size(const CoreTestConfiguration& config) {
   static const int interesting_sizes[] = {4096, 0,     100,
                                           1000, 32768, 4 * 1024 * 1024};
   size_t i, j;
diff --git a/test/core/end2end/tests/invoke_large_request.cc b/test/core/end2end/tests/invoke_large_request.cc
index c1c853d..8b91583 100644
--- a/test/core/end2end/tests/invoke_large_request.cc
+++ b/test/core/end2end/tests/invoke_large_request.cc
@@ -16,9 +16,10 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -30,6 +31,7 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/debug/event_log.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/no_destruct.h"
@@ -39,58 +41,17 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, n_seconds_from_now(5), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 static grpc_slice make_slice(int message_size) {
   GPR_ASSERT(message_size > 0);
   grpc_slice slice = grpc_slice_malloc(message_size);
@@ -98,7 +59,7 @@
   return slice;
 }
 
-static void test_invoke_large_request(grpc_end2end_test_config config,
+static void test_invoke_large_request(const CoreTestConfiguration& config,
                                       int message_size) {
   grpc_arg args[1];
   args[0].type = GRPC_ARG_INTEGER;
@@ -106,8 +67,8 @@
   args[0].value.integer = message_size;
   grpc_channel_args channel_args = {GPR_ARRAY_SIZE(args), args};
 
-  grpc_end2end_test_fixture f = begin_test(config, "test_invoke_large_request",
-                                           &channel_args, &channel_args);
+  auto f = begin_test(config, "test_invoke_large_request", &channel_args,
+                      &channel_args);
 
   grpc_slice request_payload_slice = make_slice(message_size);
   grpc_slice response_payload_slice = make_slice(message_size);
@@ -117,7 +78,7 @@
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
   grpc_byte_buffer* response_payload =
       grpc_raw_byte_buffer_create(&response_payload_slice, 1);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -131,10 +92,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = n_seconds_from_now(300);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(300);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -175,15 +136,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify(grpc_core::Duration::Seconds(60));
 
   memset(ops, 0, sizeof(ops));
@@ -198,11 +159,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify(grpc_core::Duration::Seconds(60));
 
   memset(ops, 0, sizeof(ops));
@@ -225,12 +186,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify(grpc_core::Duration::Seconds(60));
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -253,14 +214,11 @@
   grpc_byte_buffer_destroy(response_payload_recv);
   grpc_slice_unref(request_payload_slice);
   grpc_slice_unref(response_payload_slice);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 static grpc_core::NoDestruct<grpc_core::EventLog> g_event_log;
 
-void invoke_large_request(grpc_end2end_test_config config) {
+void invoke_large_request(const CoreTestConfiguration& config) {
   {
     grpc_core::ExecCtx exec_ctx;
     g_event_log->BeginCollection();
diff --git a/test/core/end2end/tests/keepalive_timeout.cc b/test/core/end2end/tests/keepalive_timeout.cc
index 0e01cc1..905bf5c 100644
--- a/test/core/end2end/tests/keepalive_timeout.cc
+++ b/test/core/end2end/tests/keepalive_timeout.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -28,6 +30,7 @@
 #include <grpc/support/time.h>
 
 #include "src/core/ext/transport/chttp2/transport/frame_ping.h"
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/global_config_generic.h"
 #include "src/core/lib/gprpp/memory.h"
@@ -40,65 +43,20 @@
 #include "src/core/lib/iomgr/ev_posix.h"
 #endif  // GRPC_POSIX_SOCKET
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "%s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Client sends a request, then waits for the keepalive watchdog timeouts before
 // returning status.
-static void test_keepalive_timeout(grpc_end2end_test_config config) {
+static void test_keepalive_timeout(const CoreTestConfiguration& config) {
   grpc_call* c;
 
   grpc_arg keepalive_arg_elems[3];
@@ -114,9 +72,8 @@
   grpc_channel_args keepalive_args = {GPR_ARRAY_SIZE(keepalive_arg_elems),
                                       keepalive_arg_elems};
 
-  grpc_end2end_test_fixture f =
-      begin_test(config, "keepalive_timeout", &keepalive_args, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "keepalive_timeout", &keepalive_args, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -128,10 +85,10 @@
   // Disable ping ack to trigger the keepalive timeout
   grpc_set_disable_ping_ack(true);
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -152,11 +109,11 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNAVAILABLE);
@@ -167,16 +124,13 @@
   grpc_metadata_array_destroy(&trailing_metadata_recv);
 
   grpc_call_unref(c);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 // Verify that reads reset the keepalive ping timer. The client sends 30 pings
 // with a sleep of 10ms in between. It has a configured keepalive timer of
 // 200ms. In the success case, each ping ack should reset the keepalive timer so
 // that the keepalive ping is never sent.
-static void test_read_delays_keepalive(grpc_end2end_test_config config) {
+static void test_read_delays_keepalive(const CoreTestConfiguration& config) {
 #ifdef GRPC_POSIX_SOCKET
   grpc_core::UniquePtr<char> poller = GPR_GLOBAL_CONFIG_GET(grpc_poll_strategy);
   // It is hard to get the timing right for the polling engine poll.
@@ -197,13 +151,13 @@
   keepalive_arg_elems[2].value.integer = 0;
   grpc_channel_args keepalive_args = {GPR_ARRAY_SIZE(keepalive_arg_elems),
                                       keepalive_arg_elems};
-  grpc_end2end_test_fixture f = begin_test(config, "test_read_delays_keepalive",
-                                           &keepalive_args, nullptr);
+  auto f = begin_test(config, "test_read_delays_keepalive", &keepalive_args,
+                      nullptr);
   // Disable ping ack to trigger the keepalive timeout
   grpc_set_disable_ping_ack(true);
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -224,10 +178,10 @@
   grpc_slice response_payload_slice =
       grpc_slice_from_copied_string("hello you");
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -254,15 +208,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(100));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(100));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(100), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(100), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -277,8 +231,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(101),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(101), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   for (i = 0; i < 30; i++) {
@@ -297,8 +251,8 @@
     op->flags = 0;
     op->reserved = nullptr;
     op++;
-    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                  nullptr);
+    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                  grpc_core::CqVerifier::tag(2), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
     memset(ops, 0, sizeof(ops));
@@ -309,9 +263,9 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
+                                  grpc_core::CqVerifier::tag(102), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv.Expect(tag(102), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(102), true);
     cqv.Verify();
 
     memset(ops, 0, sizeof(ops));
@@ -322,10 +276,10 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(103), nullptr);
+                                  grpc_core::CqVerifier::tag(103), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv.Expect(tag(103), true);
-    cqv.Expect(tag(2), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(2), true);
     cqv.Verify();
 
     grpc_byte_buffer_destroy(request_payload);
@@ -345,8 +299,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -359,14 +313,14 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
-  cqv.Expect(tag(3), true);
-  cqv.Expect(tag(101), true);
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   cqv.Verify();
 
   grpc_call_unref(c);
@@ -377,12 +331,9 @@
   grpc_metadata_array_destroy(&request_metadata_recv);
   grpc_call_details_destroy(&call_details);
   grpc_slice_unref(details);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void keepalive_timeout(grpc_end2end_test_config config) {
+void keepalive_timeout(const CoreTestConfiguration& config) {
   test_keepalive_timeout(config);
   test_read_delays_keepalive(config);
 }
diff --git a/test/core/end2end/tests/large_metadata.cc b/test/core/end2end/tests/large_metadata.cc
index cdc15b2..370c018 100644
--- a/test/core/end2end/tests/large_metadata.cc
+++ b/test/core/end2end/tests/large_metadata.cc
@@ -16,11 +16,13 @@
 //
 //
 
-#include <stdint.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
-#include <grpc/byte_buffer.h>
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -28,105 +30,49 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gpr/useful.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-// Request with a large amount of metadata.
-static void test_request_with_large_metadata(grpc_end2end_test_config config) {
+static grpc_status_code send_metadata(CoreTestFixture* f,
+                                      const size_t metadata_size,
+                                      grpc_slice* client_details) {
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_call* c;
   grpc_call* s;
-  grpc_slice request_payload_slice =
-      grpc_slice_from_copied_string("hello world");
-  grpc_byte_buffer* request_payload =
-      grpc_raw_byte_buffer_create(&request_payload_slice, 1);
   grpc_metadata meta;
-  const size_t large_size = 64 * 1024;
-  grpc_arg arg;
-  arg.type = GRPC_ARG_INTEGER;
-  arg.key = const_cast<char*>(GRPC_ARG_MAX_METADATA_SIZE);
-  arg.value.integer = static_cast<int>(large_size) + 1024;
-  grpc_channel_args args = {1, &arg};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_request_with_large_metadata", &args, &args);
-  grpc_core::CqVerifier cqv(f.cq);
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
   grpc_metadata_array trailing_metadata_recv;
   grpc_metadata_array request_metadata_recv;
-  grpc_byte_buffer* request_payload_recv = nullptr;
   grpc_call_details call_details;
   grpc_status_code status;
   grpc_call_error error;
-  grpc_slice details;
   int was_cancelled = 2;
-
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
+  // Add metadata of size `metadata_size`.
   meta.key = grpc_slice_from_static_string("key");
-  meta.value = grpc_slice_malloc(large_size);
-  memset(GRPC_SLICE_START_PTR(meta.value), 'a', large_size);
+  meta.value = grpc_slice_malloc(metadata_size);
+  memset(GRPC_SLICE_START_PTR(meta.value), 'a', metadata_size);
 
   grpc_metadata_array_init(&initial_metadata_recv);
   grpc_metadata_array_init(&trailing_metadata_recv);
@@ -134,16 +80,10 @@
   grpc_call_details_init(&call_details);
 
   memset(ops, 0, sizeof(ops));
-  // Client: send request.
+  // Client: wait on initial metadata from server.
   op = ops;
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
-  op->data.send_initial_metadata.count = 1;
-  op->data.send_initial_metadata.metadata = &meta;
-  op->flags = 0;
-  op->reserved = nullptr;
-  op++;
-  op->op = GRPC_OP_SEND_MESSAGE;
-  op->data.send_message.send_message = request_payload;
+  op->data.send_initial_metadata.count = 0;
   op->flags = 0;
   op->reserved = nullptr;
   op++;
@@ -159,46 +99,31 @@
   op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
   op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv;
   op->data.recv_status_on_client.status = &status;
-  op->data.recv_status_on_client.status_details = &details;
+  op->data.recv_status_on_client.status_details = client_details;
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
-  // Server: send initial metadata and receive request.
+  // Server: send metadata of size `metadata_size`.
   op = ops;
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
-  op->data.send_initial_metadata.count = 0;
+  op->data.send_initial_metadata.count = 1;
+  op->data.send_initial_metadata.metadata = &meta;
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  op->op = GRPC_OP_RECV_MESSAGE;
-  op->data.recv_message.recv_message = &request_payload_recv;
-  op->flags = 0;
-  op->reserved = nullptr;
-  op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
-  GPR_ASSERT(GRPC_CALL_OK == error);
-
-  cqv.Expect(tag(102), true);
-  cqv.Verify();
-
-  memset(ops, 0, sizeof(ops));
-  // Server: receive close and send status.  This should trigger
-  // completion of request on client.
-  op = ops;
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op->flags = 0;
@@ -212,24 +137,13 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
-  GPR_ASSERT(status == GRPC_STATUS_OK);
-  GPR_ASSERT(0 == grpc_slice_str_cmp(details, "xyz"));
-  GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/foo"));
-  GPR_ASSERT(was_cancelled == 0);
-  GPR_ASSERT(byte_buffer_eq_string(request_payload_recv, "hello world"));
-  GPR_ASSERT(contains_metadata_slices(&request_metadata_recv,
-                                      grpc_slice_from_static_string("key"),
-                                      meta.value));
-
-  grpc_slice_unref(details);
   grpc_metadata_array_destroy(&initial_metadata_recv);
   grpc_metadata_array_destroy(&trailing_metadata_recv);
   grpc_metadata_array_destroy(&request_metadata_recv);
@@ -238,153 +152,398 @@
   grpc_call_unref(c);
   grpc_call_unref(s);
 
-  grpc_byte_buffer_destroy(request_payload);
-  grpc_byte_buffer_destroy(request_payload_recv);
-
   grpc_slice_unref(meta.value);
 
-  end_test(&f);
-  config.tear_down_data(&f);
+  return status;
 }
 
-// Server responds with metadata larger than what the client accepts.
-static void test_request_with_bad_large_metadata_response(
-    grpc_end2end_test_config config) {
-  grpc_arg arg;
-  arg.type = GRPC_ARG_INTEGER;
-  arg.key = const_cast<char*>(GRPC_ARG_MAX_METADATA_SIZE);
-  arg.value.integer = 1024;
-  grpc_channel_args args = {1, &arg};
-  grpc_end2end_test_fixture f = begin_test(
-      config, "test_request_with_bad_large_metadata_response", &args, &args);
-  grpc_core::CqVerifier cqv(f.cq);
+// Server responds with metadata under soft limit of what client accepts. No
+// requests should be rejected.
+static void test_request_with_large_metadata_under_soft_limit(
+    const CoreTestConfiguration& config) {
+  const size_t soft_limit = 32 * 1024;
+  const size_t hard_limit = 45 * 1024;
+  const size_t metadata_size = soft_limit;
+  grpc_arg arg[] = {
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_MAX_METADATA_SIZE), soft_limit + 1024),
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE),
+          hard_limit + 1024)};
+  grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
+  auto f =
+      begin_test(config, "test_request_with_large_metadata_under_soft_limit",
+                 &args, &args);
+  for (int i = 0; i < 100; i++) {
+    grpc_slice client_details;
+    auto status = send_metadata(f.get(), metadata_size, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_OK);
+    GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    grpc_slice_unref(client_details);
+  }
+}
 
-  for (int i = 0; i < 10; i++) {
-    grpc_call* c;
-    grpc_call* s;
-    grpc_metadata meta;
-    const size_t large_size = 64 * 1024;
-    grpc_op ops[6];
-    grpc_op* op;
-    grpc_metadata_array initial_metadata_recv;
-    grpc_metadata_array trailing_metadata_recv;
-    grpc_metadata_array request_metadata_recv;
-    grpc_call_details call_details;
-    grpc_status_code status;
-    grpc_call_error error;
-    grpc_slice details;
-    int was_cancelled = 2;
+// Server responds with metadata between soft and hard limits of what client
+// accepts. Some requests should be rejected.
+static void test_request_with_large_metadata_between_soft_and_hard_limits(
+    const CoreTestConfiguration& config) {
+  const size_t soft_limit = 32 * 1024;
+  const size_t hard_limit = 45 * 1024;
+  const size_t metadata_size = (soft_limit + hard_limit) / 2;
+  grpc_arg arg[] = {
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_MAX_METADATA_SIZE), soft_limit + 1024),
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE),
+          hard_limit + 1024)};
+  grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
+  auto f = begin_test(
+      config, "test_request_with_large_metadata_between_soft_and_hard_limits",
+      &args, &args);
 
-    gpr_timespec deadline = five_seconds_from_now();
-    c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                                 f.cq, grpc_slice_from_static_string("/foo"),
-                                 nullptr, deadline, nullptr);
-    GPR_ASSERT(c);
-
-    meta.key = grpc_slice_from_static_string("key");
-    meta.value = grpc_slice_malloc(large_size);
-    memset(GRPC_SLICE_START_PTR(meta.value), 'a', large_size);
-
-    grpc_metadata_array_init(&initial_metadata_recv);
-    grpc_metadata_array_init(&trailing_metadata_recv);
-    grpc_metadata_array_init(&request_metadata_recv);
-    grpc_call_details_init(&call_details);
-
-    memset(ops, 0, sizeof(ops));
-    // Client: send request.
-    op = ops;
-    op->op = GRPC_OP_SEND_INITIAL_METADATA;
-    op->data.send_initial_metadata.count = 0;
-    op->flags = 0;
-    op->reserved = nullptr;
-    op++;
-    op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
-    op->flags = 0;
-    op->reserved = nullptr;
-    op++;
-    op->op = GRPC_OP_RECV_INITIAL_METADATA;
-    op->data.recv_initial_metadata.recv_initial_metadata =
-        &initial_metadata_recv;
-    op->flags = 0;
-    op->reserved = nullptr;
-    op++;
-    op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
-    op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv;
-    op->data.recv_status_on_client.status = &status;
-    op->data.recv_status_on_client.status_details = &details;
-    op->flags = 0;
-    op->reserved = nullptr;
-    op++;
-    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                  nullptr);
-    GPR_ASSERT(GRPC_CALL_OK == error);
-
-    error =
-        grpc_server_request_call(f.server, &s, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101));
-    GPR_ASSERT(GRPC_CALL_OK == error);
-
-    cqv.Expect(tag(101), true);
-    cqv.Verify();
-
-    memset(ops, 0, sizeof(ops));
-    // Server: send large initial metadata
-    op = ops;
-    op->op = GRPC_OP_SEND_INITIAL_METADATA;
-    op->data.send_initial_metadata.count = 1;
-    op->data.send_initial_metadata.metadata = &meta;
-    op->flags = 0;
-    op->reserved = nullptr;
-    op++;
-    op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
-    op->data.recv_close_on_server.cancelled = &was_cancelled;
-    op->flags = 0;
-    op->reserved = nullptr;
-    op++;
-    op->op = GRPC_OP_SEND_STATUS_FROM_SERVER;
-    op->data.send_status_from_server.trailing_metadata_count = 0;
-    op->data.send_status_from_server.status = GRPC_STATUS_OK;
-    grpc_slice status_details = grpc_slice_from_static_string("xyz");
-    op->data.send_status_from_server.status_details = &status_details;
-    op->flags = 0;
-    op->reserved = nullptr;
-    op++;
-    error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
-    GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv.Expect(tag(102), true);
-    cqv.Expect(tag(1), true);
-    cqv.Verify();
-
-    GPR_ASSERT(status == GRPC_STATUS_RESOURCE_EXHAUSTED);
-    const char* expected_error = "received initial metadata size exceeds limit";
-    grpc_slice actual_error =
-        grpc_slice_split_head(&details, strlen(expected_error));
-    GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
-    GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/foo"));
-
-    grpc_slice_unref(actual_error);
-    grpc_slice_unref(details);
-    grpc_metadata_array_destroy(&initial_metadata_recv);
-    grpc_metadata_array_destroy(&trailing_metadata_recv);
-    grpc_metadata_array_destroy(&request_metadata_recv);
-    grpc_call_details_destroy(&call_details);
-
-    grpc_call_unref(c);
-    grpc_call_unref(s);
-
-    grpc_slice_unref(meta.value);
+  int num_requests_rejected = 0;
+  for (int i = 0; i < 100; i++) {
+    grpc_slice client_details;
+    auto status = send_metadata(f.get(), metadata_size, &client_details);
+    if (status == GRPC_STATUS_RESOURCE_EXHAUSTED) {
+      num_requests_rejected++;
+      const char* expected_error = "received metadata size exceeds soft limit";
+      grpc_slice actual_error =
+          grpc_slice_split_head(&client_details, strlen(expected_error));
+      GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+      grpc_slice_unref(actual_error);
+    } else {
+      GPR_ASSERT(status == GRPC_STATUS_OK);
+      GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    }
+    grpc_slice_unref(client_details);
   }
 
-  end_test(&f);
-  config.tear_down_data(&f);
+  // Check that some requests were rejected.
+  GPR_ASSERT(abs(num_requests_rejected - 50) <= 45);
 }
 
-void large_metadata(grpc_end2end_test_config config) {
-  test_request_with_large_metadata(config);
+// Server responds with metadata above hard limit of what the client accepts.
+// All requests should be rejected.
+static void test_request_with_large_metadata_above_hard_limit(
+    const CoreTestConfiguration& config) {
+  const size_t soft_limit = 32 * 1024;
+  const size_t hard_limit = 45 * 1024;
+  const size_t metadata_size = hard_limit * 1.5;
+  grpc_arg arg[] = {
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_MAX_METADATA_SIZE), soft_limit + 1024),
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE),
+          hard_limit + 1024)};
+  grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
+  auto f =
+      begin_test(config, "test_request_with_large_metadata_above_hard_limit",
+                 &args, &args);
+
+  for (int i = 0; i < 100; i++) {
+    grpc_slice client_details;
+    auto status = send_metadata(f.get(), metadata_size, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_RESOURCE_EXHAUSTED);
+    const char* expected_error = "received metadata size exceeds hard limit";
+    grpc_slice actual_error =
+        grpc_slice_split_head(&client_details, strlen(expected_error));
+    GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+    grpc_slice_unref(actual_error);
+    grpc_slice_unref(client_details);
+  }
+}
+
+// Set soft limit higher than hard limit. All requests above hard limit should
+// be rejected, all requests below hard limit should be accepted (soft limit
+// should not be respected).
+static void test_request_with_large_metadata_soft_limit_above_hard_limit(
+    const CoreTestConfiguration& config) {
+  const size_t soft_limit = 64 * 1024;
+  const size_t hard_limit = 32 * 1024;
+  const size_t metadata_size_below_hard_limit = hard_limit;
+  const size_t metadata_size_above_hard_limit = hard_limit * 2;
+  grpc_arg arg[] = {
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_MAX_METADATA_SIZE), soft_limit + 1024),
+      grpc_channel_arg_integer_create(
+          const_cast<char*>(GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE),
+          hard_limit + 1024)};
+  grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
+  auto f = begin_test(
+      config, "test_request_with_large_metadata_soft_limit_above_hard_limit",
+      &args, &args);
+
+  // Send 50 requests below hard limit. Should be accepted.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_below_hard_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_OK);
+    GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    grpc_slice_unref(client_details);
+  }
+
+  // Send 50 requests above hard limit. Should be rejected.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_above_hard_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_RESOURCE_EXHAUSTED);
+    const char* expected_error = "received metadata size exceeds hard limit";
+    grpc_slice actual_error =
+        grpc_slice_split_head(&client_details, strlen(expected_error));
+    GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+    grpc_slice_unref(actual_error);
+    grpc_slice_unref(client_details);
+  }
+}
+
+// Set soft limit * 1.25 higher than default hard limit and do not set hard
+// limit. Soft limit * 1.25 should be used as hard limit.
+static void test_request_with_large_metadata_soft_limit_overrides_default_hard(
+    const CoreTestConfiguration& config) {
+  const size_t soft_limit = 64 * 1024;
+  const size_t metadata_size_below_soft_limit = soft_limit;
+  const size_t metadata_size_above_hard_limit = soft_limit * 1.5;
+  const size_t metadata_size_between_limits =
+      (soft_limit + soft_limit * 1.25) / 2;
+  grpc_arg arg[] = {grpc_channel_arg_integer_create(
+      const_cast<char*>(GRPC_ARG_MAX_METADATA_SIZE), soft_limit + 1024)};
+  grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
+  auto f = begin_test(
+      config,
+      "test_request_with_large_metadata_soft_limit_overrides_default_hard",
+      &args, &args);
+
+  // Send 50 requests below soft limit. Should be accepted.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_below_soft_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_OK);
+    GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    grpc_slice_unref(client_details);
+  }
+
+  // Send 100 requests between soft and hard limits. Some should be rejected.
+  int num_requests_rejected = 0;
+  for (int i = 0; i < 100; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_between_limits, &client_details);
+    if (status == GRPC_STATUS_RESOURCE_EXHAUSTED) {
+      num_requests_rejected++;
+      const char* expected_error = "received metadata size exceeds soft limit";
+      grpc_slice actual_error =
+          grpc_slice_split_head(&client_details, strlen(expected_error));
+      GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+      grpc_slice_unref(actual_error);
+    } else {
+      GPR_ASSERT(status == GRPC_STATUS_OK);
+      GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    }
+    grpc_slice_unref(client_details);
+  }
+  // Check that some requests were rejected.
+  GPR_ASSERT(abs(num_requests_rejected - 50) <= 45);
+
+  // Send 50 requests above hard limit. Should be rejected.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_above_hard_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_RESOURCE_EXHAUSTED);
+    const char* expected_error = "received metadata size exceeds hard limit";
+    grpc_slice actual_error =
+        grpc_slice_split_head(&client_details, strlen(expected_error));
+    GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+    grpc_slice_unref(actual_error);
+    grpc_slice_unref(client_details);
+  }
+}
+
+// Set hard limit * 0.8 higher than default soft limit and do not set soft
+// limit. Hard limit * 0.8 should be used as soft limit.
+static void test_request_with_large_metadata_hard_limit_overrides_default_soft(
+    const CoreTestConfiguration& config) {
+  const size_t hard_limit = 45 * 1024;
+  const size_t metadata_size_below_soft_limit = hard_limit * 0.5;
+  const size_t metadata_size_above_hard_limit = hard_limit * 1.5;
+  const size_t metadata_size_between_limits =
+      (hard_limit * 0.8 + hard_limit) / 2;
+  grpc_arg arg[] = {grpc_channel_arg_integer_create(
+      const_cast<char*>(GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE),
+      hard_limit + 1024)};
+  grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
+  auto f = begin_test(
+      config,
+      "test_request_with_large_metadata_hard_limit_overrides_default_soft",
+      &args, &args);
+
+  // Send 50 requests below soft limit. Should be accepted.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_below_soft_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_OK);
+    GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    grpc_slice_unref(client_details);
+  }
+
+  // Send 100 requests between soft and hard limits. Some should be rejected.
+  int num_requests_rejected = 0;
+  for (int i = 0; i < 100; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_between_limits, &client_details);
+    if (status == GRPC_STATUS_RESOURCE_EXHAUSTED) {
+      num_requests_rejected++;
+      const char* expected_error = "received metadata size exceeds soft limit";
+      grpc_slice actual_error =
+          grpc_slice_split_head(&client_details, strlen(expected_error));
+      GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+      grpc_slice_unref(actual_error);
+    } else {
+      GPR_ASSERT(status == GRPC_STATUS_OK);
+      GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    }
+    grpc_slice_unref(client_details);
+  }
+  // Check that some requests were rejected.
+  GPR_ASSERT(abs(num_requests_rejected - 50) <= 45);
+
+  // Send 50 requests above hard limit. Should be rejected.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_above_hard_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_RESOURCE_EXHAUSTED);
+    const char* expected_error = "received metadata size exceeds hard limit";
+    grpc_slice actual_error =
+        grpc_slice_split_head(&client_details, strlen(expected_error));
+    GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+    grpc_slice_unref(actual_error);
+    grpc_slice_unref(client_details);
+  }
+}
+
+// Set hard limit lower than default hard limit and ensure new limit is
+// respected. Default soft limit is not respected since hard limit is lower than
+// soft limit.
+static void test_request_with_large_metadata_hard_limit_below_default_hard(
+    const CoreTestConfiguration& config) {
+  const size_t hard_limit = 4 * 1024;
+  const size_t metadata_size_below_hard_limit = hard_limit;
+  const size_t metadata_size_above_hard_limit = hard_limit * 2;
+  grpc_arg arg[] = {grpc_channel_arg_integer_create(
+      const_cast<char*>(GRPC_ARG_ABSOLUTE_MAX_METADATA_SIZE),
+      hard_limit + 1024)};
+  grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
+  auto f = begin_test(
+      config, "test_request_with_large_metadata_hard_limit_below_default_hard",
+      &args, &args);
+
+  // Send 50 requests below hard limit. Should be accepted.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_below_hard_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_OK);
+    GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    grpc_slice_unref(client_details);
+  }
+
+  // Send 50 requests above hard limit. Should be rejected.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_above_hard_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_RESOURCE_EXHAUSTED);
+    const char* expected_error = "received metadata size exceeds hard limit";
+    grpc_slice actual_error =
+        grpc_slice_split_head(&client_details, strlen(expected_error));
+    GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+    grpc_slice_unref(actual_error);
+    grpc_slice_unref(client_details);
+  }
+}
+
+// Set soft limit lower than default soft limit and ensure new limit is
+// respected. Hard limit should be default hard since this is greater than 2 *
+// soft limit.
+static void test_request_with_large_metadata_soft_limit_below_default_soft(
+    const CoreTestConfiguration& config) {
+  const size_t soft_limit = 1 * 1024;
+  const size_t metadata_size_below_soft_limit = soft_limit;
+  // greater than 2 * soft, less than default hard
+  const size_t metadata_size_between_limits = 10 * 1024;
+  const size_t metadata_size_above_hard_limit = 75 * 1024;
+  grpc_arg arg[] = {grpc_channel_arg_integer_create(
+      const_cast<char*>(GRPC_ARG_MAX_METADATA_SIZE), soft_limit + 1024)};
+  grpc_channel_args args = {GPR_ARRAY_SIZE(arg), arg};
+  auto f = begin_test(
+      config, "test_request_with_large_metadata_soft_limit_below_default_soft",
+      &args, &args);
+
+  // Send 50 requests below soft limit. Should be accepted.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_below_soft_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_OK);
+    GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    grpc_slice_unref(client_details);
+  }
+
+  // Send 100 requests between soft and hard limits. Some should be rejected.
+  int num_requests_rejected = 0;
+  for (int i = 0; i < 100; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_between_limits, &client_details);
+    if (status == GRPC_STATUS_RESOURCE_EXHAUSTED) {
+      num_requests_rejected++;
+      const char* expected_error = "received metadata size exceeds soft limit";
+      grpc_slice actual_error =
+          grpc_slice_split_head(&client_details, strlen(expected_error));
+      GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+      grpc_slice_unref(actual_error);
+    } else {
+      GPR_ASSERT(status == GRPC_STATUS_OK);
+      GPR_ASSERT(0 == grpc_slice_str_cmp(client_details, "xyz"));
+    }
+    grpc_slice_unref(client_details);
+  }
+  // Check that some requests were rejected.
+  GPR_ASSERT((abs(num_requests_rejected - 50) <= 49));
+
+  // Send 50 requests above hard limit. Should be rejected.
+  for (int i = 0; i < 50; i++) {
+    grpc_slice client_details;
+    auto status =
+        send_metadata(f.get(), metadata_size_above_hard_limit, &client_details);
+    GPR_ASSERT(status == GRPC_STATUS_RESOURCE_EXHAUSTED);
+    const char* expected_error = "received metadata size exceeds hard limit";
+    grpc_slice actual_error =
+        grpc_slice_split_head(&client_details, strlen(expected_error));
+    GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
+    grpc_slice_unref(actual_error);
+    grpc_slice_unref(client_details);
+  }
+}
+
+void large_metadata(const CoreTestConfiguration& config) {
+  test_request_with_large_metadata_under_soft_limit(config);
   // TODO(yashykt): Maybe add checks for metadata size in inproc transport too.
   if (strcmp(config.name, "inproc") != 0) {
-    test_request_with_bad_large_metadata_response(config);
+    test_request_with_large_metadata_between_soft_and_hard_limits(config);
+    test_request_with_large_metadata_above_hard_limit(config);
+    test_request_with_large_metadata_soft_limit_above_hard_limit(config);
+    test_request_with_large_metadata_soft_limit_overrides_default_hard(config);
+    test_request_with_large_metadata_hard_limit_overrides_default_soft(config);
+    test_request_with_large_metadata_hard_limit_below_default_hard(config);
+    test_request_with_large_metadata_soft_limit_below_default_soft(config);
   }
 }
 
diff --git a/test/core/end2end/tests/load_reporting_hook.cc b/test/core/end2end/tests/load_reporting_hook.cc
index 4524d9a..4ad8bf3 100644
--- a/test/core/end2end/tests/load_reporting_hook.cc
+++ b/test/core/end2end/tests/load_reporting_hook.cc
@@ -53,64 +53,21 @@
   bool fully_processed;
 } load_reporting_data;
 
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
 
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
 
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 static void request_response_with_payload(
-    grpc_end2end_test_config config, grpc_end2end_test_fixture f,
+    const CoreTestConfiguration& config, CoreTestFixture f,
     const char* method_name, const char* request_msg, const char* response_msg,
     grpc_metadata* initial_lr_metadata, grpc_metadata* trailing_lr_metadata) {
   grpc_slice request_payload_slice = grpc_slice_from_static_string(request_msg);
@@ -122,7 +79,7 @@
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
   grpc_byte_buffer* response_payload =
       grpc_raw_byte_buffer_create(&response_payload_slice, 1);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -136,10 +93,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string(method_name),
-                               nullptr, deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string(method_name), nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -182,15 +139,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -205,11 +162,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -234,12 +191,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -263,7 +220,7 @@
 extern void (*g_load_reporting_fn)(
     const grpc_load_reporting_call_data* call_data);
 
-static void test_load_reporting_hook(grpc_end2end_test_config config) {
+static void test_load_reporting_hook(const CoreTestConfiguration& config) {
   // TODO(dgq): this test is currently a noop until LR is fully defined.
   // Leaving the rest here, as it'll likely be reusable.
 
@@ -272,7 +229,7 @@
   grpc_channel_args* lr_server_args =
       grpc_channel_args_copy_and_add(nullptr, &arg, 1);
 
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_load_reporting_hook", nullptr, lr_server_args);
 
   const char* method_name = "/gRPCFTW";
@@ -295,15 +252,14 @@
   request_response_with_payload(config, f, method_name, request_msg,
                                 response_msg, &initial_lr_metadata,
                                 &trailing_lr_metadata);
-  end_test(&f);
+
   {
     grpc_core::ExecCtx exec_ctx;
     grpc_channel_args_destroy(lr_server_args);
   }
-  config.tear_down_data(&f);
 }
 
-void load_reporting_hook(grpc_end2end_test_config config) {
+void load_reporting_hook(const CoreTestConfiguration& config) {
   test_load_reporting_hook(config);
 }
 
diff --git a/test/core/end2end/tests/max_concurrent_streams.cc b/test/core/end2end/tests/max_concurrent_streams.cc
index 702c67a..24915cc 100644
--- a/test/core/end2end/tests/max_concurrent_streams.cc
+++ b/test/core/end2end/tests/max_concurrent_streams.cc
@@ -19,6 +19,9 @@
 #include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -26,71 +29,27 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture f) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -102,10 +61,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -136,15 +95,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -167,12 +126,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -190,8 +149,7 @@
   grpc_call_unref(s);
 }
 
-static void test_max_concurrent_streams(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
+static void test_max_concurrent_streams(const CoreTestConfiguration& config) {
   grpc_arg server_arg;
   grpc_channel_args server_args;
   grpc_call* c1;
@@ -225,8 +183,9 @@
   server_args.num_args = 1;
   server_args.args = &server_arg;
 
-  f = begin_test(config, "test_max_concurrent_streams", nullptr, &server_args);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f =
+      begin_test(config, "test_max_concurrent_streams", nullptr, &server_args);
+  grpc_core::CqVerifier cqv(f->cq());
 
   grpc_metadata_array_init(&request_metadata_recv);
   grpc_metadata_array_init(&initial_metadata_recv1);
@@ -237,25 +196,26 @@
 
   // perform a ping-pong to ensure that settings have had a chance to round
   // trip
-  simple_request_body(config, f);
+  simple_request_body(config, f.get());
   // perform another one to make sure that the one stream case still works
-  simple_request_body(config, f);
+  simple_request_body(config, f.get());
 
   // start two requests - ensuring that the second is not accepted until
   // the first completes
-  deadline = n_seconds_from_now(1000);
-  c1 = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                                f.cq, grpc_slice_from_static_string("/alpha"),
-                                nullptr, deadline, nullptr);
+  deadline = grpc_timeout_seconds_to_deadline(1000);
+  c1 = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string("/alpha"), nullptr, deadline, nullptr);
   GPR_ASSERT(c1);
-  c2 = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                                f.cq, grpc_slice_from_static_string("/beta"),
+  c2 = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                                f->cq(), grpc_slice_from_static_string("/beta"),
                                 nullptr, deadline, nullptr);
   GPR_ASSERT(c2);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s1, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101)));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s1, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(101)));
 
   memset(ops, 0, sizeof(ops));
   op = ops;
@@ -269,7 +229,7 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c1, ops, static_cast<size_t>(op - ops),
-                                tag(301), nullptr);
+                                grpc_core::CqVerifier::tag(301), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -288,7 +248,7 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c1, ops, static_cast<size_t>(op - ops),
-                                tag(302), nullptr);
+                                grpc_core::CqVerifier::tag(302), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -303,7 +263,7 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c2, ops, static_cast<size_t>(op - ops),
-                                tag(401), nullptr);
+                                grpc_core::CqVerifier::tag(401), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -322,23 +282,24 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c2, ops, static_cast<size_t>(op - ops),
-                                tag(402), nullptr);
+                                grpc_core::CqVerifier::tag(402), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   got_client_start = 0;
   got_server_start = 0;
   live_call = -1;
   while (!got_client_start || !got_server_start) {
-    ev = grpc_completion_queue_next(f.cq, grpc_timeout_seconds_to_deadline(3),
-                                    nullptr);
+    ev = grpc_completion_queue_next(
+        f->cq(), grpc_timeout_seconds_to_deadline(3), nullptr);
     GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
     GPR_ASSERT(ev.success);
-    if (ev.tag == tag(101)) {
+    if (ev.tag == grpc_core::CqVerifier::tag(101)) {
       GPR_ASSERT(!got_server_start);
       got_server_start = 1;
     } else {
       GPR_ASSERT(!got_client_start);
-      GPR_ASSERT(ev.tag == tag(301) || ev.tag == tag(401));
+      GPR_ASSERT(ev.tag == grpc_core::CqVerifier::tag(301) ||
+                 ev.tag == grpc_core::CqVerifier::tag(401));
       // The /alpha or /beta calls started above could be invoked (but NOT
       // both);
       // check this here
@@ -370,22 +331,23 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(s1, ops, static_cast<size_t>(op - ops),
-                                tag(102), nullptr);
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(live_call + 2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(live_call + 2), true);
   // first request is finished, we should be able to start the second
   live_call = (live_call == 300) ? 400 : 300;
-  cqv.Expect(tag(live_call + 1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(live_call + 1), true);
   cqv.Verify();
 
   grpc_call_details_destroy(&call_details);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s2, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(201)));
-  cqv.Expect(tag(201), true);
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s2, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(201)));
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -408,11 +370,11 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(s2, ops, static_cast<size_t>(op - ops),
-                                tag(202), nullptr);
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(live_call + 2), true);
-  cqv.Expect(tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(live_call + 2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
   cqv.Verify();
 
   grpc_call_unref(c1);
@@ -428,14 +390,10 @@
   grpc_metadata_array_destroy(&trailing_metadata_recv2);
   grpc_metadata_array_destroy(&request_metadata_recv);
   grpc_call_details_destroy(&call_details);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 static void test_max_concurrent_streams_with_timeout_on_first(
-    grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
+    const CoreTestConfiguration& config) {
   grpc_arg server_arg;
   grpc_channel_args server_args;
   grpc_call* c1;
@@ -464,9 +422,10 @@
   server_args.num_args = 1;
   server_args.args = &server_arg;
 
-  f = begin_test(config, "test_max_concurrent_streams_with_timeout_on_first",
+  auto f =
+      begin_test(config, "test_max_concurrent_streams_with_timeout_on_first",
                  nullptr, &server_args);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
   grpc_metadata_array_init(&request_metadata_recv);
   grpc_metadata_array_init(&initial_metadata_recv1);
@@ -477,24 +436,27 @@
 
   // perform a ping-pong to ensure that settings have had a chance to round
   // trip
-  simple_request_body(config, f);
+  simple_request_body(config, f.get());
   // perform another one to make sure that the one stream case still works
-  simple_request_body(config, f);
+  simple_request_body(config, f.get());
 
   // start two requests - ensuring that the second is not accepted until
   // the first completes
-  c1 = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                                f.cq, grpc_slice_from_static_string("/alpha"),
-                                nullptr, n_seconds_from_now(3), nullptr);
+  c1 = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string("/alpha"), nullptr,
+      grpc_timeout_seconds_to_deadline(3), nullptr);
   GPR_ASSERT(c1);
-  c2 = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                                f.cq, grpc_slice_from_static_string("/beta"),
-                                nullptr, n_seconds_from_now(1000), nullptr);
+  c2 = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                                f->cq(), grpc_slice_from_static_string("/beta"),
+                                nullptr, grpc_timeout_seconds_to_deadline(1000),
+                                nullptr);
   GPR_ASSERT(c2);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s1, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101)));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s1, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(101)));
 
   memset(ops, 0, sizeof(ops));
   op = ops;
@@ -508,7 +470,7 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c1, ops, static_cast<size_t>(op - ops),
-                                tag(301), nullptr);
+                                grpc_core::CqVerifier::tag(301), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -527,11 +489,11 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c1, ops, static_cast<size_t>(op - ops),
-                                tag(302), nullptr);
+                                grpc_core::CqVerifier::tag(302), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(101), true);
-  cqv.Expect(tag(301), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(301), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -546,7 +508,7 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c2, ops, static_cast<size_t>(op - ops),
-                                tag(401), nullptr);
+                                grpc_core::CqVerifier::tag(401), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -565,19 +527,20 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c2, ops, static_cast<size_t>(op - ops),
-                                tag(402), nullptr);
+                                grpc_core::CqVerifier::tag(402), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   grpc_call_details_destroy(&call_details);
   grpc_call_details_init(&call_details);
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s2, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(201)));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s2, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(201)));
 
-  cqv.Expect(tag(302), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(302), true);
   // first request is finished, we should be able to start the second
-  cqv.Expect(tag(401), true);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(401), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -601,11 +564,11 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(s2, ops, static_cast<size_t>(op - ops),
-                                tag(202), nullptr);
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(402), true);
-  cqv.Expect(tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(402), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
   cqv.Verify();
 
   grpc_call_unref(c1);
@@ -621,14 +584,10 @@
   grpc_metadata_array_destroy(&trailing_metadata_recv2);
   grpc_metadata_array_destroy(&request_metadata_recv);
   grpc_call_details_destroy(&call_details);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 static void test_max_concurrent_streams_with_timeout_on_second(
-    grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
+    const CoreTestConfiguration& config) {
   grpc_arg server_arg;
   grpc_channel_args server_args;
   grpc_call* c1;
@@ -656,9 +615,10 @@
   server_args.num_args = 1;
   server_args.args = &server_arg;
 
-  f = begin_test(config, "test_max_concurrent_streams_with_timeout_on_second",
+  auto f =
+      begin_test(config, "test_max_concurrent_streams_with_timeout_on_second",
                  nullptr, &server_args);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
   grpc_metadata_array_init(&request_metadata_recv);
   grpc_metadata_array_init(&initial_metadata_recv1);
@@ -669,25 +629,28 @@
 
   // perform a ping-pong to ensure that settings have had a chance to round
   // trip
-  simple_request_body(config, f);
+  simple_request_body(config, f.get());
   // perform another one to make sure that the one stream case still works
-  simple_request_body(config, f);
+  simple_request_body(config, f.get());
 
   // start two requests - ensuring that the second is not accepted until
   // the first completes , and the second request will timeout in the
   // concurrent_list
-  c1 = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                                f.cq, grpc_slice_from_static_string("/alpha"),
-                                nullptr, n_seconds_from_now(1000), nullptr);
+  c1 = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string("/alpha"), nullptr,
+      grpc_timeout_seconds_to_deadline(1000), nullptr);
   GPR_ASSERT(c1);
-  c2 = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                                f.cq, grpc_slice_from_static_string("/beta"),
-                                nullptr, n_seconds_from_now(3), nullptr);
+  c2 = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                                f->cq(), grpc_slice_from_static_string("/beta"),
+                                nullptr, grpc_timeout_seconds_to_deadline(3),
+                                nullptr);
   GPR_ASSERT(c2);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s1, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101)));
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s1, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(101)));
 
   memset(ops, 0, sizeof(ops));
   op = ops;
@@ -701,7 +664,7 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c1, ops, static_cast<size_t>(op - ops),
-                                tag(301), nullptr);
+                                grpc_core::CqVerifier::tag(301), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -720,11 +683,11 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c1, ops, static_cast<size_t>(op - ops),
-                                tag(302), nullptr);
+                                grpc_core::CqVerifier::tag(302), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(101), true);
-  cqv.Expect(tag(301), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(301), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -739,7 +702,7 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c2, ops, static_cast<size_t>(op - ops),
-                                tag(401), nullptr);
+                                grpc_core::CqVerifier::tag(401), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -758,12 +721,12 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(c2, ops, static_cast<size_t>(op - ops),
-                                tag(402), nullptr);
+                                grpc_core::CqVerifier::tag(402), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // the second request is time out
-  cqv.Expect(tag(401), false);
-  cqv.Expect(tag(402), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(401), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(402), true);
   cqv.Verify();
 
   // second request is finished because of time out, so destroy the second call
@@ -792,11 +755,11 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(s1, ops, static_cast<size_t>(op - ops),
-                                tag(102), nullptr);
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(302), true);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(302), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   grpc_call_unref(c1);
@@ -810,12 +773,9 @@
   grpc_metadata_array_destroy(&trailing_metadata_recv2);
   grpc_metadata_array_destroy(&request_metadata_recv);
   grpc_call_details_destroy(&call_details);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void max_concurrent_streams(grpc_end2end_test_config config) {
+void max_concurrent_streams(const CoreTestConfiguration& config) {
   test_max_concurrent_streams_with_timeout_on_first(config);
   test_max_concurrent_streams_with_timeout_on_second(config);
   test_max_concurrent_streams(config);
diff --git a/test/core/end2end/tests/max_connection_age.cc b/test/core/end2end/tests/max_connection_age.cc
index 4834422..0e4c1c4 100644
--- a/test/core/end2end/tests/max_connection_age.cc
+++ b/test/core/end2end/tests/max_connection_age.cc
@@ -17,9 +17,9 @@
 //
 
 #include <limits.h>
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
 #include <memory>
 
 #include <grpc/grpc.h>
@@ -29,7 +29,7 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
-#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/time.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
@@ -52,54 +52,19 @@
 // The grace period for the test to observe the channel shutdown process
 #define IMMEDIATE_SHUTDOWN_GRACE_TIME_MS 3000
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
+static void test_max_age_forcibly_close(const CoreTestConfiguration& config) {
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
+  auto cqv = std::make_unique<grpc_core::CqVerifier>(f->cq());
+  auto server_args =
+      grpc_core::ChannelArgs()
+          .Set(GRPC_ARG_MAX_CONNECTION_AGE_MS, MAX_CONNECTION_AGE_MS)
+          .Set(GRPC_ARG_MAX_CONNECTION_AGE_GRACE_MS,
+               MAX_CONNECTION_AGE_GRACE_MS)
+          .Set(GRPC_ARG_MAX_CONNECTION_IDLE_MS, MAX_CONNECTION_IDLE_MS);
 
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void test_max_age_forcibly_close(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = config.create_fixture(nullptr, nullptr);
-  auto cqv = std::make_unique<grpc_core::CqVerifier>(f.cq);
-  grpc_arg server_a[3];
-  server_a[0].type = GRPC_ARG_INTEGER;
-  server_a[0].key = const_cast<char*>(GRPC_ARG_MAX_CONNECTION_AGE_MS);
-  server_a[0].value.integer = MAX_CONNECTION_AGE_MS;
-  server_a[1].type = GRPC_ARG_INTEGER;
-  server_a[1].key = const_cast<char*>(GRPC_ARG_MAX_CONNECTION_AGE_GRACE_MS);
-  server_a[1].value.integer = MAX_CONNECTION_AGE_GRACE_MS;
-  server_a[2].type = GRPC_ARG_INTEGER;
-  server_a[2].key = const_cast<char*>(GRPC_ARG_MAX_CONNECTION_IDLE_MS);
-  server_a[2].value.integer = MAX_CONNECTION_IDLE_MS;
-  grpc_channel_args server_args = {GPR_ARRAY_SIZE(server_a), server_a};
-
-  config.init_client(&f, nullptr);
-  config.init_server(&f, &server_args);
+  f->InitClient(grpc_core::ChannelArgs());
+  f->InitServer(server_args);
 
   grpc_call* c;
   grpc_call* s = nullptr;
@@ -115,9 +80,9 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -149,21 +114,22 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   grpc_event ev = grpc_completion_queue_next(
-      f.cq, gpr_inf_future(GPR_CLOCK_MONOTONIC), nullptr);
+      f->cq(), gpr_inf_future(GPR_CLOCK_MONOTONIC), nullptr);
   GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
-  GPR_ASSERT(ev.tag == tag(1) || ev.tag == tag(101));
+  GPR_ASSERT(ev.tag == grpc_core::CqVerifier::tag(1) ||
+             ev.tag == grpc_core::CqVerifier::tag(101));
 
-  if (ev.tag == tag(101)) {
+  if (ev.tag == grpc_core::CqVerifier::tag(101)) {
     // Request got through to the server before connection timeout
 
     // Wait for the channel to reach its max age
@@ -172,7 +138,7 @@
 
     // After the channel reaches its max age, we still do nothing here. And wait
     // for it to use up its max age grace period.
-    cqv->Expect(tag(1), true);
+    cqv->Expect(grpc_core::CqVerifier::tag(1), true);
     cqv->Verify();
 
     gpr_timespec expect_shutdown_time = grpc_timeout_milliseconds_to_deadline(
@@ -204,9 +170,9 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
+                                  grpc_core::CqVerifier::tag(102), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv->Expect(tag(102), true);
+    cqv->Expect(grpc_core::CqVerifier::tag(102), true);
     cqv->Verify();
 
     GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/foo"));
@@ -215,10 +181,11 @@
     // Request failed before getting to the server
   }
 
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
-  cqv->Expect(tag(0xdead), true);
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(0xdead));
+  cqv->Expect(grpc_core::CqVerifier::tag(0xdead), true);
   if (s == nullptr) {
-    cqv->Expect(tag(101), false);
+    cqv->Expect(grpc_core::CqVerifier::tag(101), false);
   }
   cqv->Verify();
 
@@ -237,27 +204,20 @@
   grpc_call_details_destroy(&call_details);
   grpc_call_unref(c);
   cqv.reset();
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-static void test_max_age_gracefully_close(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = config.create_fixture(nullptr, nullptr);
-  auto cqv = std::make_unique<grpc_core::CqVerifier>(f.cq);
-  grpc_arg server_a[3];
-  server_a[0].type = GRPC_ARG_INTEGER;
-  server_a[0].key = const_cast<char*>(GRPC_ARG_MAX_CONNECTION_AGE_MS);
-  server_a[0].value.integer = MAX_CONNECTION_AGE_MS;
-  server_a[1].type = GRPC_ARG_INTEGER;
-  server_a[1].key = const_cast<char*>(GRPC_ARG_MAX_CONNECTION_AGE_GRACE_MS);
-  server_a[1].value.integer = INT_MAX;
-  server_a[2].type = GRPC_ARG_INTEGER;
-  server_a[2].key = const_cast<char*>(GRPC_ARG_MAX_CONNECTION_IDLE_MS);
-  server_a[2].value.integer = MAX_CONNECTION_IDLE_MS;
-  grpc_channel_args server_args = {GPR_ARRAY_SIZE(server_a), server_a};
+static void test_max_age_gracefully_close(const CoreTestConfiguration& config) {
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
+  auto cqv = std::make_unique<grpc_core::CqVerifier>(f->cq());
+  auto server_args =
+      grpc_core::ChannelArgs()
+          .Set(GRPC_ARG_MAX_CONNECTION_AGE_MS, MAX_CONNECTION_AGE_MS)
+          .Set(GRPC_ARG_MAX_CONNECTION_AGE_GRACE_MS, INT_MAX)
+          .Set(GRPC_ARG_MAX_CONNECTION_IDLE_MS, MAX_CONNECTION_IDLE_MS);
 
-  config.init_client(&f, nullptr);
-  config.init_server(&f, &server_args);
+  f->InitClient(grpc_core::ChannelArgs());
+  f->InitServer(server_args);
 
   grpc_call* c;
   grpc_call* s = nullptr;
@@ -273,9 +233,9 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -307,21 +267,22 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   grpc_event ev = grpc_completion_queue_next(
-      f.cq, gpr_inf_future(GPR_CLOCK_MONOTONIC), nullptr);
+      f->cq(), gpr_inf_future(GPR_CLOCK_MONOTONIC), nullptr);
   GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
-  GPR_ASSERT(ev.tag == tag(1) || ev.tag == tag(101));
+  GPR_ASSERT(ev.tag == grpc_core::CqVerifier::tag(1) ||
+             ev.tag == grpc_core::CqVerifier::tag(101));
 
-  if (ev.tag == tag(101)) {
+  if (ev.tag == grpc_core::CqVerifier::tag(101)) {
     // Request got through to the server before connection timeout
 
     // Wait for the channel to reach its max age
@@ -354,11 +315,11 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
+                                  grpc_core::CqVerifier::tag(102), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
-    cqv->Expect(tag(102), true);
-    cqv->Expect(tag(1), true);
+    cqv->Expect(grpc_core::CqVerifier::tag(102), true);
+    cqv->Expect(grpc_core::CqVerifier::tag(1), true);
     cqv->Verify();
 
     GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/foo"));
@@ -367,10 +328,11 @@
     // Request failed before getting to the server
   }
 
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
-  cqv->Expect(tag(0xdead), true);
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(0xdead));
+  cqv->Expect(grpc_core::CqVerifier::tag(0xdead), true);
   if (s == nullptr) {
-    cqv->Expect(tag(101), false);
+    cqv->Expect(grpc_core::CqVerifier::tag(101), false);
   }
   cqv->Verify();
 
@@ -390,11 +352,9 @@
   grpc_call_details_destroy(&call_details);
   grpc_call_unref(c);
   cqv.reset();
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void max_connection_age(grpc_end2end_test_config config) {
+void max_connection_age(const CoreTestConfiguration& config) {
   test_max_age_forcibly_close(config);
   test_max_age_gracefully_close(config);
 }
diff --git a/test/core/end2end/tests/max_connection_idle.cc b/test/core/end2end/tests/max_connection_idle.cc
index f667161..7b43577 100644
--- a/test/core/end2end/tests/max_connection_idle.cc
+++ b/test/core/end2end/tests/max_connection_idle.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -28,7 +30,6 @@
 #include <grpc/support/time.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/useful.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
@@ -36,21 +37,11 @@
 #define MAX_CONNECTION_IDLE_MS 2000
 #define MAX_CONNECTION_AGE_MS 9999
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture* f) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f->cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -64,8 +55,8 @@
   char* peer;
 
   gpr_timespec deadline = grpc_timeout_seconds_to_deadline(30);
-  c = grpc_channel_create_call(f->client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                               f->cq, grpc_slice_from_static_string("/foo"),
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
@@ -103,15 +94,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f->server, &s, &call_details,
-                               &request_metadata_recv, f->cq, f->cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -143,12 +134,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -166,72 +157,63 @@
   grpc_call_unref(s);
 }
 
-static void test_max_connection_idle(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = config.create_fixture(nullptr, nullptr);
+static void test_max_connection_idle(const CoreTestConfiguration& config) {
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
   grpc_connectivity_state state = GRPC_CHANNEL_IDLE;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
   auto client_args = grpc_core::ChannelArgs()
                          .Set(GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS, 1000)
                          .Set(GRPC_ARG_MAX_RECONNECT_BACKOFF_MS, 1000)
-                         .Set(GRPC_ARG_MIN_RECONNECT_BACKOFF_MS, 5000)
-                         .ToC();
-  grpc_arg server_a[2];
-  server_a[0].type = GRPC_ARG_INTEGER;
-  server_a[0].key = const_cast<char*>(GRPC_ARG_MAX_CONNECTION_IDLE_MS);
-  server_a[0].value.integer = MAX_CONNECTION_IDLE_MS;
-  server_a[1].type = GRPC_ARG_INTEGER;
-  server_a[1].key = const_cast<char*>(GRPC_ARG_MAX_CONNECTION_AGE_MS);
-  server_a[1].value.integer = MAX_CONNECTION_AGE_MS;
-  grpc_channel_args server_args = {GPR_ARRAY_SIZE(server_a), server_a};
+                         .Set(GRPC_ARG_MIN_RECONNECT_BACKOFF_MS, 5000);
+  auto server_args =
+      grpc_core::ChannelArgs()
+          .Set(GRPC_ARG_MAX_CONNECTION_IDLE_MS, MAX_CONNECTION_IDLE_MS)
+          .Set(GRPC_ARG_MAX_CONNECTION_AGE_MS, MAX_CONNECTION_AGE_MS);
 
-  config.init_client(&f, client_args.get());
-  config.init_server(&f, &server_args);
+  f->InitClient(client_args);
+  f->InitServer(server_args);
 
   // check that we're still in idle, and start connecting
-  GPR_ASSERT(grpc_channel_check_connectivity_state(f.client, 1) ==
+  GPR_ASSERT(grpc_channel_check_connectivity_state(f->client(), 1) ==
              GRPC_CHANNEL_IDLE);
   // we'll go through some set of transitions (some might be missed), until
   // READY is reached
   while (state != GRPC_CHANNEL_READY) {
     grpc_channel_watch_connectivity_state(
-        f.client, state, grpc_timeout_seconds_to_deadline(10), f.cq, tag(99));
-    cqv.Expect(tag(99), true);
+        f->client(), state, grpc_timeout_seconds_to_deadline(10), f->cq(),
+        grpc_core::CqVerifier::tag(99));
+    cqv.Expect(grpc_core::CqVerifier::tag(99), true);
     cqv.Verify();
-    state = grpc_channel_check_connectivity_state(f.client, 0);
+    state = grpc_channel_check_connectivity_state(f->client(), 0);
     GPR_ASSERT(state == GRPC_CHANNEL_READY ||
                state == GRPC_CHANNEL_CONNECTING ||
                state == GRPC_CHANNEL_TRANSIENT_FAILURE);
   }
 
   // Use a simple request to cancel and reset the max idle timer
-  simple_request_body(config, &f);
+  simple_request_body(config, f.get());
 
   // wait for the channel to reach its maximum idle time
   grpc_channel_watch_connectivity_state(
-      f.client, GRPC_CHANNEL_READY,
+      f->client(), GRPC_CHANNEL_READY,
       gpr_time_add(grpc_timeout_milliseconds_to_deadline(3000),
                    gpr_time_from_millis(MAX_CONNECTION_IDLE_MS, GPR_TIMESPAN)),
-      f.cq, tag(99));
-  cqv.Expect(tag(99), true);
+      f->cq(), grpc_core::CqVerifier::tag(99));
+  cqv.Expect(grpc_core::CqVerifier::tag(99), true);
   cqv.Verify();
-  state = grpc_channel_check_connectivity_state(f.client, 0);
+  state = grpc_channel_check_connectivity_state(f->client(), 0);
   GPR_ASSERT(state == GRPC_CHANNEL_TRANSIENT_FAILURE ||
              state == GRPC_CHANNEL_CONNECTING || state == GRPC_CHANNEL_IDLE);
 
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
-  cqv.Expect(tag(0xdead), true);
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(0xdead));
+  cqv.Expect(grpc_core::CqVerifier::tag(0xdead), true);
   cqv.Verify();
-
-  grpc_server_destroy(f.server);
-  grpc_channel_destroy(f.client);
-  grpc_completion_queue_shutdown(f.cq);
-  drain_cq(f.cq);
-  grpc_completion_queue_destroy(f.cq);
-  config.tear_down_data(&f);
 }
 
-void max_connection_idle(grpc_end2end_test_config config) {
+void max_connection_idle(const CoreTestConfiguration& config) {
   test_max_connection_idle(config);
 }
 
diff --git a/test/core/end2end/tests/max_message_length.cc b/test/core/end2end/tests/max_message_length.cc
index b778ae4..3127db5 100644
--- a/test/core/end2end/tests/max_message_length.cc
+++ b/test/core/end2end/tests/max_message_length.cc
@@ -16,9 +16,10 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include "absl/strings/match.h"
@@ -36,15 +37,10 @@
 #include "src/core/lib/slice/slice_internal.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "\n\n\nRunning test: %s/%s client_args=%s server_args=%s",
           test_name, config.name,
           grpc_core::ChannelArgs::FromC(client_args).ToString().c_str(),
@@ -52,66 +48,24 @@
   // We intentionally do not pass the client and server args to
   // create_fixture(), since we don't want the limit enforced on the
   // proxy, only on the backend server.
-  f = config.create_fixture(nullptr, nullptr);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev = grpc_completion_queue_next(
-      f->cq, grpc_timeout_seconds_to_deadline(5), nullptr);
-  GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
-  GPR_ASSERT(ev.tag == tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Test with request larger than the limit.
 // If send_limit is true, applies send limit on client; otherwise, applies
 // recv limit on server.
-static void test_max_message_length_on_request(grpc_end2end_test_config config,
-                                               bool send_limit,
-                                               bool use_service_config,
-                                               bool use_string_json_value) {
+static void test_max_message_length_on_request(
+    const CoreTestConfiguration& config, bool send_limit,
+    bool use_service_config, bool use_string_json_value) {
   gpr_log(GPR_INFO,
           "testing request with send_limit=%d use_service_config=%d "
           "use_string_json_value=%d",
           send_limit, use_service_config, use_string_json_value);
 
-  grpc_end2end_test_fixture f;
   grpc_call* c = nullptr;
   grpc_call* s = nullptr;
   grpc_op ops[6];
@@ -175,20 +129,20 @@
     }
   }
 
-  f = begin_test(config, "test_max_request_message_length", client_args,
-                 server_args);
+  auto f = begin_test(config, "test_max_request_message_length", client_args,
+                      server_args);
   {
     grpc_core::ExecCtx exec_ctx;
     if (client_args != nullptr) grpc_channel_args_destroy(client_args);
     if (server_args != nullptr) grpc_channel_args_destroy(server_args);
   }
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/service/method"),
-                               nullptr, gpr_inf_future(GPR_CLOCK_REALTIME),
-                               nullptr);
+  c = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string("/service/method"), nullptr,
+      gpr_inf_future(GPR_CLOCK_REALTIME), nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -224,21 +178,21 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   if (send_limit) {
-    cqv.Expect(tag(1), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(1), true);
     cqv.Verify();
     goto done;
   }
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -253,12 +207,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/service/method"));
@@ -282,24 +236,19 @@
 
   grpc_call_unref(c);
   if (s != nullptr) grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 // Test with response larger than the limit.
 // If send_limit is true, applies send limit on server; otherwise, applies
 // recv limit on client.
-static void test_max_message_length_on_response(grpc_end2end_test_config config,
-                                                bool send_limit,
-                                                bool use_service_config,
-                                                bool use_string_json_value) {
+static void test_max_message_length_on_response(
+    const CoreTestConfiguration& config, bool send_limit,
+    bool use_service_config, bool use_string_json_value) {
   gpr_log(GPR_INFO,
           "testing response with send_limit=%d use_service_config=%d "
           "use_string_json_value=%d",
           send_limit, use_service_config, use_string_json_value);
 
-  grpc_end2end_test_fixture f;
   grpc_call* c = nullptr;
   grpc_call* s = nullptr;
   grpc_op ops[6];
@@ -361,19 +310,19 @@
     }
   }
 
-  f = begin_test(config, "test_max_response_message_length", client_args,
-                 server_args);
+  auto f = begin_test(config, "test_max_response_message_length", client_args,
+                      server_args);
   {
     grpc_core::ExecCtx exec_ctx;
     if (client_args != nullptr) grpc_channel_args_destroy(client_args);
     if (server_args != nullptr) grpc_channel_args_destroy(server_args);
   }
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/service/method"),
-                               nullptr, gpr_inf_future(GPR_CLOCK_REALTIME),
-                               nullptr);
+  c = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string("/service/method"), nullptr,
+      gpr_inf_future(GPR_CLOCK_REALTIME), nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -409,15 +358,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -445,12 +394,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/service/method"));
@@ -471,8 +420,6 @@
 
   grpc_call_unref(c);
   if (s != nullptr) grpc_call_unref(s);
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 static grpc_metadata gzip_compression_override() {
@@ -487,9 +434,9 @@
 
 // Test receive message limit with compressed request larger than the limit
 static void test_max_receive_message_length_on_compressed_request(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   gpr_log(GPR_INFO, "test max receive message length on compressed request");
-  grpc_end2end_test_fixture f;
+
   grpc_call* c = nullptr;
   grpc_call* s = nullptr;
   grpc_op ops[6];
@@ -515,17 +462,17 @@
   grpc_channel_args* server_args =
       grpc_channel_args_copy_and_add(nullptr, arg, 1);
 
-  f = begin_test(config, "test_max_request_message_length", nullptr,
-                 server_args);
+  auto f = begin_test(config, "test_max_request_message_length", nullptr,
+                      server_args);
   {
     grpc_core::ExecCtx exec_ctx;
     grpc_channel_args_destroy(server_args);
   }
-  grpc_core::CqVerifier cqv(f.cq);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/service/method"),
-                               nullptr, gpr_inf_future(GPR_CLOCK_REALTIME),
-                               nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
+  c = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string("/service/method"), nullptr,
+      gpr_inf_future(GPR_CLOCK_REALTIME), nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -563,15 +510,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -581,8 +528,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -592,16 +539,16 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // WARNING!!
   // It's believed the following line (and the associated batch) is the only
   // test we have for failing a receive operation in a batch.
-  cqv.Expect(tag(102), false);
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/service/method"));
@@ -619,17 +566,14 @@
   grpc_byte_buffer_destroy(recv_payload);
   grpc_call_unref(c);
   if (s != nullptr) grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 // Test receive message limit with compressed response larger than the limit.
 static void test_max_receive_message_length_on_compressed_response(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   gpr_log(GPR_INFO,
           "testing max receive message length on compressed response");
-  grpc_end2end_test_fixture f;
+
   grpc_call* c = nullptr;
   grpc_call* s = nullptr;
   grpc_op ops[6];
@@ -655,18 +599,18 @@
   grpc_channel_args* client_args =
       grpc_channel_args_copy_and_add(nullptr, arg, 1);
 
-  f = begin_test(config, "test_max_response_message_length", client_args,
-                 nullptr);
+  auto f = begin_test(config, "test_max_response_message_length", client_args,
+                      nullptr);
   {
     grpc_core::ExecCtx exec_ctx;
     grpc_channel_args_destroy(client_args);
   }
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/service/method"),
-                               nullptr, gpr_inf_future(GPR_CLOCK_REALTIME),
-                               nullptr);
+  c = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string("/service/method"), nullptr,
+      gpr_inf_future(GPR_CLOCK_REALTIME), nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -702,15 +646,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   grpc_metadata compression_md = gzip_compression_override();
@@ -740,12 +684,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/service/method"));
@@ -763,12 +707,9 @@
 
   grpc_call_unref(c);
   if (s != nullptr) grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void max_message_length(grpc_end2end_test_config config) {
+void max_message_length(const CoreTestConfiguration& config) {
   test_max_message_length_on_request(config, false /* send_limit */,
                                      false /* use_service_config */,
                                      false /* use_string_json_value */);
diff --git a/test/core/end2end/tests/negative_deadline.cc b/test/core/end2end/tests/negative_deadline.cc
index df601a7..1c0935e 100644
--- a/test/core/end2end/tests/negative_deadline.cc
+++ b/test/core/end2end/tests/negative_deadline.cc
@@ -20,6 +20,9 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -27,70 +30,25 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture f, size_t num_ops) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f, size_t num_ops) {
   grpc_call* c;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -102,9 +60,9 @@
   gpr_log(GPR_DEBUG, "test with %" PRIuPTR " ops", num_ops);
 
   gpr_timespec deadline = gpr_inf_past(GPR_CLOCK_REALTIME);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -134,10 +92,11 @@
   op->reserved = nullptr;
   op++;
   GPR_ASSERT(num_ops <= (size_t)(op - ops));
-  error = grpc_call_start_batch(c, ops, num_ops, tag(1), nullptr);
+  error = grpc_call_start_batch(c, ops, num_ops, grpc_core::CqVerifier::tag(1),
+                                nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_DEADLINE_EXCEEDED);
@@ -149,17 +108,13 @@
   grpc_call_unref(c);
 }
 
-static void test_invoke_simple_request(grpc_end2end_test_config config,
+static void test_invoke_simple_request(const CoreTestConfiguration& config,
                                        size_t num_ops) {
-  grpc_end2end_test_fixture f;
-
-  f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
-  simple_request_body(config, f, num_ops);
-  end_test(&f);
-  config.tear_down_data(&f);
+  auto f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
+  simple_request_body(config, f.get(), num_ops);
 }
 
-void negative_deadline(grpc_end2end_test_config config) {
+void negative_deadline(const CoreTestConfiguration& config) {
   size_t i;
   for (i = 1; i <= 4; i++) {
     test_invoke_simple_request(config, i);
diff --git a/test/core/end2end/tests/no_logging.cc b/test/core/end2end/tests/no_logging.cc
index cce5786..6cde13c 100644
--- a/test/core/end2end/tests/no_logging.cc
+++ b/test/core/end2end/tests/no_logging.cc
@@ -16,10 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_cat.h"
@@ -33,6 +34,7 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/env.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
@@ -40,8 +42,6 @@
 
 enum { TIMEOUT = 200000 };
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 void gpr_default_log(gpr_log_func_args* args);
 
 static void test_no_log(gpr_log_func_args* args) {
@@ -65,65 +65,22 @@
   log_func(args);
 }
 
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture f) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -136,10 +93,10 @@
   int was_cancelled = 2;
   char* peer;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   peer = grpc_call_get_peer(c);
@@ -174,15 +131,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -212,12 +169,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -235,32 +192,28 @@
   grpc_call_unref(s);
 }
 
-static void test_invoke_simple_request(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
-
-  f = begin_test(config, "test_invoke_simple_request_with_no_error_logging",
+static void test_invoke_simple_request(const CoreTestConfiguration& config) {
+  auto f =
+      begin_test(config, "test_invoke_simple_request_with_no_error_logging",
                  nullptr, nullptr);
-  simple_request_body(config, f);
-  end_test(&f);
-  config.tear_down_data(&f);
+  simple_request_body(config, f.get());
 }
 
-static void test_invoke_10_simple_requests(grpc_end2end_test_config config) {
+static void test_invoke_10_simple_requests(
+    const CoreTestConfiguration& config) {
   int i;
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_invoke_10_simple_requests_with_no_error_logging",
                  nullptr, nullptr);
   for (i = 0; i < 10; i++) {
-    simple_request_body(config, f);
+    simple_request_body(config, f.get());
     gpr_log(GPR_INFO, "Passed simple request %d", i);
   }
-  simple_request_body(config, f);
-  end_test(&f);
-  config.tear_down_data(&f);
+  simple_request_body(config, f.get());
 }
 
 static void test_no_error_logging_in_entire_process(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   int i;
   gpr_atm_no_barrier_store(&g_log_func, (gpr_atm)test_no_error_log);
   for (i = 0; i < 10; i++) {
@@ -270,21 +223,20 @@
   gpr_atm_no_barrier_store(&g_log_func, (gpr_atm)gpr_default_log);
 }
 
-static void test_no_logging_in_one_request(grpc_end2end_test_config config) {
+static void test_no_logging_in_one_request(
+    const CoreTestConfiguration& config) {
   int i;
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_no_logging_in_last_request", nullptr, nullptr);
   for (i = 0; i < 10; i++) {
-    simple_request_body(config, f);
+    simple_request_body(config, f.get());
   }
   gpr_atm_no_barrier_store(&g_log_func, (gpr_atm)test_no_log);
-  simple_request_body(config, f);
+  simple_request_body(config, f.get());
   gpr_atm_no_barrier_store(&g_log_func, (gpr_atm)gpr_default_log);
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void no_logging(grpc_end2end_test_config config) {
+void no_logging(const CoreTestConfiguration& config) {
   grpc_core::SetEnv("GRPC_TRACE", "");
   gpr_set_log_verbosity(GPR_LOG_SEVERITY_DEBUG);
   grpc_tracer_set_enabled("all", 0);
diff --git a/test/core/end2end/tests/no_op.cc b/test/core/end2end/tests/no_op.cc
index e10d277..66c2074 100644
--- a/test/core/end2end/tests/no_op.cc
+++ b/test/core/end2end/tests/no_op.cc
@@ -16,77 +16,30 @@
 //
 //
 
-#include <stdint.h>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
-#include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
+static void test_no_op(const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "no-op", nullptr, nullptr);
 }
 
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void test_no_op(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = begin_test(config, "no-op", nullptr, nullptr);
-  end_test(&f);
-  config.tear_down_data(&f);
-}
-
-void no_op(grpc_end2end_test_config config) { test_no_op(config); }
+void no_op(const CoreTestConfiguration& config) { test_no_op(config); }
 
 void no_op_pre_init(void) {}
diff --git a/test/core/end2end/tests/payload.cc b/test/core/end2end/tests/payload.cc
index 3590836..a4b3cfd 100644
--- a/test/core/end2end/tests/payload.cc
+++ b/test/core/end2end/tests/payload.cc
@@ -16,11 +16,13 @@
 //
 //
 
-#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -30,66 +32,22 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Creates and returns a grpc_slice containing random alphanumeric characters.
 //
 static grpc_slice generate_random_slice() {
@@ -107,8 +65,8 @@
   return out;
 }
 
-static void request_response_with_payload(grpc_end2end_test_config /*config*/,
-                                          grpc_end2end_test_fixture f) {
+static void request_response_with_payload(
+    const CoreTestConfiguration& /*config*/, CoreTestFixture* f) {
   // Create large request and response bodies. These are big enough to require
   // multiple round trips to deliver to the peer, and their exact contents of
   // will be verified on completion.
@@ -121,7 +79,7 @@
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
   grpc_byte_buffer* response_payload =
       grpc_raw_byte_buffer_create(&response_payload_slice, 1);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -135,10 +93,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = n_seconds_from_now(60);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(60);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -179,15 +137,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -202,11 +160,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -229,12 +187,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -263,27 +221,23 @@
 // Client sends a request with payload, server reads then returns a response
 // payload and status.
 static void test_invoke_request_response_with_payload(
-    grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = begin_test(
-      config, "test_invoke_request_response_with_payload", nullptr, nullptr);
-  request_response_with_payload(config, f);
-  end_test(&f);
-  config.tear_down_data(&f);
+    const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "test_invoke_request_response_with_payload",
+                      nullptr, nullptr);
+  request_response_with_payload(config, f.get());
 }
 
 static void test_invoke_10_request_response_with_payload(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   int i;
-  grpc_end2end_test_fixture f = begin_test(
-      config, "test_invoke_10_request_response_with_payload", nullptr, nullptr);
+  auto f = begin_test(config, "test_invoke_10_request_response_with_payload",
+                      nullptr, nullptr);
   for (i = 0; i < 10; i++) {
-    request_response_with_payload(config, f);
+    request_response_with_payload(config, f.get());
   }
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void payload(grpc_end2end_test_config config) {
+void payload(const CoreTestConfiguration& config) {
   test_invoke_request_response_with_payload(config);
   test_invoke_10_request_response_with_payload(config);
 }
diff --git a/test/core/end2end/tests/ping.cc b/test/core/end2end/tests/ping.cc
index 831725a..83abe51 100644
--- a/test/core/end2end/tests/ping.cc
+++ b/test/core/end2end/tests/ping.cc
@@ -16,14 +16,14 @@
 //
 //
 
-#include <stdint.h>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
 #include "src/core/lib/channel/channel_args.h"
-#include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/surface/channel.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
@@ -31,77 +31,63 @@
 
 #define PING_NUM 5
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static void test_ping(grpc_end2end_test_config config,
+static void test_ping(const CoreTestConfiguration& config,
                       int min_time_between_pings_ms) {
-  grpc_end2end_test_fixture f = config.create_fixture(nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_connectivity_state state = GRPC_CHANNEL_IDLE;
   int i;
 
-  grpc_arg client_a[] = {
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA), 0),
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS), 1)};
-  grpc_arg server_a[] = {
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(
-              GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS),
-          0),
-      grpc_channel_arg_integer_create(
-          const_cast<char*>(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS), 1)};
-  grpc_channel_args client_args = {GPR_ARRAY_SIZE(client_a), client_a};
-  grpc_channel_args server_args = {GPR_ARRAY_SIZE(server_a), server_a};
+  auto client_args = grpc_core::ChannelArgs()
+                         .Set(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA, 0)
+                         .Set(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);
+  auto server_args =
+      grpc_core::ChannelArgs()
+          .Set(GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS, 0)
+          .Set(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);
 
-  config.init_client(&f, &client_args);
-  config.init_server(&f, &server_args);
+  f->InitClient(client_args);
+  f->InitServer(server_args);
 
-  grpc_channel_ping(f.client, f.cq, tag(0), nullptr);
-  cqv.Expect(tag(0), false);
+  grpc_channel_ping(f->client(), f->cq(), grpc_core::CqVerifier::tag(0),
+                    nullptr);
+  cqv.Expect(grpc_core::CqVerifier::tag(0), false);
 
   // check that we're still in idle, and start connecting
-  GPR_ASSERT(grpc_channel_check_connectivity_state(f.client, 1) ==
+  GPR_ASSERT(grpc_channel_check_connectivity_state(f->client(), 1) ==
              GRPC_CHANNEL_IDLE);
   // we'll go through some set of transitions (some might be missed), until
   // READY is reached
   while (state != GRPC_CHANNEL_READY) {
     grpc_channel_watch_connectivity_state(
-        f.client, state,
+        f->client(), state,
         gpr_time_add(grpc_timeout_seconds_to_deadline(3),
                      gpr_time_from_millis(min_time_between_pings_ms * PING_NUM,
                                           GPR_TIMESPAN)),
-        f.cq, tag(99));
-    cqv.Expect(tag(99), true);
+        f->cq(), grpc_core::CqVerifier::tag(99));
+    cqv.Expect(grpc_core::CqVerifier::tag(99), true);
     cqv.Verify();
-    state = grpc_channel_check_connectivity_state(f.client, 0);
+    state = grpc_channel_check_connectivity_state(f->client(), 0);
     GPR_ASSERT(state == GRPC_CHANNEL_READY ||
                state == GRPC_CHANNEL_CONNECTING ||
                state == GRPC_CHANNEL_TRANSIENT_FAILURE);
   }
 
   for (i = 1; i <= PING_NUM; i++) {
-    grpc_channel_ping(f.client, f.cq, tag(i), nullptr);
-    cqv.Expect(tag(i), true);
+    grpc_channel_ping(f->client(), f->cq(), grpc_core::CqVerifier::tag(i),
+                      nullptr);
+    cqv.Expect(grpc_core::CqVerifier::tag(i), true);
     cqv.Verify();
   }
 
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(0xdead));
-  cqv.Expect(tag(0xdead), true);
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(0xdead));
+  cqv.Expect(grpc_core::CqVerifier::tag(0xdead), true);
   cqv.Verify();
-
-  // cleanup server
-  grpc_server_destroy(f.server);
-
-  grpc_channel_destroy(f.client);
-  grpc_completion_queue_shutdown(f.cq);
-  grpc_completion_queue_destroy(f.cq);
-
-  config.tear_down_data(&f);
 }
 
-void ping(grpc_end2end_test_config config) {
+void ping(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION);
   test_ping(config, 0);
   test_ping(config, 100);
diff --git a/test/core/end2end/tests/ping_pong_streaming.cc b/test/core/end2end/tests/ping_pong_streaming.cc
index ff8c437..394da3a 100644
--- a/test/core/end2end/tests/ping_pong_streaming.cc
+++ b/test/core/end2end/tests/ping_pong_streaming.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -27,74 +29,29 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Client pings and server pongs. Repeat messages rounds before finishing.
-static void test_pingpong_streaming(grpc_end2end_test_config config,
+static void test_pingpong_streaming(const CoreTestConfiguration& config,
                                     int messages) {
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_pingpong_streaming", nullptr, nullptr);
+  auto f = begin_test(config, "test_pingpong_streaming", nullptr, nullptr);
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -115,10 +72,10 @@
   grpc_slice response_payload_slice =
       grpc_slice_from_copied_string("hello you");
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -145,15 +102,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(100));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(100));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(100), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(100), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -168,8 +125,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(101),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(101), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   for (i = 0; i < messages; i++) {
@@ -188,8 +145,8 @@
     op->flags = 0;
     op->reserved = nullptr;
     op++;
-    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                  nullptr);
+    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                  grpc_core::CqVerifier::tag(2), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
     memset(ops, 0, sizeof(ops));
@@ -200,9 +157,9 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
+                                  grpc_core::CqVerifier::tag(102), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv.Expect(tag(102), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(102), true);
     cqv.Verify();
 
     memset(ops, 0, sizeof(ops));
@@ -213,10 +170,10 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(103), nullptr);
+                                  grpc_core::CqVerifier::tag(103), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv.Expect(tag(103), true);
-    cqv.Expect(tag(2), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(2), true);
     cqv.Verify();
 
     grpc_byte_buffer_destroy(request_payload);
@@ -234,8 +191,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -248,14 +205,14 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
-  cqv.Expect(tag(3), true);
-  cqv.Expect(tag(101), true);
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   cqv.Verify();
 
   grpc_call_unref(c);
@@ -266,12 +223,9 @@
   grpc_metadata_array_destroy(&request_metadata_recv);
   grpc_call_details_destroy(&call_details);
   grpc_slice_unref(details);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void ping_pong_streaming(grpc_end2end_test_config config) {
+void ping_pong_streaming(const CoreTestConfiguration& config) {
   int i;
 
   for (i = 1; i < 10; i++) {
diff --git a/test/core/end2end/tests/proxy_auth.cc b/test/core/end2end/tests/proxy_auth.cc
index 54dcb77..2ce8e0c 100644
--- a/test/core/end2end/tests/proxy_auth.cc
+++ b/test/core/end2end/tests/proxy_auth.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -27,72 +29,28 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/end2end/fixtures/http_proxy_fixture.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture f) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -105,10 +63,10 @@
   int was_cancelled = 2;
   char* peer;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   peer = grpc_call_get_peer(c);
@@ -144,15 +102,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -184,12 +142,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -207,21 +165,18 @@
   grpc_call_unref(s);
 }
 
-static void test_invoke_proxy_auth(grpc_end2end_test_config config) {
+static void test_invoke_proxy_auth(const CoreTestConfiguration& config) {
   // Indicate that the proxy requires user auth
   grpc_arg client_arg;
   client_arg.type = GRPC_ARG_STRING;
   client_arg.key = const_cast<char*>(GRPC_ARG_HTTP_PROXY_AUTH_CREDS);
   client_arg.value.string = const_cast<char*>(GRPC_TEST_HTTP_PROXY_AUTH_CREDS);
   grpc_channel_args client_args = {1, &client_arg};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_invoke_proxy_auth", &client_args, nullptr);
-  simple_request_body(config, f);
-  end_test(&f);
-  config.tear_down_data(&f);
+  auto f = begin_test(config, "test_invoke_proxy_auth", &client_args, nullptr);
+  simple_request_body(config, f.get());
 }
 
-void proxy_auth(grpc_end2end_test_config config) {
+void proxy_auth(const CoreTestConfiguration& config) {
   test_invoke_proxy_auth(config);
 }
 
diff --git a/test/core/end2end/tests/registered_call.cc b/test/core/end2end/tests/registered_call.cc
index f547f8b..6d0b4c8 100644
--- a/test/core/end2end/tests/registered_call.cc
+++ b/test/core/end2end/tests/registered_call.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -26,71 +28,27 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture f, void* rc) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f, void* rc) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -102,9 +60,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_registered_call(
-      f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq, rc, deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_registered_call(f->client(), nullptr,
+                                          GRPC_PROPAGATE_DEFAULTS, f->cq(), rc,
+                                          deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -135,15 +94,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -166,12 +125,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -189,31 +148,27 @@
   grpc_call_unref(s);
 }
 
-static void test_invoke_simple_request(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
-  void* rc = grpc_channel_register_call(f.client, "/foo", nullptr, nullptr);
+static void test_invoke_simple_request(const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
+  void* rc = grpc_channel_register_call(f->client(), "/foo", nullptr, nullptr);
 
-  simple_request_body(config, f, rc);
-  end_test(&f);
-  config.tear_down_data(&f);
+  simple_request_body(config, f.get(), rc);
 }
 
-static void test_invoke_10_simple_requests(grpc_end2end_test_config config) {
+static void test_invoke_10_simple_requests(
+    const CoreTestConfiguration& config) {
   int i;
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_invoke_10_simple_requests", nullptr, nullptr);
-  void* rc = grpc_channel_register_call(f.client, "/foo", nullptr, nullptr);
+  void* rc = grpc_channel_register_call(f->client(), "/foo", nullptr, nullptr);
 
   for (i = 0; i < 10; i++) {
-    simple_request_body(config, f, rc);
+    simple_request_body(config, f.get(), rc);
     gpr_log(GPR_INFO, "Passed simple request %d", i);
   }
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void registered_call(grpc_end2end_test_config config) {
+void registered_call(const CoreTestConfiguration& config) {
   test_invoke_simple_request(config);
   test_invoke_10_simple_requests(config);
 }
diff --git a/test/core/end2end/tests/request_with_flags.cc b/test/core/end2end/tests/request_with_flags.cc
index 94557dd..408307f 100644
--- a/test/core/end2end/tests/request_with_flags.cc
+++ b/test/core/end2end/tests/request_with_flags.cc
@@ -19,6 +19,8 @@
 #include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_cat.h"
@@ -31,75 +33,37 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/transport/transport.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec one_second_from_now(void) { return n_seconds_from_now(1); }
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, one_second_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
+static gpr_timespec one_second_from_now(void) {
+  return grpc_timeout_seconds_to_deadline(1);
 }
 
 static void test_invoke_request_with_flags(
-    grpc_end2end_test_config config, uint32_t* flags_for_op,
+    const CoreTestConfiguration& config, uint32_t* flags_for_op,
     grpc_call_error call_start_batch_expected_result) {
   grpc_call* c;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
-  grpc_end2end_test_fixture f = begin_test(
+  auto f = begin_test(
       config,
       absl::StrCat("test_invoke_request_with_flags[",
                    absl::Hex(flags_for_op[GRPC_OP_SEND_INITIAL_METADATA]), ",",
@@ -111,7 +75,7 @@
                    grpc_call_error_to_string(call_start_batch_expected_result))
           .c_str(),
       nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -125,9 +89,9 @@
   grpc_call_error expectation;
 
   gpr_timespec deadline = one_second_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -164,15 +128,15 @@
   op->reserved = nullptr;
   op++;
   expectation = call_start_batch_expected_result;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(expectation == error);
 
   if (expectation == GRPC_CALL_OK) {
     if (config.feature_mask & FEATURE_MASK_DOES_NOT_SUPPORT_DEADLINES) {
       GPR_ASSERT(GRPC_CALL_OK == grpc_call_cancel(c, nullptr));
     }
-    cqv.Expect(tag(1), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(1), true);
     cqv.Verify();
     grpc_slice_unref(details);
   }
@@ -186,12 +150,9 @@
 
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void request_with_flags(grpc_end2end_test_config config) {
+void request_with_flags(const CoreTestConfiguration& config) {
   size_t i;
   uint32_t flags_for_op[GRPC_OP_RECV_CLOSE_ON_SERVER + 1];
 
diff --git a/test/core/end2end/tests/request_with_payload.cc b/test/core/end2end/tests/request_with_payload.cc
index 2b390d9..9c74ab1 100644
--- a/test/core/end2end/tests/request_with_payload.cc
+++ b/test/core/end2end/tests/request_with_payload.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -27,77 +29,34 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Client sends a request with payload, server reads then returns status.
-static void test_invoke_request_with_payload(grpc_end2end_test_config config) {
+static void test_invoke_request_with_payload(
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_invoke_request_with_payload", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -110,10 +69,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -149,14 +108,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101)));
-  cqv.Expect(tag(101), true);
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(101)));
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -171,11 +131,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -193,12 +153,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -218,12 +178,9 @@
 
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void request_with_payload(grpc_end2end_test_config config) {
+void request_with_payload(const CoreTestConfiguration& config) {
   test_invoke_request_with_payload(config);
 }
 
diff --git a/test/core/end2end/tests/resource_quota_server.cc b/test/core/end2end/tests/resource_quota_server.cc
index 2f40709..d4092f7 100644
--- a/test/core/end2end/tests/resource_quota_server.cc
+++ b/test/core/end2end/tests/resource_quota_server.cc
@@ -21,7 +21,9 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <functional>
 #include <initializer_list>
+#include <memory>
 
 #include "absl/strings/str_format.h"
 
@@ -32,68 +34,24 @@
 #include <grpc/status.h>
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
-#include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/gprpp/crash.h"
+#include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Creates and returns a grpc_slice containing random alphanumeric characters.
 //
 static grpc_slice generate_random_slice() {
@@ -111,7 +69,7 @@
   return out;
 }
 
-void resource_quota_server(grpc_end2end_test_config config) {
+void resource_quota_server(const CoreTestConfiguration& config) {
   grpc_resource_quota* resource_quota =
       grpc_resource_quota_create("test_server");
   grpc_resource_quota_resize(resource_quota, 5 * 1024 * 1024);
@@ -129,8 +87,7 @@
   arg.value.pointer.vtable = grpc_resource_quota_arg_vtable();
   grpc_channel_args args = {1, &arg};
 
-  grpc_end2end_test_fixture f =
-      begin_test(config, "resource_quota_server", nullptr, &args);
+  auto f = begin_test(config, "resource_quota_server", nullptr, &args);
 
   // Create large request and response bodies. These are big enough to require
   // multiple round trips to deliver to the peer, and their exact contents of
@@ -186,18 +143,19 @@
 
   for (int i = 0; i < NUM_CALLS; i++) {
     error = grpc_server_request_call(
-        f.server, &server_calls[i], &call_details[i], &request_metadata_recv[i],
-        f.cq, f.cq, tag(SERVER_START_BASE_TAG + i));
+        f->server(), &server_calls[i], &call_details[i],
+        &request_metadata_recv[i], f->cq(), f->cq(),
+        grpc_core::CqVerifier::tag(SERVER_START_BASE_TAG + i));
     GPR_ASSERT(GRPC_CALL_OK == error);
 
     pending_server_start_calls++;
   }
 
   for (int i = 0; i < NUM_CALLS; i++) {
-    client_calls[i] =
-        grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                                 f.cq, grpc_slice_from_static_string("/foo"),
-                                 nullptr, n_seconds_from_now(60), nullptr);
+    client_calls[i] = grpc_channel_create_call(
+        f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+        grpc_slice_from_static_string("/foo"), nullptr,
+        grpc_timeout_seconds_to_deadline(60), nullptr);
 
     memset(ops, 0, sizeof(ops));
     op = ops;
@@ -229,9 +187,9 @@
     op->flags = 0;
     op->reserved = nullptr;
     op++;
-    error = grpc_call_start_batch(client_calls[i], ops,
-                                  static_cast<size_t>(op - ops),
-                                  tag(CLIENT_BASE_TAG + i), nullptr);
+    error = grpc_call_start_batch(
+        client_calls[i], ops, static_cast<size_t>(op - ops),
+        grpc_core::CqVerifier::tag(CLIENT_BASE_TAG + i), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
     pending_client_calls++;
@@ -240,8 +198,8 @@
   while (pending_client_calls + pending_server_recv_calls +
              pending_server_end_calls >
          0) {
-    grpc_event ev =
-        grpc_completion_queue_next(f.cq, n_seconds_from_now(60), nullptr);
+    grpc_event ev = grpc_completion_queue_next(
+        f->cq(), grpc_timeout_seconds_to_deadline(60), nullptr);
     GPR_ASSERT(ev.type == GRPC_OP_COMPLETE);
 
     int ev_tag = static_cast<int>(reinterpret_cast<intptr_t>(ev.tag));
@@ -297,7 +255,7 @@
       op++;
       error = grpc_call_start_batch(
           server_calls[call_id], ops, static_cast<size_t>(op - ops),
-          tag(SERVER_RECV_BASE_TAG + call_id), nullptr);
+          grpc_core::CqVerifier::tag(SERVER_RECV_BASE_TAG + call_id), nullptr);
       GPR_ASSERT(GRPC_CALL_OK == error);
 
       GPR_ASSERT(pending_server_start_calls > 0);
@@ -338,7 +296,7 @@
       op++;
       error = grpc_call_start_batch(
           server_calls[call_id], ops, static_cast<size_t>(op - ops),
-          tag(SERVER_END_BASE_TAG + call_id), nullptr);
+          grpc_core::CqVerifier::tag(SERVER_END_BASE_TAG + call_id), nullptr);
       GPR_ASSERT(GRPC_CALL_OK == error);
 
       GPR_ASSERT(pending_server_recv_calls > 0);
@@ -365,12 +323,11 @@
           NUM_CALLS, cancelled_calls_on_server, cancelled_calls_on_client,
           deadline_exceeded, unavailable);
 
+  f->ShutdownServer();
+
   grpc_slice_unref(request_payload_slice);
   grpc_resource_quota_unref(resource_quota);
 
-  end_test(&f);
-  config.tear_down_data(&f);
-
   free(client_calls);
   free(server_calls);
   free(initial_metadata_recv);
diff --git a/test/core/end2end/tests/retry.cc b/test/core/end2end/tests/retry.cc
index e253607..46d650a 100644
--- a/test/core/end2end/tests/retry.cc
+++ b/test/core/end2end/tests/retry.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,67 +36,22 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests a basic retry scenario:
 // - 2 retries allowed for ABORTED status
 // - first attempt returns ABORTED
 // - second attempt returns OK
-static void test_retry(grpc_end2end_test_config config) {
+static void test_retry(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -137,13 +94,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry", &client_args, nullptr);
+  auto f = begin_test(config, "retry", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -180,15 +137,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Make sure the "grpc-previous-rpc-attempts" header was not sent in the
@@ -221,11 +178,11 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -234,11 +191,11 @@
   grpc_call_details_destroy(&call_details);
   grpc_call_details_init(&call_details);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   // Make sure the "grpc-previous-rpc-attempts" header was sent in the retry.
@@ -275,8 +232,8 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = response_payload;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -289,13 +246,13 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(203),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(203), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(202), true);
-  cqv.Expect(tag(203), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(203), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -315,12 +272,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry(grpc_end2end_test_config config) {
+void retry(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry(config);
 }
diff --git a/test/core/end2end/tests/retry_cancel_after_first_attempt_starts.cc b/test/core/end2end/tests/retry_cancel_after_first_attempt_starts.cc
index 5a31d1b..52fbaf9 100644
--- a/test/core/end2end/tests/retry_cancel_after_first_attempt_starts.cc
+++ b/test/core/end2end/tests/retry_cancel_after_first_attempt_starts.cc
@@ -14,9 +14,11 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -31,66 +33,21 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we can unref a call after the first attempt starts but
 // before any ops complete.  This should not cause a memory leak.
 static void test_retry_cancel_after_first_attempt_starts(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_op ops[6];
   grpc_op* op;
@@ -131,13 +88,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f = begin_test(
-      config, "retry_cancel_after_first_attempt_starts", &client_args, nullptr);
+  auto f = begin_test(config, "retry_cancel_after_first_attempt_starts",
+                      &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -156,8 +114,8 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client starts recv_initial_metadata and recv_message, but not
@@ -170,8 +128,8 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &response_payload_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client starts recv_trailing_metadata.
@@ -182,8 +140,8 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client unrefs the call without starting recv_trailing_metadata.
@@ -192,9 +150,9 @@
 
   // The send ops batch and the first recv ops batch will fail in most
   // fixtures but will pass in the proxy fixtures on some platforms.
-  cqv.Expect(tag(1), grpc_core::CqVerifier::AnyStatus());
-  cqv.Expect(tag(2), grpc_core::CqVerifier::AnyStatus());
-  cqv.Expect(tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), grpc_core::CqVerifier::AnyStatus());
+  cqv.Expect(grpc_core::CqVerifier::tag(2), grpc_core::CqVerifier::AnyStatus());
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
   cqv.Verify();
 
   grpc_slice_unref(details);
@@ -202,12 +160,10 @@
   grpc_metadata_array_destroy(&trailing_metadata_recv);
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(response_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_cancel_after_first_attempt_starts(grpc_end2end_test_config config) {
+void retry_cancel_after_first_attempt_starts(
+    const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_cancel_after_first_attempt_starts(config);
 }
diff --git a/test/core/end2end/tests/retry_cancel_during_delay.cc b/test/core/end2end/tests/retry_cancel_during_delay.cc
index c5aa417..09e279c 100644
--- a/test/core/end2end/tests/retry_cancel_during_delay.cc
+++ b/test/core/end2end/tests/retry_cancel_during_delay.cc
@@ -14,10 +14,11 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
 #include <initializer_list>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_cat.h"
@@ -43,64 +44,19 @@
 #include "test/core/end2end/tests/cancel_test_helpers.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests retry cancellation during backoff.
-static void test_retry_cancel_during_delay(grpc_end2end_test_config config,
+static void test_retry_cancel_during_delay(const CoreTestConfiguration& config,
                                            cancellation_mode mode) {
   grpc_call* c;
   grpc_call* s;
@@ -147,14 +103,14 @@
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
   std::string name = absl::StrCat("retry_cancel_during_delay/", mode.name);
-  grpc_end2end_test_fixture f =
-      begin_test(config, name.c_str(), &client_args, nullptr);
+  auto f = begin_test(config, name.c_str(), &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec expect_finish_before = n_seconds_from_now(10);
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec expect_finish_before = grpc_timeout_seconds_to_deadline(10);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -192,16 +148,16 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server gets a call and fails with retryable status.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -226,11 +182,11 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -241,15 +197,15 @@
 
   // Server should never get a second call, because the initial retry
   // delay is longer than the call's deadline.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Initiate cancellation.
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c, nullptr));
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   gpr_timespec finish_time = gpr_now(GPR_CLOCK_MONOTONIC);
@@ -277,12 +233,9 @@
   grpc_byte_buffer_destroy(response_payload_recv);
 
   grpc_call_unref(c);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_cancel_during_delay(grpc_end2end_test_config config) {
+void retry_cancel_during_delay(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   for (size_t i = 0; i < GPR_ARRAY_SIZE(cancellation_modes); ++i) {
     test_retry_cancel_during_delay(config, cancellation_modes[i]);
diff --git a/test/core/end2end/tests/retry_cancel_with_multiple_send_batches.cc b/test/core/end2end/tests/retry_cancel_with_multiple_send_batches.cc
index 9af3b16..bb48dab 100644
--- a/test/core/end2end/tests/retry_cancel_with_multiple_send_batches.cc
+++ b/test/core/end2end/tests/retry_cancel_with_multiple_send_batches.cc
@@ -16,10 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
 #include <initializer_list>
+#include <memory>
 #include <new>
 #include <string>
 
@@ -54,65 +55,20 @@
 #include "test/core/end2end/tests/cancel_test_helpers.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests cancellation with multiple send op batches.
 static void test_retry_cancel_with_multiple_send_batches(
-    grpc_end2end_test_config config, cancellation_mode mode) {
+    const CoreTestConfiguration& config, cancellation_mode mode) {
   grpc_call* c;
   grpc_op ops[6];
   grpc_op* op;
@@ -154,13 +110,13 @@
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
   std::string name =
       absl::StrCat("retry_cancel_with_multiple_send_batches/", mode.name);
-  grpc_end2end_test_fixture f =
-      begin_test(config, name.c_str(), &client_args, nullptr);
+  auto f = begin_test(config, name.c_str(), &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = n_seconds_from_now(3);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(3);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -179,8 +135,8 @@
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
   op->data.send_initial_metadata.count = 0;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Start a batch containing send_message.
@@ -189,8 +145,8 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = request_payload;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Start a batch containing send_trailing_metadata.
@@ -198,8 +154,8 @@
   op = ops;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Start a batch containing recv ops.
@@ -216,18 +172,18 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(4), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Initiate cancellation.
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c, nullptr));
 
   // Client ops should now complete.
-  cqv.Expect(tag(1), false);
-  cqv.Expect(tag(2), false);
-  cqv.Expect(tag(3), false);
-  cqv.Expect(tag(4), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   cqv.Verify();
 
   gpr_log(GPR_INFO, "status=%d expected=%d", status, mode.expect_status);
@@ -240,9 +196,6 @@
   grpc_byte_buffer_destroy(response_payload_recv);
 
   grpc_call_unref(c);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 namespace {
@@ -333,7 +286,8 @@
 
 }  // namespace
 
-void retry_cancel_with_multiple_send_batches(grpc_end2end_test_config config) {
+void retry_cancel_with_multiple_send_batches(
+    const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
@@ -341,7 +295,7 @@
         builder->channel_init()->RegisterStage(GRPC_CLIENT_SUBCHANNEL, 0,
                                                MaybeAddFilter);
       },
-      [config]() {
+      [&config]() {
         for (size_t i = 0; i < GPR_ARRAY_SIZE(cancellation_modes); ++i) {
           test_retry_cancel_with_multiple_send_batches(config,
                                                        cancellation_modes[i]);
diff --git a/test/core/end2end/tests/retry_cancellation.cc b/test/core/end2end/tests/retry_cancellation.cc
index 1f04ed6..e91c9e8 100644
--- a/test/core/end2end/tests/retry_cancellation.cc
+++ b/test/core/end2end/tests/retry_cancellation.cc
@@ -16,9 +16,10 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_cat.h"
@@ -39,64 +40,19 @@
 #include "test/core/end2end/tests/cancel_test_helpers.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests retry cancellation.
-static void test_retry_cancellation(grpc_end2end_test_config config,
+static void test_retry_cancellation(const CoreTestConfiguration& config,
                                     cancellation_mode mode) {
   grpc_call* c;
   grpc_call* s;
@@ -142,13 +98,13 @@
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
   std::string name = absl::StrCat("retry_cancellation/", mode.name);
-  grpc_end2end_test_fixture f =
-      begin_test(config, name.c_str(), &client_args, nullptr);
+  auto f = begin_test(config, name.c_str(), &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -186,16 +142,16 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server gets a call and fails with retryable status.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -220,11 +176,11 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -234,17 +190,17 @@
   grpc_call_details_init(&call_details);
 
   // Server gets a second call (the retry).
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   // Initiate cancellation.
   GPR_ASSERT(GRPC_CALL_OK == mode.initiate_cancel(c, nullptr));
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == mode.expect_status);
@@ -262,12 +218,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_cancellation(grpc_end2end_test_config config) {
+void retry_cancellation(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   for (size_t i = 0; i < GPR_ARRAY_SIZE(cancellation_modes); ++i) {
     test_retry_cancellation(config, cancellation_modes[i]);
diff --git a/test/core/end2end/tests/retry_disabled.cc b/test/core/end2end/tests/retry_disabled.cc
index 32216ac..f0257e3 100644
--- a/test/core/end2end/tests/retry_disabled.cc
+++ b/test/core/end2end/tests/retry_disabled.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,68 +36,23 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we don't retry when retries are disabled via the
 // GRPC_ARG_ENABLE_RETRIES channel arg, even when there is retry
 // configuration in the service config.
 // - 1 retry allowed for ABORTED status
 // - first attempt returns ABORTED but does not retry
-static void test_retry_disabled(grpc_end2end_test_config config) {
+static void test_retry_disabled(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -140,13 +97,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_disabled", &client_args, nullptr);
+  auto f = begin_test(config, "retry_disabled", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -183,15 +140,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -216,12 +173,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -241,12 +198,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_disabled(grpc_end2end_test_config config) {
+void retry_disabled(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_disabled(config);
 }
diff --git a/test/core/end2end/tests/retry_exceeds_buffer_size_in_delay.cc b/test/core/end2end/tests/retry_exceeds_buffer_size_in_delay.cc
index 4378c8a..04ab3ed 100644
--- a/test/core/end2end/tests/retry_exceeds_buffer_size_in_delay.cc
+++ b/test/core/end2end/tests/retry_exceeds_buffer_size_in_delay.cc
@@ -14,10 +14,12 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -33,62 +35,17 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests the case where the retry buffer size is exceeded during backoff.
 // - 1 retry allowed for ABORTED status
 // - buffer size set to 100 KiB (larger than initial metadata)
@@ -97,7 +54,7 @@
 // - client sends a 100 KiB message, thus exceeding the buffer size limit
 // - retry attempt gets ABORTED but is not retried
 static void test_retry_exceeds_buffer_size_in_delay(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -146,13 +103,14 @@
           const_cast<char*>(GRPC_ARG_PER_RPC_RETRY_BUFFER_SIZE), 102400),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f = begin_test(
-      config, "retry_exceeds_buffer_size_in_delay", &client_args, nullptr);
+  auto f = begin_test(config, "retry_exceeds_buffer_size_in_delay",
+                      &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
   gpr_timespec deadline = grpc_timeout_milliseconds_to_deadline(15000);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -186,16 +144,16 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server gets a call.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -221,10 +179,10 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -246,18 +204,18 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   // Server gets another call.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   // Server again sends ABORTED.  But this time, the client won't retry,
@@ -275,11 +233,11 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(202), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -300,12 +258,10 @@
   grpc_call_unref(c);
   grpc_call_unref(s);
 
-  end_test(&f);
-  config.tear_down_data(&f);
   gpr_free(buf);
 }
 
-void retry_exceeds_buffer_size_in_delay(grpc_end2end_test_config config) {
+void retry_exceeds_buffer_size_in_delay(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_exceeds_buffer_size_in_delay(config);
 }
diff --git a/test/core/end2end/tests/retry_exceeds_buffer_size_in_initial_batch.cc b/test/core/end2end/tests/retry_exceeds_buffer_size_in_initial_batch.cc
index 23bc66c..0688977 100644
--- a/test/core/end2end/tests/retry_exceeds_buffer_size_in_initial_batch.cc
+++ b/test/core/end2end/tests/retry_exceeds_buffer_size_in_initial_batch.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,62 +36,17 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we don't make any further attempts after we exceed the
 // max buffer size.
 // - 1 retry allowed for ABORTED status
@@ -97,7 +54,7 @@
 // - client sends a 3-byte message
 // - first attempt gets ABORTED but is not retried
 static void test_retry_exceeds_buffer_size_in_initial_batch(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -142,14 +99,14 @@
           const_cast<char*>(GRPC_ARG_PER_RPC_RETRY_BUFFER_SIZE), 2),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_exceeds_buffer_size_in_initial_batch",
-                 &client_args, nullptr);
+  auto f = begin_test(config, "retry_exceeds_buffer_size_in_initial_batch",
+                      &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -186,15 +143,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -219,12 +176,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -244,13 +201,10 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 void retry_exceeds_buffer_size_in_initial_batch(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_exceeds_buffer_size_in_initial_batch(config);
 }
diff --git a/test/core/end2end/tests/retry_exceeds_buffer_size_in_subsequent_batch.cc b/test/core/end2end/tests/retry_exceeds_buffer_size_in_subsequent_batch.cc
index 06c70a2..a8f8c99 100644
--- a/test/core/end2end/tests/retry_exceeds_buffer_size_in_subsequent_batch.cc
+++ b/test/core/end2end/tests/retry_exceeds_buffer_size_in_subsequent_batch.cc
@@ -16,10 +16,12 @@
 //
 //
 
-#include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -35,62 +37,17 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Similar to the retry_exceeds_buffer_size_in_initial_batch test, but we
 // don't exceed the buffer size until the second batch.
 // - 1 retry allowed for ABORTED status
@@ -98,7 +55,7 @@
 // - client sends a 100 KiB message
 // - first attempt gets ABORTED but is not retried
 static void test_retry_exceeds_buffer_size_in_subsequent_batch(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -148,14 +105,14 @@
           const_cast<char*>(GRPC_ARG_PER_RPC_RETRY_BUFFER_SIZE), 102400),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_exceeds_buffer_size_in_subsequent_batch",
-                 &client_args, nullptr);
+  auto f = begin_test(config, "retry_exceeds_buffer_size_in_subsequent_batch",
+                      &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -176,10 +133,10 @@
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
   op->data.send_initial_metadata.count = 0;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -200,15 +157,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -233,12 +190,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -259,13 +216,11 @@
   grpc_call_unref(c);
   grpc_call_unref(s);
 
-  end_test(&f);
-  config.tear_down_data(&f);
   gpr_free(buf);
 }
 
 void retry_exceeds_buffer_size_in_subsequent_batch(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_exceeds_buffer_size_in_subsequent_batch(config);
 }
diff --git a/test/core/end2end/tests/retry_lb_drop.cc b/test/core/end2end/tests/retry_lb_drop.cc
index f396e45..4b2a0b0 100644
--- a/test/core/end2end/tests/retry_lb_drop.cc
+++ b/test/core/end2end/tests/retry_lb_drop.cc
@@ -18,6 +18,7 @@
 #include <string.h>
 
 #include <algorithm>
+#include <functional>
 #include <memory>
 #include <utility>
 #include <vector>
@@ -107,67 +108,22 @@
 }  // namespace
 }  // namespace grpc_core
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we don't retry when the LB policy drops a call,
 // even when there is retry configuration in the service config.
 // - 1 retry allowed for UNAVAILABLE status
 // - first attempt returns UNAVAILABLE due to LB drop but does not retry
-static void test_retry_lb_drop(grpc_end2end_test_config config) {
+static void test_retry_lb_drop(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_op ops[6];
   grpc_op* op;
@@ -207,13 +163,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_lb_drop", &client_args, nullptr);
+  auto f = begin_test(config, "retry_lb_drop", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -242,11 +198,11 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNAVAILABLE);
@@ -265,12 +221,9 @@
   GPR_ASSERT(pick_args_seen.size() == 1);
 
   grpc_core::g_pick_args_vector = nullptr;
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_lb_drop(grpc_end2end_test_config config) {
+void retry_lb_drop(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_lb_drop(config);
 }
diff --git a/test/core/end2end/tests/retry_lb_fail.cc b/test/core/end2end/tests/retry_lb_fail.cc
index cd2e818..b3ebde6 100644
--- a/test/core/end2end/tests/retry_lb_fail.cc
+++ b/test/core/end2end/tests/retry_lb_fail.cc
@@ -14,10 +14,11 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
 #include <atomic>
+#include <functional>
+#include <memory>
 
 #include "absl/status/status.h"
 
@@ -43,69 +44,24 @@
 
 }  // namespace
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we retry properly when the LB policy fails the call before
 // it ever gets to the transport, even if recv_trailing_metadata isn't
 // started by the application until after the LB pick fails.
 // - 1 retry allowed for ABORTED status
 // - on first attempt, LB policy fails with ABORTED before application
 //   starts recv_trailing_metadata op
-static void test_retry_lb_fail(grpc_end2end_test_config config) {
+static void test_retry_lb_fail(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_op ops[6];
   grpc_op* op;
@@ -146,13 +102,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_lb_fail", &client_args, nullptr);
+  auto f = begin_test(config, "retry_lb_fail", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -165,11 +121,11 @@
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
   op->data.send_initial_metadata.count = 0;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(1), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), false);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -179,11 +135,11 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNAVAILABLE);
@@ -200,12 +156,9 @@
   int num_picks = g_num_lb_picks.load(std::memory_order_relaxed);
   gpr_log(GPR_INFO, "NUM LB PICKS: %d", num_picks);
   GPR_ASSERT(num_picks == 2);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_lb_fail(grpc_end2end_test_config config) {
+void retry_lb_fail(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_lb_fail(config);
 }
diff --git a/test/core/end2end/tests/retry_non_retriable_status.cc b/test/core/end2end/tests/retry_non_retriable_status.cc
index cda6f5f..ee36fbe 100644
--- a/test/core/end2end/tests/retry_non_retriable_status.cc
+++ b/test/core/end2end/tests/retry_non_retriable_status.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,66 +36,22 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we don't retry for non-retryable status codes.
 // - 1 retry allowed for ABORTED status
 // - first attempt gets INVALID_ARGUMENT, so no retry is done
-static void test_retry_non_retriable_status(grpc_end2end_test_config config) {
+static void test_retry_non_retriable_status(
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -136,13 +94,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "retry_non_retriable_status", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -179,15 +138,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -212,12 +171,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_INVALID_ARGUMENT);
@@ -237,12 +196,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_non_retriable_status(grpc_end2end_test_config config) {
+void retry_non_retriable_status(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_non_retriable_status(config);
 }
diff --git a/test/core/end2end/tests/retry_non_retriable_status_before_recv_trailing_metadata_started.cc b/test/core/end2end/tests/retry_non_retriable_status_before_recv_trailing_metadata_started.cc
index b2776df..c1c8e4d 100644
--- a/test/core/end2end/tests/retry_non_retriable_status_before_recv_trailing_metadata_started.cc
+++ b/test/core/end2end/tests/retry_non_retriable_status_before_recv_trailing_metadata_started.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,69 +36,24 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we don't retry for non-retryable status codes, even if
 // status is received before the recv_trailing_metadata op is started.
 // - 1 retry allowed for ABORTED status
 // - first attempt gets INVALID_ARGUMENT, so no retry is done
 static void
 test_retry_non_retriable_status_before_recv_trailing_metadata_started(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -139,15 +96,16 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f = begin_test(
+  auto f = begin_test(
       config,
       "retry_non_retriable_status_before_recv_trailing_metadata_started",
       &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -176,15 +134,15 @@
   op->op = GRPC_OP_RECV_INITIAL_METADATA;
   op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -209,12 +167,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -227,11 +185,11 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_INVALID_ARGUMENT);
@@ -251,13 +209,10 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 void retry_non_retriable_status_before_recv_trailing_metadata_started(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_non_retriable_status_before_recv_trailing_metadata_started(config);
 }
diff --git a/test/core/end2end/tests/retry_per_attempt_recv_timeout.cc b/test/core/end2end/tests/retry_per_attempt_recv_timeout.cc
index 3321c90..39e1deb 100644
--- a/test/core/end2end/tests/retry_per_attempt_recv_timeout.cc
+++ b/test/core/end2end/tests/retry_per_attempt_recv_timeout.cc
@@ -14,10 +14,11 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
 #include <initializer_list>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_format.h"
@@ -37,69 +38,24 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests perAttemptRecvTimeout:
 // - 2 retries allowed for ABORTED status
 // - first attempt does not receive a response until after perAttemptRecvTimeout
 // - second attempt returns ABORTED
 // - third attempt returns OK
 static void test_retry_per_attempt_recv_timeout(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_call* s0;
@@ -148,13 +104,14 @@
                                      const_cast<char*>(service_config.c_str())),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f = begin_test(
-      config, "test_retry_per_attempt_recv_timeout", &client_args, nullptr);
+  auto f = begin_test(config, "test_retry_per_attempt_recv_timeout",
+                      &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = n_seconds_from_now(10);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(10);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -186,16 +143,16 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server gets a call but does not respond to the call.
-  error =
-      grpc_server_request_call(f.server, &s0, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s0, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Make sure the "grpc-previous-rpc-attempts" header was not sent in the
@@ -212,11 +169,11 @@
   grpc_call_details_init(&call_details);
 
   // Server gets a second call.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   // Now we can unref the first call.
@@ -259,10 +216,10 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -272,11 +229,11 @@
   grpc_call_details_init(&call_details);
 
   // Server gets a third call.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(301));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(301));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(301), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(301), true);
   cqv.Verify();
 
   // Make sure the "grpc-previous-rpc-attempts" header was sent in the retry.
@@ -298,8 +255,8 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(302),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(302), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server sends OK status.
@@ -319,13 +276,13 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(303),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(303), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(302), true);
-  cqv.Expect(tag(303), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(302), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(303), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -345,12 +302,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_per_attempt_recv_timeout(grpc_end2end_test_config config) {
+void retry_per_attempt_recv_timeout(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_per_attempt_recv_timeout(config);
 }
diff --git a/test/core/end2end/tests/retry_per_attempt_recv_timeout_on_last_attempt.cc b/test/core/end2end/tests/retry_per_attempt_recv_timeout_on_last_attempt.cc
index faeeba3..bdb1696 100644
--- a/test/core/end2end/tests/retry_per_attempt_recv_timeout_on_last_attempt.cc
+++ b/test/core/end2end/tests/retry_per_attempt_recv_timeout_on_last_attempt.cc
@@ -14,10 +14,11 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
 #include <initializer_list>
+#include <memory>
 #include <string>
 
 #include "absl/strings/str_format.h"
@@ -36,67 +37,22 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests perAttemptRecvTimeout:
 // - 1 retry allowed for ABORTED status
 // - both attempts do not receive a response until after perAttemptRecvTimeout
 static void test_retry_per_attempt_recv_timeout_on_last_attempt(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_call* s0;
@@ -143,14 +99,15 @@
                                      const_cast<char*>(service_config.c_str())),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_retry_per_attempt_recv_timeout_on_last_attempt",
                  &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = n_seconds_from_now(10);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(10);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -181,16 +138,16 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server gets a call but does not respond to the call.
-  error =
-      grpc_server_request_call(f.server, &s0, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s0, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Make sure the "grpc-previous-rpc-attempts" header was not sent in the
@@ -207,11 +164,11 @@
   grpc_call_details_init(&call_details);
 
   // Server gets a second call, which it also does not respond to.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   // Now we can unref the first call.
@@ -232,7 +189,7 @@
   GPR_ASSERT(found_retry_header);
 
   // Client sees call completion.
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_CANCELLED);
@@ -252,13 +209,10 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 void retry_per_attempt_recv_timeout_on_last_attempt(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_per_attempt_recv_timeout_on_last_attempt(config);
 }
diff --git a/test/core/end2end/tests/retry_recv_initial_metadata.cc b/test/core/end2end/tests/retry_recv_initial_metadata.cc
index 2229734..426512d 100644
--- a/test/core/end2end/tests/retry_recv_initial_metadata.cc
+++ b/test/core/end2end/tests/retry_recv_initial_metadata.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,67 +36,23 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that receiving initial metadata commits the call.
 // - 1 retry allowed for ABORTED status
 // - first attempt receives initial metadata before trailing metadata,
 //   so no retry is done even though status was ABORTED
-static void test_retry_recv_initial_metadata(grpc_end2end_test_config config) {
+static void test_retry_recv_initial_metadata(
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -141,13 +99,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "retry_recv_initial_metadata", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -184,15 +143,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -217,11 +176,11 @@
   op->data.send_initial_metadata.count = 1;
   op->data.send_initial_metadata.metadata = &initial_metadata_from_server;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -234,12 +193,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -259,12 +218,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_recv_initial_metadata(grpc_end2end_test_config config) {
+void retry_recv_initial_metadata(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_recv_initial_metadata(config);
 }
diff --git a/test/core/end2end/tests/retry_recv_message.cc b/test/core/end2end/tests/retry_recv_message.cc
index c04914a..f28804c 100644
--- a/test/core/end2end/tests/retry_recv_message.cc
+++ b/test/core/end2end/tests/retry_recv_message.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,67 +36,22 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that receiving a message commits the call.
 // - 1 retry allowed for ABORTED status
 // - first attempt receives a message and therefore does not retry even
 //   though the final status is ABORTED
-static void test_retry_recv_message(grpc_end2end_test_config config) {
+static void test_retry_recv_message(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -137,13 +94,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_recv_message", &client_args, nullptr);
+  auto f = begin_test(config, "retry_recv_message", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -180,15 +137,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -216,12 +173,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -241,12 +198,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_recv_message(grpc_end2end_test_config config) {
+void retry_recv_message(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_recv_message(config);
 }
diff --git a/test/core/end2end/tests/retry_recv_message_replay.cc b/test/core/end2end/tests/retry_recv_message_replay.cc
index 4914131..ca3ad5b 100644
--- a/test/core/end2end/tests/retry_recv_message_replay.cc
+++ b/test/core/end2end/tests/retry_recv_message_replay.cc
@@ -16,9 +16,10 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <new>
 
 #include "absl/status/status.h"
@@ -49,69 +50,25 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests the fix for a bug found in real-world code where recv_message
 // was incorrectly replayed on a call attempt that it was already sent
 // to when the recv_message completion had already been returned but was
 // deferred at the point where recv_trailing_metadata was started from
 // the surface.  This resulted in ASAN failures caused by not unreffing
 // a grpc_error.
-static void test_retry_recv_message_replay(grpc_end2end_test_config config) {
+static void test_retry_recv_message_replay(
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -154,13 +111,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "retry_recv_message_replay", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -185,8 +143,8 @@
   op->op = GRPC_OP_RECV_INITIAL_METADATA;
   op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Start a batch containing recv_message.
@@ -195,8 +153,8 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &response_payload_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Start a batch containing recv_trailing_metadata.
@@ -207,16 +165,16 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server should get a call.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Server fails with status ABORTED.
@@ -233,17 +191,17 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // In principle, the server batch should complete before the client
   // batches, but in the proxy fixtures, there are multiple threads
   // involved, so the completion order tends to be a little racy.
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
-  cqv.Expect(tag(2), true);
-  cqv.Expect(tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -263,9 +221,6 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 namespace {
@@ -352,7 +307,7 @@
 
 }  // namespace
 
-void retry_recv_message_replay(grpc_end2end_test_config config) {
+void retry_recv_message_replay(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
diff --git a/test/core/end2end/tests/retry_recv_trailing_metadata_error.cc b/test/core/end2end/tests/retry_recv_trailing_metadata_error.cc
index ce47c33..7fbccf2 100644
--- a/test/core/end2end/tests/retry_recv_trailing_metadata_error.cc
+++ b/test/core/end2end/tests/retry_recv_trailing_metadata_error.cc
@@ -14,9 +14,10 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <new>
 
 #include "absl/status/status.h"
@@ -47,62 +48,17 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we honor the error passed to recv_trailing_metadata_ready
 // when determining the call's status, even if the op completion runs before
 // the recv_trailing_metadata op is started from the surface.
@@ -110,7 +66,7 @@
 // - server returns ABORTED, but filter overwrites to INVALID_ARGUMENT,
 //   so no retry is done
 static void test_retry_recv_trailing_metadata_error(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -153,13 +109,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f = begin_test(
-      config, "retry_recv_trailing_metadata_error", &client_args, nullptr);
+  auto f = begin_test(config, "retry_recv_trailing_metadata_error",
+                      &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -191,15 +148,15 @@
   op->op = GRPC_OP_RECV_INITIAL_METADATA;
   op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -224,12 +181,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -239,11 +196,11 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_INVALID_ARGUMENT);
@@ -263,9 +220,6 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 namespace {
@@ -361,7 +315,7 @@
 
 }  // namespace
 
-void retry_recv_trailing_metadata_error(grpc_end2end_test_config config) {
+void retry_recv_trailing_metadata_error(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
diff --git a/test/core/end2end/tests/retry_send_initial_metadata_refs.cc b/test/core/end2end/tests/retry_send_initial_metadata_refs.cc
index 98e229e..cf138af 100644
--- a/test/core/end2end/tests/retry_send_initial_metadata_refs.cc
+++ b/test/core/end2end/tests/retry_send_initial_metadata_refs.cc
@@ -16,9 +16,10 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include <grpc/byte_buffer.h>
@@ -36,69 +37,24 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we hold refs to send_initial_metadata payload while
 // cached, even after the caller has released its refs:
 // - 2 retries allowed for ABORTED status
 // - first attempt returns ABORTED
 // - second attempt returns OK
 static void test_retry_send_initial_metadata_refs(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -142,13 +98,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f = begin_test(
-      config, "retry_send_initial_metadata_refs", &client_args, nullptr);
+  auto f = begin_test(config, "retry_send_initial_metadata_refs", &client_args,
+                      nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -192,10 +149,10 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   for (size_t i = 0; i < client_send_initial_metadata.count; ++i) {
@@ -217,15 +174,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Make sure the "grpc-previous-rpc-attempts" header was not sent in the
@@ -258,11 +215,11 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -271,11 +228,11 @@
   grpc_call_details_destroy(&call_details);
   grpc_call_details_init(&call_details);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   // Make sure the "grpc-previous-rpc-attempts" header was sent in the retry.
@@ -305,8 +262,8 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -325,13 +282,13 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(203),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(203), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(202), true);
-  cqv.Expect(tag(203), true);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(203), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -351,12 +308,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_send_initial_metadata_refs(grpc_end2end_test_config config) {
+void retry_send_initial_metadata_refs(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_send_initial_metadata_refs(config);
 }
diff --git a/test/core/end2end/tests/retry_send_op_fails.cc b/test/core/end2end/tests/retry_send_op_fails.cc
index 7f252ca..46bc7fd 100644
--- a/test/core/end2end/tests/retry_send_op_fails.cc
+++ b/test/core/end2end/tests/retry_send_op_fails.cc
@@ -16,9 +16,10 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <new>
 
 #include "absl/status/status.h"
@@ -49,62 +50,17 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests failure on a send op batch:
 // - 2 retries allowed for ABORTED status
 // - on the first call attempt, the batch containing the
@@ -112,7 +68,7 @@
 //   all without ever going out on the wire
 // - second attempt returns ABORTED but does not retry, because only 2
 //   attempts are allowed
-static void test_retry_send_op_fails(grpc_end2end_test_config config) {
+static void test_retry_send_op_fails(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -155,13 +111,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_send_op_fails", &client_args, nullptr);
+  auto f = begin_test(config, "retry_send_op_fails", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -188,8 +144,8 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Start a batch containing recv ops.
@@ -206,20 +162,20 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client send ops should now complete.
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   // Server should get a call.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Server fails with status ABORTED.
@@ -236,15 +192,15 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // In principle, the server batch should complete before the client
   // recv ops batch, but in the proxy fixtures, there are multiple threads
   // involved, so the completion order tends to be a little racy.
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -278,9 +234,6 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 namespace {
@@ -368,7 +321,7 @@
 
 }  // namespace
 
-void retry_send_op_fails(grpc_end2end_test_config config) {
+void retry_send_op_fails(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
diff --git a/test/core/end2end/tests/retry_send_recv_batch.cc b/test/core/end2end/tests/retry_send_recv_batch.cc
index 9942620..779942b 100644
--- a/test/core/end2end/tests/retry_send_recv_batch.cc
+++ b/test/core/end2end/tests/retry_send_recv_batch.cc
@@ -14,9 +14,11 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -31,67 +33,22 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests a scenario where there is a batch containing both a send op and
 // a recv op, where the send op completes but the recv op does not, and
 // then a subsequent recv op is started.  This ensures that we do not
 // incorrectly attempt to replay the send op.
-static void test_retry_send_recv_batch(grpc_end2end_test_config config) {
+static void test_retry_send_recv_batch(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -133,13 +90,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_send_recv_batch", &client_args, nullptr);
+  auto f = begin_test(config, "retry_send_recv_batch", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -159,8 +116,8 @@
   op->op = GRPC_OP_RECV_INITIAL_METADATA;
   op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client starts a batch with send_message and recv_trailing_metadata.
@@ -174,16 +131,16 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server gets a call.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Client starts a batch containing recv_message.
@@ -192,8 +149,8 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &response_payload_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server fails the call with a non-retriable status.
@@ -210,14 +167,14 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
-  cqv.Expect(tag(2), true);
-  cqv.Expect(tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_PERMISSION_DENIED);
@@ -237,12 +194,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_send_recv_batch(grpc_end2end_test_config config) {
+void retry_send_recv_batch(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_send_recv_batch(config);
 }
diff --git a/test/core/end2end/tests/retry_server_pushback_delay.cc b/test/core/end2end/tests/retry_server_pushback_delay.cc
index c3a89a6..e645ba1 100644
--- a/test/core/end2end/tests/retry_server_pushback_delay.cc
+++ b/test/core/end2end/tests/retry_server_pushback_delay.cc
@@ -19,6 +19,8 @@
 #include <inttypes.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
@@ -40,67 +42,23 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we honor server push-back delay.
 // - 2 retries allowed for ABORTED status
 // - first attempt gets ABORTED with a long delay
 // - second attempt succeeds
-static void test_retry_server_pushback_delay(grpc_end2end_test_config config) {
+static void test_retry_server_pushback_delay(
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -148,13 +106,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "retry_server_pushback_delay", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -192,15 +151,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify(grpc_core::Duration::Seconds(20));
 
   peer = grpc_call_get_peer(s);
@@ -226,11 +185,11 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   gpr_timespec before_retry = gpr_now(GPR_CLOCK_MONOTONIC);
@@ -241,11 +200,11 @@
   grpc_call_details_destroy(&call_details);
   grpc_call_details_init(&call_details);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   gpr_timespec after_retry = gpr_now(GPR_CLOCK_MONOTONIC);
@@ -281,12 +240,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(202), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   gpr_log(GPR_INFO, "status=%d message=\"%s\"", status,
@@ -308,12 +267,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_server_pushback_delay(grpc_end2end_test_config config) {
+void retry_server_pushback_delay(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_server_pushback_delay(config);
 }
diff --git a/test/core/end2end/tests/retry_server_pushback_disabled.cc b/test/core/end2end/tests/retry_server_pushback_disabled.cc
index fa37385..43dfcd4 100644
--- a/test/core/end2end/tests/retry_server_pushback_disabled.cc
+++ b/test/core/end2end/tests/retry_server_pushback_disabled.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,68 +36,23 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we don't retry when disabled by server push-back.
 // - 2 retries allowed for ABORTED status
 // - first attempt gets ABORTED
 // - second attempt gets ABORTED but server push back disables retrying
 static void test_retry_server_pushback_disabled(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -143,13 +100,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f = begin_test(
-      config, "retry_server_pushback_disabled", &client_args, nullptr);
+  auto f = begin_test(config, "retry_server_pushback_disabled", &client_args,
+                      nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -186,15 +144,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -219,11 +177,11 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -232,11 +190,11 @@
   grpc_call_details_destroy(&call_details);
   grpc_call_details_init(&call_details);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -262,12 +220,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(202), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -287,12 +245,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_server_pushback_disabled(grpc_end2end_test_config config) {
+void retry_server_pushback_disabled(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_server_pushback_disabled(config);
 }
diff --git a/test/core/end2end/tests/retry_streaming.cc b/test/core/end2end/tests/retry_streaming.cc
index a4c9510..6a4f8e0 100644
--- a/test/core/end2end/tests/retry_streaming.cc
+++ b/test/core/end2end/tests/retry_streaming.cc
@@ -16,9 +16,10 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <string>
 
 #include <grpc/byte_buffer.h>
@@ -38,62 +39,17 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests retrying a streaming RPC.  This is the same as
 // the basic retry test, except that the client sends two messages on the
 // call before the initial attempt fails.
@@ -103,7 +59,7 @@
 // replayed ops happen under the hood -- they are not surfaced to the
 // C-core API, and therefore we have no way to inject the commit at the
 // right point.
-static void test_retry_streaming(grpc_end2end_test_config config) {
+static void test_retry_streaming(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -158,17 +114,17 @@
               "  } ]\n"
               "}"))};
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_streaming", &client_args, nullptr);
+  auto f = begin_test(config, "retry_streaming", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   grpc_core::channelz::ChannelNode* channelz_channel =
-      grpc_channel_get_channelz_node(f.client);
+      grpc_channel_get_channelz_node(f->client());
 
   GPR_ASSERT(c);
 
@@ -198,8 +154,8 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client sends initial metadata and a message.
@@ -211,18 +167,18 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = request_payload;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   // Server gets a call with received initial metadata.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -240,10 +196,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   // Client sends a second message.
@@ -252,10 +208,10 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = request2_payload;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
   cqv.Verify();
 
   // Server receives the second message.
@@ -264,10 +220,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request2_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
   cqv.Verify();
 
   // Server sends both initial and trailing metadata.
@@ -284,10 +240,10 @@
   op->data.send_status_from_server.status = GRPC_STATUS_ABORTED;
   op->data.send_status_from_server.status_details = &status_details;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   cqv.Verify();
 
   // Clean up from first attempt.
@@ -305,11 +261,11 @@
   request2_payload_recv = nullptr;
 
   // Server gets a second call (the retry).
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -327,10 +283,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
   cqv.Verify();
 
   // Server receives a second message.
@@ -339,10 +295,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request2_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(203),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(203), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(203), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(203), true);
   cqv.Verify();
 
   // Client sends a third message and a close.
@@ -353,10 +309,10 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(4), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(4), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   cqv.Verify();
 
   // Server receives a third message.
@@ -365,10 +321,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request3_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(204),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(204), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(204), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(204), true);
   cqv.Verify();
 
   // Server receives a close and sends initial metadata, a message, and
@@ -391,11 +347,11 @@
   op->data.send_status_from_server.status = GRPC_STATUS_ABORTED;
   op->data.send_status_from_server.status_details = &status_details;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(205),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(205), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(205), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(205), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -436,12 +392,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_streaming(grpc_end2end_test_config config) {
+void retry_streaming(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
 
   test_retry_streaming(config);
diff --git a/test/core/end2end/tests/retry_streaming_after_commit.cc b/test/core/end2end/tests/retry_streaming_after_commit.cc
index 323c4ae..d42c61c 100644
--- a/test/core/end2end/tests/retry_streaming_after_commit.cc
+++ b/test/core/end2end/tests/retry_streaming_after_commit.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,65 +36,21 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we can continue to send/recv messages on a streaming call
 // after retries are committed.
-static void test_retry_streaming_after_commit(grpc_end2end_test_config config) {
+static void test_retry_streaming_after_commit(
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -143,13 +101,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "retry_streaming_after_commit", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -175,8 +134,8 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &response_payload_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client sends initial metadata and a message.
@@ -188,18 +147,18 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = request_payload;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
   cqv.Verify();
 
   // Server gets a call with received initial metadata.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -217,10 +176,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   // Server sends initial metadata and a message.
@@ -232,12 +191,12 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = response_payload;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
   // Client receives initial metadata and a message.
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   // Client sends a second message and a close.
@@ -248,10 +207,10 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(4), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(4), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   cqv.Verify();
 
   // Server receives a second message.
@@ -260,10 +219,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request2_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   cqv.Verify();
 
   // Server receives a close, sends a second message, and sends status.
@@ -282,10 +241,10 @@
   op->data.send_status_from_server.status = GRPC_STATUS_ABORTED;
   op->data.send_status_from_server.status_details = &status_details;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(105),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(105), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(105), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(105), true);
   cqv.Verify();
 
   // Client receives a second message.
@@ -294,10 +253,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &response2_payload_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(5),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(5), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(5), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(5), true);
   cqv.Verify();
 
   // Client receives status.
@@ -308,10 +267,10 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -341,12 +300,9 @@
   grpc_byte_buffer_destroy(response2_payload_recv);
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_streaming_after_commit(grpc_end2end_test_config config) {
+void retry_streaming_after_commit(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_streaming_after_commit(config);
 }
diff --git a/test/core/end2end/tests/retry_streaming_succeeds_before_replay_finished.cc b/test/core/end2end/tests/retry_streaming_succeeds_before_replay_finished.cc
index 73ca2f9..395e77d 100644
--- a/test/core/end2end/tests/retry_streaming_succeeds_before_replay_finished.cc
+++ b/test/core/end2end/tests/retry_streaming_succeeds_before_replay_finished.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,66 +36,21 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we correctly clean up if the second attempt finishes
 // before we have finished replaying all of the send ops.
 static void test_retry_streaming_succeeds_before_replay_finished(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -144,13 +101,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_streaming", &client_args, nullptr);
+  auto f = begin_test(config, "retry_streaming", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -181,8 +138,8 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client sends initial metadata and a message.
@@ -194,18 +151,18 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = request_payload;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   // Server gets a call with received initial metadata.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -223,10 +180,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   // Client sends a second message.
@@ -235,10 +192,10 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = request2_payload;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
   cqv.Verify();
 
   // Server receives the second message.
@@ -247,10 +204,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request2_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
   cqv.Verify();
 
   // Client sends a third message.
@@ -259,10 +216,10 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = request3_payload;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(4), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(4), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   cqv.Verify();
 
   // Server receives the third message.
@@ -271,10 +228,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request3_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   cqv.Verify();
 
   // Server sends both initial and trailing metadata.
@@ -291,10 +248,10 @@
   op->data.send_status_from_server.status = GRPC_STATUS_ABORTED;
   op->data.send_status_from_server.status_details = &status_details;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(105),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(105), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(105), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(105), true);
   cqv.Verify();
 
   // Clean up from first attempt.
@@ -316,11 +273,11 @@
   request3_payload_recv = nullptr;
 
   // Server gets a second call (the retry).
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -338,10 +295,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
   cqv.Verify();
 
   // Server sends initial metadata, a message, and trailing metadata.
@@ -360,11 +317,11 @@
   op->data.send_status_from_server.status = GRPC_STATUS_ABORTED;
   op->data.send_status_from_server.status_details = &status_details;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(205),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(205), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(205), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(205), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -387,13 +344,10 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 void retry_streaming_succeeds_before_replay_finished(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_streaming_succeeds_before_replay_finished(config);
 }
diff --git a/test/core/end2end/tests/retry_throttled.cc b/test/core/end2end/tests/retry_throttled.cc
index 416bf0c..5438dc7 100644
--- a/test/core/end2end/tests/retry_throttled.cc
+++ b/test/core/end2end/tests/retry_throttled.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,66 +36,21 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we don't retry when throttled.
 // - 1 retry allowed for ABORTED status
 // - first attempt gets ABORTED but is over limit, so no retry is done
-static void test_retry_throttled(grpc_end2end_test_config config) {
+static void test_retry_throttled(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -143,13 +100,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_throttled", &client_args, nullptr);
+  auto f = begin_test(config, "retry_throttled", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -186,15 +143,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -219,12 +176,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -244,12 +201,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_throttled(grpc_end2end_test_config config) {
+void retry_throttled(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_throttled(config);
 }
diff --git a/test/core/end2end/tests/retry_too_many_attempts.cc b/test/core/end2end/tests/retry_too_many_attempts.cc
index 1aab106..f4d1bca 100644
--- a/test/core/end2end/tests/retry_too_many_attempts.cc
+++ b/test/core/end2end/tests/retry_too_many_attempts.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -34,67 +36,22 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we stop retrying after the configured number of attempts.
 // - 1 retry allowed for ABORTED status
 // - first attempt gets ABORTED
 // - second attempt gets ABORTED but does not retry
-static void test_retry_too_many_attempts(grpc_end2end_test_config config) {
+static void test_retry_too_many_attempts(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -137,13 +94,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_too_many_attempts", &client_args, nullptr);
+  auto f = begin_test(config, "retry_too_many_attempts", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -180,15 +137,15 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -213,11 +170,11 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   grpc_call_unref(s);
@@ -226,11 +183,11 @@
   grpc_call_details_destroy(&call_details);
   grpc_call_details_init(&call_details);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -255,12 +212,12 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(202), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_ABORTED);
@@ -280,12 +237,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_too_many_attempts(grpc_end2end_test_config config) {
+void retry_too_many_attempts(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_too_many_attempts(config);
 }
diff --git a/test/core/end2end/tests/retry_transparent_goaway.cc b/test/core/end2end/tests/retry_transparent_goaway.cc
index a31ddbc..1b6486d 100644
--- a/test/core/end2end/tests/retry_transparent_goaway.cc
+++ b/test/core/end2end/tests/retry_transparent_goaway.cc
@@ -14,9 +14,10 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <new>
 
 #include "absl/status/status.h"
@@ -48,64 +49,19 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests transparent retries when the call was never sent out on the wire.
-static void test_retry_transparent_goaway(grpc_end2end_test_config config) {
+static void test_retry_transparent_goaway(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -128,13 +84,13 @@
   int was_cancelled = 2;
   char* peer;
 
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_transparent_goaway", nullptr, nullptr);
+  auto f = begin_test(config, "retry_transparent_goaway", nullptr, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -161,8 +117,8 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Start a batch containing recv ops.
@@ -179,20 +135,20 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client send ops should now complete.
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   // Server should get a call.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Server receives the request.
@@ -201,10 +157,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   // Server sends a response with status OK.
@@ -224,15 +180,15 @@
   op->data.send_status_from_server.status = GRPC_STATUS_OK;
   op->data.send_status_from_server.status_details = &status_details;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // In principle, the server batch should complete before the client
   // recv ops batch, but in the proxy fixtures, there are multiple threads
   // involved, so the completion order tends to be a little racy.
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -263,9 +219,6 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 namespace {
@@ -362,7 +315,7 @@
 
 }  // namespace
 
-void retry_transparent_goaway(grpc_end2end_test_config config) {
+void retry_transparent_goaway(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
diff --git a/test/core/end2end/tests/retry_transparent_max_concurrent_streams.cc b/test/core/end2end/tests/retry_transparent_max_concurrent_streams.cc
index 2dc427b..e75e948 100644
--- a/test/core/end2end/tests/retry_transparent_max_concurrent_streams.cc
+++ b/test/core/end2end/tests/retry_transparent_max_concurrent_streams.cc
@@ -14,9 +14,11 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -30,62 +32,17 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    const grpc_core::ChannelArgs& client_args,
+    const grpc_core::ChannelArgs& server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(client_args, server_args);
+  f->InitServer(server_args);
+  f->InitClient(client_args);
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests transparent retries when the call was never sent out on the wire.
 // This is similar to retry_transparent_not_sent_on_wire, except that
 // instead of simulating the response with a filter, we actually have
@@ -96,7 +53,7 @@
 // restarted.  The second call will fail in that transport instance and
 // will be transparently retried after the server starts up again.
 static void test_retry_transparent_max_concurrent_streams(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_op ops[6];
   grpc_op* op;
   grpc_slice request_payload_slice = grpc_slice_from_static_string("foo");
@@ -104,22 +61,20 @@
   grpc_slice status_details = grpc_slice_from_static_string("xyz");
   grpc_call_error error;
 
-  grpc_arg arg = grpc_channel_arg_integer_create(
-      const_cast<char*>(GRPC_ARG_MAX_CONCURRENT_STREAMS), 1);
-  grpc_channel_args server_args = {1, &arg};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_transparent_max_concurrent_streams", nullptr,
-                 &server_args);
+  auto server_args =
+      grpc_core::ChannelArgs().Set(GRPC_ARG_MAX_CONCURRENT_STREAMS, 1);
+  auto f = begin_test(config, "retry_transparent_max_concurrent_streams",
+                      grpc_core::ChannelArgs(), server_args);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
 
   // Client starts a call.
-  grpc_call* c =
-      grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/service/method"),
-                               nullptr, deadline, nullptr);
+  grpc_call* c = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string("/service/method"), nullptr, deadline,
+      nullptr);
   GPR_ASSERT(c);
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
@@ -151,8 +106,8 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server should get a call.
@@ -161,11 +116,11 @@
   grpc_metadata_array_init(&request_metadata_recv);
   grpc_call_details call_details;
   grpc_call_details_init(&call_details);
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/service/method"));
   grpc_call_details_destroy(&call_details);
@@ -174,10 +129,10 @@
   // Client starts a second call.
   // We set wait_for_ready for this call, so that if it retries before
   // the server comes back up, it stays pending.
-  grpc_call* c2 =
-      grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/service/method"),
-                               nullptr, deadline, nullptr);
+  grpc_call* c2 = grpc_channel_create_call(
+      f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS, f->cq(),
+      grpc_slice_from_static_string("/service/method"), nullptr, deadline,
+      nullptr);
   GPR_ASSERT(c2);
   grpc_byte_buffer* request_payload2 =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
@@ -211,12 +166,13 @@
   op->data.recv_status_on_client.status = &status2;
   op->data.recv_status_on_client.status_details = &details2;
   op++;
-  error = grpc_call_start_batch(c2, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c2, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Start server shutdown.
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(102));
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(102));
 
   // Server handles the first call.
   grpc_byte_buffer* request_payload_recv = nullptr;
@@ -228,8 +184,8 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -248,16 +204,16 @@
   op->data.send_status_from_server.status = GRPC_STATUS_OK;
   op->data.send_status_from_server.status_details = &status_details;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server completes first call and shutdown.
   // Client completes first call.
-  cqv.Expect(tag(104), true);
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   // Clean up from first call.
@@ -278,18 +234,17 @@
   grpc_call_unref(c);
 
   // Destroy server and then restart it.
-  grpc_server_destroy(f.server);
-  f.server = nullptr;
-  config.init_server(&f, &server_args);
+  f->DestroyServer();
+  f->InitServer(server_args);
 
   // Server should get the second call.
   grpc_metadata_array_init(&request_metadata_recv);
   grpc_call_details_init(&call_details);
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(201));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(201));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(201), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(201), true);
   cqv.Verify();
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/service/method"));
   grpc_call_details_destroy(&call_details);
@@ -312,8 +267,8 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(202),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(202), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -332,14 +287,14 @@
   op->data.send_status_from_server.status = GRPC_STATUS_OK;
   op->data.send_status_from_server.status_details = &status_details;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(203),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(203), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Second call completes.
-  cqv.Expect(tag(203), true);
-  cqv.Expect(tag(202), true);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(203), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(202), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   // Clean up from second call.
@@ -358,12 +313,10 @@
   GPR_ASSERT(0 == grpc_slice_str_cmp(details2, "xyz"));
   grpc_slice_unref(details2);
   grpc_call_unref(c2);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_transparent_max_concurrent_streams(grpc_end2end_test_config config) {
+void retry_transparent_max_concurrent_streams(
+    const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_transparent_max_concurrent_streams(config);
 }
diff --git a/test/core/end2end/tests/retry_transparent_not_sent_on_wire.cc b/test/core/end2end/tests/retry_transparent_not_sent_on_wire.cc
index 0543df5..b7c1a1f 100644
--- a/test/core/end2end/tests/retry_transparent_not_sent_on_wire.cc
+++ b/test/core/end2end/tests/retry_transparent_not_sent_on_wire.cc
@@ -14,9 +14,10 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
 #include <new>
 
 #include "absl/status/status.h"
@@ -48,65 +49,20 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests transparent retries when the call was never sent out on the wire.
 static void test_retry_transparent_not_sent_on_wire(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -129,13 +85,14 @@
   int was_cancelled = 2;
   char* peer;
 
-  grpc_end2end_test_fixture f = begin_test(
-      config, "retry_transparent_not_sent_on_wire", nullptr, nullptr);
+  auto f = begin_test(config, "retry_transparent_not_sent_on_wire", nullptr,
+                      nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -162,8 +119,8 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Start a batch containing recv ops.
@@ -180,20 +137,20 @@
   op->data.recv_status_on_client.status = &status;
   op->data.recv_status_on_client.status_details = &details;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client send ops should now complete.
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   // Server should get a call.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Server receives the request.
@@ -202,10 +159,10 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   // Server sends a response with status OK.
@@ -225,15 +182,15 @@
   op->data.send_status_from_server.status = GRPC_STATUS_OK;
   op->data.send_status_from_server.status_details = &status_details;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // In principle, the server batch should complete before the client
   // recv ops batch, but in the proxy fixtures, there are multiple threads
   // involved, so the completion order tends to be a little racy.
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -264,9 +221,6 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
 namespace {
@@ -361,7 +315,7 @@
 
 }  // namespace
 
-void retry_transparent_not_sent_on_wire(grpc_end2end_test_config config) {
+void retry_transparent_not_sent_on_wire(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   grpc_core::CoreConfiguration::RunWithSpecialConfiguration(
       [](grpc_core::CoreConfiguration::Builder* builder) {
diff --git a/test/core/end2end/tests/retry_unref_before_finish.cc b/test/core/end2end/tests/retry_unref_before_finish.cc
index a0ee16d..7216ff1 100644
--- a/test/core/end2end/tests/retry_unref_before_finish.cc
+++ b/test/core/end2end/tests/retry_unref_before_finish.cc
@@ -14,9 +14,11 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -31,65 +33,21 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we can unref a call whose status is cached but not yet
 // requested by the application.  This should not cause a memory leak.
-static void test_retry_unref_before_finish(grpc_end2end_test_config config) {
+static void test_retry_unref_before_finish(
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -128,13 +86,14 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "retry_unref_before_finish", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -155,8 +114,8 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client starts recv_initial_metadata and recv_message, but not
@@ -169,17 +128,17 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &response_payload_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server gets a call and client send ops complete.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(1), true);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Server immediately sends FAILED_PRECONDITION status (not retriable).
@@ -198,13 +157,13 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server ops complete and client recv ops complete.
-  cqv.Expect(tag(2), true);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/service/method"));
@@ -227,12 +186,9 @@
   grpc_byte_buffer_destroy(response_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
   grpc_byte_buffer_destroy(response_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_unref_before_finish(grpc_end2end_test_config config) {
+void retry_unref_before_finish(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_unref_before_finish(config);
 }
diff --git a/test/core/end2end/tests/retry_unref_before_recv.cc b/test/core/end2end/tests/retry_unref_before_recv.cc
index cf06678..7daacec 100644
--- a/test/core/end2end/tests/retry_unref_before_recv.cc
+++ b/test/core/end2end/tests/retry_unref_before_recv.cc
@@ -14,9 +14,11 @@
 // limitations under the License.
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -31,66 +33,21 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Tests that we can unref a call while recv ops are started but before
 // they complete.  This ensures that we don't drop callbacks or cause a
 // memory leak.
-static void test_retry_unref_before_recv(grpc_end2end_test_config config) {
+static void test_retry_unref_before_recv(const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_op ops[6];
@@ -129,13 +86,13 @@
               "}")),
   };
   grpc_channel_args client_args = {GPR_ARRAY_SIZE(args), args};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "retry_unref_before_recv", &client_args, nullptr);
+  auto f = begin_test(config, "retry_unref_before_recv", &client_args, nullptr);
 
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(),
                                grpc_slice_from_static_string("/service/method"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
@@ -156,8 +113,8 @@
   op++;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client starts recv_initial_metadata and recv_message, but not
@@ -170,17 +127,17 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &response_payload_recv;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server gets a call and client send ops complete.
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(1), true);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   // Client unrefs the call without starting recv_trailing_metadata.
@@ -203,13 +160,13 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Server ops complete and client recv ops complete.
-  cqv.Expect(tag(2), false);  // Failure!
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), false);  // Failure!
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/service/method"));
@@ -230,12 +187,9 @@
   grpc_byte_buffer_destroy(response_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
   grpc_byte_buffer_destroy(response_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void retry_unref_before_recv(grpc_end2end_test_config config) {
+void retry_unref_before_recv(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_CLIENT_CHANNEL);
   test_retry_unref_before_recv(config);
 }
diff --git a/test/core/end2end/tests/server_finishes_request.cc b/test/core/end2end/tests/server_finishes_request.cc
index 8fa3ae1..d12a732 100644
--- a/test/core/end2end/tests/server_finishes_request.cc
+++ b/test/core/end2end/tests/server_finishes_request.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -26,71 +28,27 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_request_body(grpc_end2end_test_config /*config*/,
-                                grpc_end2end_test_fixture f) {
+static void simple_request_body(const CoreTestConfiguration& /*config*/,
+                                CoreTestFixture* f) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -102,10 +60,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -132,15 +90,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -163,12 +121,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -186,16 +144,12 @@
   grpc_call_unref(s);
 }
 
-static void test_invoke_simple_request(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
-
-  f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
-  simple_request_body(config, f);
-  end_test(&f);
-  config.tear_down_data(&f);
+static void test_invoke_simple_request(const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
+  simple_request_body(config, f.get());
 }
 
-void server_finishes_request(grpc_end2end_test_config config) {
+void server_finishes_request(const CoreTestConfiguration& config) {
   test_invoke_simple_request(config);
 }
 
diff --git a/test/core/end2end/tests/server_streaming.cc b/test/core/end2end/tests/server_streaming.cc
index 91f8997..80e1e8a 100644
--- a/test/core/end2end/tests/server_streaming.cc
+++ b/test/core/end2end/tests/server_streaming.cc
@@ -16,9 +16,9 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
 #include <memory>
 #include <string>
 
@@ -30,78 +30,34 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args,
-                                            int num_messages) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args,
+    int num_messages) {
   gpr_log(GPR_INFO, "%s\nRunning test: %s/%s/%d", std::string(100, '*').c_str(),
           test_name, config.name, num_messages);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Client requests status along with the initial metadata. Server streams
 // messages and ends with a non-OK status. Client reads after server is done
 // writing, and expects to get the status after the messages.
-static void test_server_streaming(grpc_end2end_test_config config,
+static void test_server_streaming(const CoreTestConfiguration& config,
                                   int num_messages) {
-  grpc_end2end_test_fixture f = begin_test(config, "test_server_streaming",
-                                           nullptr, nullptr, num_messages);
+  auto f = begin_test(config, "test_server_streaming", nullptr, nullptr,
+                      num_messages);
   grpc_call* c;
   grpc_call* s;
-  auto cqv = std::make_unique<grpc_core::CqVerifier>(f.cq);
+  auto cqv = std::make_unique<grpc_core::CqVerifier>(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -117,10 +73,10 @@
   grpc_slice response_payload_slice =
       grpc_slice_from_copied_string("hello world");
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -149,8 +105,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Client sends close early
@@ -160,17 +116,17 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv->Expect(tag(3), true);
+  cqv->Expect(grpc_core::CqVerifier::tag(3), true);
   cqv->Verify();
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(100));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(100));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv->Expect(tag(100), true);
+  cqv->Expect(grpc_core::CqVerifier::tag(100), true);
   cqv->Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -180,11 +136,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(101),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(101), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv->Expect(tag(101), true);
+  cqv->Expect(grpc_core::CqVerifier::tag(101), true);
   cqv->Verify();
 
   // Server writes bunch of messages
@@ -199,9 +155,9 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
-                                  tag(103), nullptr);
+                                  grpc_core::CqVerifier::tag(103), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv->Expect(tag(103), true);
+    cqv->Expect(grpc_core::CqVerifier::tag(103), true);
     cqv->Verify();
 
     grpc_byte_buffer_destroy(response_payload);
@@ -223,12 +179,13 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
   bool seen_status = false;
-  cqv->Expect(tag(1), grpc_core::CqVerifier::Maybe{&seen_status});
-  cqv->Expect(tag(104), true);
+  cqv->Expect(grpc_core::CqVerifier::tag(1),
+              grpc_core::CqVerifier::Maybe{&seen_status});
+  cqv->Expect(grpc_core::CqVerifier::tag(104), true);
   cqv->Verify();
 
   // Client keeps reading messages till it gets the status
@@ -242,10 +199,11 @@
     op->reserved = nullptr;
     op++;
     error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
-                                  tag(102), nullptr);
+                                  grpc_core::CqVerifier::tag(102), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
-    cqv->Expect(tag(1), grpc_core::CqVerifier::Maybe{&seen_status});
-    cqv->Expect(tag(102), true);
+    cqv->Expect(grpc_core::CqVerifier::tag(1),
+                grpc_core::CqVerifier::Maybe{&seen_status});
+    cqv->Expect(grpc_core::CqVerifier::tag(102), true);
     cqv->Verify();
     if (request_payload_recv == nullptr) {
       // The transport has received the trailing metadata.
@@ -257,7 +215,7 @@
   }
   GPR_ASSERT(num_messages_received == num_messages);
   if (!seen_status) {
-    cqv->Expect(tag(1), true);
+    cqv->Expect(grpc_core::CqVerifier::tag(1), true);
     cqv->Verify();
   }
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -274,12 +232,9 @@
   grpc_metadata_array_destroy(&request_metadata_recv);
   grpc_call_details_destroy(&call_details);
   grpc_slice_unref(details);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void server_streaming(grpc_end2end_test_config config) {
+void server_streaming(const CoreTestConfiguration& config) {
   test_server_streaming(config, 0);
   test_server_streaming(config, 1);
   test_server_streaming(config, 10);
diff --git a/test/core/end2end/tests/shutdown_finishes_calls.cc b/test/core/end2end/tests/shutdown_finishes_calls.cc
index 8e4e3df..4d3e80a 100644
--- a/test/core/end2end/tests/shutdown_finishes_calls.cc
+++ b/test/core/end2end/tests/shutdown_finishes_calls.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -26,61 +28,30 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 static void test_early_server_shutdown_finishes_inflight_calls(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_early_server_shutdown_finishes_inflight_calls",
                  nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -92,10 +63,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -127,15 +98,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -145,8 +116,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // Make sure we don't shutdown the server while HTTP/2 PING frames are still
@@ -154,18 +125,19 @@
   // failures when testing with HTTP proxy. See
   // https://github.com/grpc/grpc/issues/14471
   //
-  gpr_sleep_until(n_seconds_from_now(1));
+  gpr_sleep_until(grpc_timeout_seconds_to_deadline(1));
 
   // shutdown and destroy the server
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(1000));
-  grpc_server_cancel_all_calls(f.server);
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(1000));
+  grpc_server_cancel_all_calls(f->server());
 
-  cqv.Expect(tag(1000), true);
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1000), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
-  grpc_server_destroy(f.server);
+  f->DestroyServer();
 
   GPR_ASSERT(status == GRPC_STATUS_UNAVAILABLE);
   GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/foo"));
@@ -179,12 +151,9 @@
 
   grpc_call_unref(c);
   grpc_call_unref(s);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void shutdown_finishes_calls(grpc_end2end_test_config config) {
+void shutdown_finishes_calls(const CoreTestConfiguration& config) {
   test_early_server_shutdown_finishes_inflight_calls(config);
 }
 
diff --git a/test/core/end2end/tests/shutdown_finishes_tags.cc b/test/core/end2end/tests/shutdown_finishes_tags.cc
index 41ac513..f774759 100644
--- a/test/core/end2end/tests/shutdown_finishes_tags.cc
+++ b/test/core/end2end/tests/shutdown_finishes_tags.cc
@@ -16,62 +16,32 @@
 //
 //
 
-#include <stdint.h>
+#include <functional>
+#include <memory>
 
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
-#include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
-#include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 static void test_early_server_shutdown_finishes_tags(
-    grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f = begin_test(
-      config, "test_early_server_shutdown_finishes_tags", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+    const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "test_early_server_shutdown_finishes_tags",
+                      nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_call* s = reinterpret_cast<grpc_call*>(1);
   grpc_call_details call_details;
   grpc_metadata_array request_metadata_recv;
@@ -81,23 +51,20 @@
 
   // upon shutdown, the server should finish all requested calls indicating
   // no new call
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101)));
-  shutdown_client(&f);
-  grpc_server_shutdown_and_notify(f.server, f.cq, tag(1000));
-  cqv.Expect(tag(101), false);
-  cqv.Expect(tag(1000), true);
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(101)));
+  f->ShutdownClient();
+  grpc_server_shutdown_and_notify(f->server(), f->cq(),
+                                  grpc_core::CqVerifier::tag(1000));
+  cqv.Expect(grpc_core::CqVerifier::tag(101), false);
+  cqv.Expect(grpc_core::CqVerifier::tag(1000), true);
   cqv.Verify();
   GPR_ASSERT(s == nullptr);
-
-  grpc_server_destroy(f.server);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void shutdown_finishes_tags(grpc_end2end_test_config config) {
+void shutdown_finishes_tags(const CoreTestConfiguration& config) {
   test_early_server_shutdown_finishes_tags(config);
 }
 
diff --git a/test/core/end2end/tests/simple_delayed_request.cc b/test/core/end2end/tests/simple_delayed_request.cc
index f9a844c..a5e307c 100644
--- a/test/core/end2end/tests/simple_delayed_request.cc
+++ b/test/core/end2end/tests/simple_delayed_request.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
 #include <grpc/slice.h>
@@ -31,58 +33,10 @@
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
-static void simple_delayed_request_body(grpc_end2end_test_config config,
-                                        grpc_end2end_test_fixture* f,
-                                        const grpc_channel_args* client_args,
-                                        const grpc_channel_args* server_args,
-                                        long /*delay_us*/) {
+static void simple_delayed_request_body(CoreTestFixture* f) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f->cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -94,12 +48,12 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  config.init_client(f, client_args);
-  config.init_server(f, server_args);
+  f->InitClient(grpc_core::ChannelArgs());
+  f->InitServer(grpc_core::ChannelArgs());
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f->client, nullptr, GRPC_PROPAGATE_DEFAULTS,
-                               f->cq, grpc_slice_from_static_string("/foo"),
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
                                nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
@@ -131,15 +85,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f->server, &s, &call_details,
-                               &request_metadata_recv, f->cq, f->cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -162,12 +116,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -185,8 +139,8 @@
   grpc_call_unref(s);
 }
 
-static void test_simple_delayed_request_short(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
+static void test_simple_delayed_request_short(
+    const CoreTestConfiguration& config) {
   auto client_args = grpc_core::ChannelArgs()
                          .Set(GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS, 1000)
                          .Set(GRPC_ARG_MAX_RECONNECT_BACKOFF_MS, 1000)
@@ -194,15 +148,14 @@
                          .ToC();
   gpr_log(GPR_INFO, "Running test: %s/%s", "test_simple_delayed_request_short",
           config.name);
-  f = config.create_fixture(nullptr, nullptr);
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
 
-  simple_delayed_request_body(config, &f, client_args.get(), nullptr, 100000);
-  end_test(&f);
-  config.tear_down_data(&f);
+  simple_delayed_request_body(f.get());
 }
 
-static void test_simple_delayed_request_long(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
+static void test_simple_delayed_request_long(
+    const CoreTestConfiguration& config) {
   auto client_args = grpc_core::ChannelArgs()
                          .Set(GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS, 1000)
                          .Set(GRPC_ARG_MAX_RECONNECT_BACKOFF_MS, 1000)
@@ -211,14 +164,13 @@
 
   gpr_log(GPR_INFO, "Running test: %s/%s", "test_simple_delayed_request_long",
           config.name);
-  f = config.create_fixture(nullptr, nullptr);
+  auto f =
+      config.create_fixture(grpc_core::ChannelArgs(), grpc_core::ChannelArgs());
   // This timeout should be longer than a single retry
-  simple_delayed_request_body(config, &f, client_args.get(), nullptr, 1500000);
-  end_test(&f);
-  config.tear_down_data(&f);
+  simple_delayed_request_body(f.get());
 }
 
-void simple_delayed_request(grpc_end2end_test_config config) {
+void simple_delayed_request(const CoreTestConfiguration& config) {
   GPR_ASSERT(config.feature_mask & FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION);
   test_simple_delayed_request_short(config);
   test_simple_delayed_request_long(config);
diff --git a/test/core/end2end/tests/simple_metadata.cc b/test/core/end2end/tests/simple_metadata.cc
index 0b6e430..79c3da0 100644
--- a/test/core/end2end/tests/simple_metadata.cc
+++ b/test/core/end2end/tests/simple_metadata.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -27,69 +29,25 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Request/response with metadata and payload.
 static void test_request_response_with_metadata_and_payload(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice =
@@ -112,10 +70,9 @@
                              {grpc_slice_from_static_string("key4"),
                               grpc_slice_from_static_string("val4"),
                               {{nullptr, nullptr, nullptr, nullptr}}}};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_request_response_with_metadata_and_payload",
-                 nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "test_request_response_with_metadata_and_payload",
+                      nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -129,10 +86,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -174,15 +131,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -198,11 +155,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -225,12 +182,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -257,12 +214,9 @@
   grpc_byte_buffer_destroy(response_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
   grpc_byte_buffer_destroy(response_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void simple_metadata(grpc_end2end_test_config config) {
+void simple_metadata(const CoreTestConfiguration& config) {
   test_request_response_with_metadata_and_payload(config);
 }
 
diff --git a/test/core/end2end/tests/simple_request.cc b/test/core/end2end/tests/simple_request.cc
index d8eeb2c..2a236ce 100644
--- a/test/core/end2end/tests/simple_request.cc
+++ b/test/core/end2end/tests/simple_request.cc
@@ -20,6 +20,7 @@
 #include <string.h>
 
 #include <algorithm>
+#include <functional>
 #include <memory>
 #include <string>
 
@@ -31,69 +32,25 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/debug/stats_data.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "%s", std::string(100, '*').c_str());
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 static void check_peer(char* peer_name) {
   // If the peer name is a uds path, then check if it is filled
   if (strncmp(peer_name, "unix:/", strlen("unix:/")) == 0) {
@@ -102,11 +59,11 @@
   }
 }
 
-static void simple_request_body(grpc_end2end_test_config config,
-                                grpc_end2end_test_fixture f) {
+static void simple_request_body(const CoreTestConfiguration& config,
+                                CoreTestFixture* f) {
   grpc_call* c;
   grpc_call* s;
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -122,10 +79,10 @@
 
   auto before = grpc_core::global_stats().Collect();
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   peer = grpc_call_get_peer(c);
@@ -162,15 +119,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(s);
@@ -204,12 +161,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_UNIMPLEMENTED);
@@ -250,28 +207,23 @@
              expected_calls);
 }
 
-static void test_invoke_simple_request(grpc_end2end_test_config config) {
-  grpc_end2end_test_fixture f;
-
-  f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
-  simple_request_body(config, f);
-  end_test(&f);
-  config.tear_down_data(&f);
+static void test_invoke_simple_request(const CoreTestConfiguration& config) {
+  auto f = begin_test(config, "test_invoke_simple_request", nullptr, nullptr);
+  simple_request_body(config, f.get());
 }
 
-static void test_invoke_10_simple_requests(grpc_end2end_test_config config) {
+static void test_invoke_10_simple_requests(
+    const CoreTestConfiguration& config) {
   int i;
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_invoke_10_simple_requests", nullptr, nullptr);
   for (i = 0; i < 10; i++) {
-    simple_request_body(config, f);
+    simple_request_body(config, f.get());
     gpr_log(GPR_INFO, "Running test: Passed simple request %d", i);
   }
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void simple_request(grpc_end2end_test_config config) {
+void simple_request(const CoreTestConfiguration& config) {
   int i;
   for (i = 0; i < 10; i++) {
     test_invoke_simple_request(config);
diff --git a/test/core/end2end/tests/streaming_error_response.cc b/test/core/end2end/tests/streaming_error_response.cc
index 6415490..82f9a38 100644
--- a/test/core/end2end/tests/streaming_error_response.cc
+++ b/test/core/end2end/tests/streaming_error_response.cc
@@ -19,9 +19,11 @@
 /// \file Verify that status ordering rules are obeyed.
 /// \ref doc/status_ordering.md
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -30,73 +32,29 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args,
-                                            bool request_status_early) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args,
+    bool request_status_early) {
   gpr_log(GPR_INFO, "Running test: %s/%s/request_status_early=%s", test_name,
           config.name, request_status_early ? "true" : "false");
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_client(f);
-  shutdown_server(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Client sends a request with payload, potentially requesting status early. The
 // server reads and streams responses. The client cancels the RPC to get an
 // error status. (Server sending a non-OK status is not considered an error
 // status.)
-static void test(grpc_end2end_test_config config, bool request_status_early,
+static void test(const CoreTestConfiguration& config, bool request_status_early,
                  bool recv_message_separately) {
   grpc_call* c;
   grpc_call* s;
@@ -106,10 +64,9 @@
   grpc_slice response_payload2_slice = grpc_slice_from_copied_string("world");
   grpc_byte_buffer* response_payload2 =
       grpc_raw_byte_buffer_create(&response_payload2_slice, 1);
-  grpc_end2end_test_fixture f =
-      begin_test(config, "streaming_error_response", nullptr, nullptr,
-                 request_status_early);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "streaming_error_response", nullptr, nullptr,
+                      request_status_early);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -123,11 +80,11 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
   GPR_ASSERT(!recv_message_separately || request_status_early);
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -157,14 +114,15 @@
     op->data.recv_status_on_client.status_details = &details;
     op++;
   }
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101)));
-  cqv.Expect(tag(101), true);
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(101)));
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -175,8 +133,8 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = response_payload1;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   if (recv_message_separately) {
@@ -185,17 +143,17 @@
     op->op = GRPC_OP_RECV_MESSAGE;
     op->data.recv_message.recv_message = &response_payload1_recv;
     op++;
-    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                  nullptr);
+    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                  grpc_core::CqVerifier::tag(4), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
   }
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   if (!request_status_early) {
-    cqv.Expect(tag(1), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   }
   if (recv_message_separately) {
-    cqv.Expect(tag(4), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   }
   cqv.Verify();
 
@@ -204,15 +162,16 @@
   op->op = GRPC_OP_SEND_MESSAGE;
   op->data.send_message.send_message = response_payload2;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
   // The success of the op depends on whether the payload is written before the
   // transport sees the end of stream. If the stream has been write closed
   // before the write completes, it would fail, otherwise it would succeed.
   // Since this behavior is dependent on the transport implementation, we allow
   // any success status with this op.
-  cqv.Expect(tag(103), grpc_core::CqVerifier::AnyStatus());
+  cqv.Expect(grpc_core::CqVerifier::tag(103),
+             grpc_core::CqVerifier::AnyStatus());
 
   if (!request_status_early) {
     memset(ops, 0, sizeof(ops));
@@ -220,11 +179,11 @@
     op->op = GRPC_OP_RECV_MESSAGE;
     op->data.recv_message.recv_message = &response_payload2_recv;
     op++;
-    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                  nullptr);
+    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                  grpc_core::CqVerifier::tag(2), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
-    cqv.Expect(tag(2), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(2), true);
     cqv.Verify();
 
     GPR_ASSERT(response_payload2_recv != nullptr);
@@ -237,13 +196,13 @@
   op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
   op->data.recv_close_on_server.cancelled = &was_cancelled;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   if (request_status_early) {
-    cqv.Expect(tag(1), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   }
   cqv.Verify();
 
@@ -255,11 +214,11 @@
     op->data.recv_status_on_client.status = &status;
     op->data.recv_status_on_client.status_details = &details;
     op++;
-    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                  nullptr);
+    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                  grpc_core::CqVerifier::tag(3), nullptr);
     GPR_ASSERT(GRPC_CALL_OK == error);
 
-    cqv.Expect(tag(3), true);
+    cqv.Expect(grpc_core::CqVerifier::tag(3), true);
     cqv.Verify();
 
     GPR_ASSERT(response_payload1_recv != nullptr);
@@ -282,12 +241,9 @@
   grpc_byte_buffer_destroy(response_payload2);
   grpc_byte_buffer_destroy(response_payload1_recv);
   grpc_byte_buffer_destroy(response_payload2_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void streaming_error_response(grpc_end2end_test_config config) {
+void streaming_error_response(const CoreTestConfiguration& config) {
   test(config, false, false);
   test(config, true, false);
   test(config, true, true);
diff --git a/test/core/end2end/tests/trailing_metadata.cc b/test/core/end2end/tests/trailing_metadata.cc
index 5dab653..55a1e06 100644
--- a/test/core/end2end/tests/trailing_metadata.cc
+++ b/test/core/end2end/tests/trailing_metadata.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -27,69 +29,25 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Request/response with metadata and payload.
 static void test_request_response_with_metadata_and_payload(
-    grpc_end2end_test_config config) {
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice =
@@ -118,10 +76,9 @@
                              {grpc_slice_from_static_string("key6"),
                               grpc_slice_from_static_string("val6"),
                               {{nullptr, nullptr, nullptr, nullptr}}}};
-  grpc_end2end_test_fixture f =
-      begin_test(config, "test_request_response_with_metadata_and_payload",
-                 nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  auto f = begin_test(config, "test_request_response_with_metadata_and_payload",
+                      nullptr, nullptr);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -135,10 +92,10 @@
   grpc_slice details;
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -180,15 +137,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  error =
-      grpc_server_request_call(f.server, &s, &call_details,
-                               &request_metadata_recv, f.cq, f.cq, tag(101));
+  error = grpc_server_request_call(f->server(), &s, &call_details,
+                                   &request_metadata_recv, f->cq(), f->cq(),
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -204,11 +161,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -232,12 +189,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -265,12 +222,9 @@
   grpc_byte_buffer_destroy(response_payload);
   grpc_byte_buffer_destroy(request_payload_recv);
   grpc_byte_buffer_destroy(response_payload_recv);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void trailing_metadata(grpc_end2end_test_config config) {
+void trailing_metadata(const CoreTestConfiguration& config) {
   test_request_response_with_metadata_and_payload(config);
 }
 
diff --git a/test/core/end2end/tests/write_buffering.cc b/test/core/end2end/tests/write_buffering.cc
index cb7b174..9dde20b 100644
--- a/test/core/end2end/tests/write_buffering.cc
+++ b/test/core/end2end/tests/write_buffering.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -27,68 +29,25 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Client sends a request with payload, server reads then returns status.
-static void test_invoke_request_with_payload(grpc_end2end_test_config config) {
+static void test_invoke_request_with_payload(
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice1 =
@@ -98,9 +57,9 @@
   grpc_slice request_payload_slice2 = grpc_slice_from_copied_string("abc123");
   grpc_byte_buffer* request_payload2 =
       grpc_raw_byte_buffer_create(&request_payload_slice2, 1);
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_invoke_request_with_payload", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -114,10 +73,10 @@
   grpc_slice details = grpc_empty_slice();
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -130,8 +89,8 @@
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
   op->data.send_initial_metadata.count = 0;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -141,15 +100,16 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101)));
-  cqv.Expect(tag(1), true);  // send message is buffered
-  cqv.Expect(tag(101), true);
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(101)));
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);  // send message is buffered
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -158,8 +118,8 @@
   op->data.send_message.send_message = request_payload1;
   op->flags = GRPC_WRITE_BUFFER_HINT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -167,8 +127,8 @@
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
   op->data.send_initial_metadata.count = 0;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // recv message should not succeed yet - it's buffered at the client still
@@ -177,13 +137,13 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv1;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(2), true);
-  cqv.Expect(tag(3), true);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   // send another message, this time not buffered
@@ -193,13 +153,13 @@
   op->data.send_message.send_message = request_payload2;
   op->flags = 0;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(4), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // now the first send should match up with the first recv
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(4), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   cqv.Verify();
 
   // and the next recv should be ready immediately also
@@ -208,11 +168,11 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv2;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -228,8 +188,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(4), nullptr);
 
   memset(ops, 0, sizeof(ops));
   op = ops;
@@ -246,12 +206,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(105),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(105), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(105), true);
-  cqv.Expect(tag(4), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(105), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -274,12 +234,9 @@
   grpc_byte_buffer_destroy(request_payload_recv1);
   grpc_byte_buffer_destroy(request_payload2);
   grpc_byte_buffer_destroy(request_payload_recv2);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void write_buffering(grpc_end2end_test_config config) {
+void write_buffering(const CoreTestConfiguration& config) {
   test_invoke_request_with_payload(config);
 }
 
diff --git a/test/core/end2end/tests/write_buffering_at_end.cc b/test/core/end2end/tests/write_buffering_at_end.cc
index 7003cd6..db4a143 100644
--- a/test/core/end2end/tests/write_buffering_at_end.cc
+++ b/test/core/end2end/tests/write_buffering_at_end.cc
@@ -16,9 +16,11 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
+#include <functional>
+#include <memory>
+
 #include <grpc/byte_buffer.h>
 #include <grpc/grpc.h>
 #include <grpc/impl/propagation_bits.h>
@@ -27,77 +29,34 @@
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
 
+#include "src/core/lib/channel/channel_args.h"
 #include "test/core/end2end/cq_verifier.h"
 #include "test/core/end2end/end2end_tests.h"
 #include "test/core/util/test_config.h"
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
-static grpc_end2end_test_fixture begin_test(grpc_end2end_test_config config,
-                                            const char* test_name,
-                                            grpc_channel_args* client_args,
-                                            grpc_channel_args* server_args) {
-  grpc_end2end_test_fixture f;
+static std::unique_ptr<CoreTestFixture> begin_test(
+    const CoreTestConfiguration& config, const char* test_name,
+    grpc_channel_args* client_args, grpc_channel_args* server_args) {
   gpr_log(GPR_INFO, "Running test: %s/%s", test_name, config.name);
-  f = config.create_fixture(client_args, server_args);
-  config.init_server(&f, server_args);
-  config.init_client(&f, client_args);
+  auto f = config.create_fixture(grpc_core::ChannelArgs::FromC(client_args),
+                                 grpc_core::ChannelArgs::FromC(server_args));
+  f->InitServer(grpc_core::ChannelArgs::FromC(server_args));
+  f->InitClient(grpc_core::ChannelArgs::FromC(client_args));
   return f;
 }
 
-static gpr_timespec n_seconds_from_now(int n) {
-  return grpc_timeout_seconds_to_deadline(n);
-}
-
-static gpr_timespec five_seconds_from_now(void) {
-  return n_seconds_from_now(5);
-}
-
-static void drain_cq(grpc_completion_queue* cq) {
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(cq, five_seconds_from_now(), nullptr);
-  } while (ev.type != GRPC_QUEUE_SHUTDOWN);
-}
-
-static void shutdown_server(grpc_end2end_test_fixture* f) {
-  if (!f->server) return;
-  grpc_server_shutdown_and_notify(f->server, f->cq, tag(1000));
-  grpc_event ev;
-  do {
-    ev = grpc_completion_queue_next(f->cq, grpc_timeout_seconds_to_deadline(5),
-                                    nullptr);
-  } while (ev.type != GRPC_OP_COMPLETE || ev.tag != tag(1000));
-  grpc_server_destroy(f->server);
-  f->server = nullptr;
-}
-
-static void shutdown_client(grpc_end2end_test_fixture* f) {
-  if (!f->client) return;
-  grpc_channel_destroy(f->client);
-  f->client = nullptr;
-}
-
-static void end_test(grpc_end2end_test_fixture* f) {
-  shutdown_server(f);
-  shutdown_client(f);
-
-  grpc_completion_queue_shutdown(f->cq);
-  drain_cq(f->cq);
-  grpc_completion_queue_destroy(f->cq);
-}
-
 // Client sends a request with payload, server reads then returns status.
-static void test_invoke_request_with_payload(grpc_end2end_test_config config) {
+static void test_invoke_request_with_payload(
+    const CoreTestConfiguration& config) {
   grpc_call* c;
   grpc_call* s;
   grpc_slice request_payload_slice =
       grpc_slice_from_copied_string("hello world");
   grpc_byte_buffer* request_payload =
       grpc_raw_byte_buffer_create(&request_payload_slice, 1);
-  grpc_end2end_test_fixture f =
+  auto f =
       begin_test(config, "test_invoke_request_with_payload", nullptr, nullptr);
-  grpc_core::CqVerifier cqv(f.cq);
+  grpc_core::CqVerifier cqv(f->cq());
   grpc_op ops[6];
   grpc_op* op;
   grpc_metadata_array initial_metadata_recv;
@@ -111,10 +70,10 @@
   grpc_slice details = grpc_empty_slice();
   int was_cancelled = 2;
 
-  gpr_timespec deadline = five_seconds_from_now();
-  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
-                               grpc_slice_from_static_string("/foo"), nullptr,
-                               deadline, nullptr);
+  gpr_timespec deadline = grpc_timeout_seconds_to_deadline(5);
+  c = grpc_channel_create_call(f->client(), nullptr, GRPC_PROPAGATE_DEFAULTS,
+                               f->cq(), grpc_slice_from_static_string("/foo"),
+                               nullptr, deadline, nullptr);
   GPR_ASSERT(c);
 
   grpc_metadata_array_init(&initial_metadata_recv);
@@ -127,8 +86,8 @@
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
   op->data.send_initial_metadata.count = 0;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -138,15 +97,16 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(2),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(2), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  GPR_ASSERT(GRPC_CALL_OK == grpc_server_request_call(
-                                 f.server, &s, &call_details,
-                                 &request_metadata_recv, f.cq, f.cq, tag(101)));
-  cqv.Expect(tag(1), true);  // send message is buffered
-  cqv.Expect(tag(101), true);
+  GPR_ASSERT(GRPC_CALL_OK ==
+             grpc_server_request_call(f->server(), &s, &call_details,
+                                      &request_metadata_recv, f->cq(), f->cq(),
+                                      grpc_core::CqVerifier::tag(101)));
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);  // send message is buffered
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -155,8 +115,8 @@
   op->data.send_message.send_message = request_payload;
   op->flags = GRPC_WRITE_BUFFER_HINT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(3),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(3), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   memset(ops, 0, sizeof(ops));
@@ -164,8 +124,8 @@
   op->op = GRPC_OP_SEND_INITIAL_METADATA;
   op->data.send_initial_metadata.count = 0;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // recv message should not succeed yet - it's buffered at the client still
@@ -174,13 +134,13 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv1;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(2), true);
-  cqv.Expect(tag(3), true);
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   // send end of stream: should release the buffering
@@ -188,13 +148,13 @@
   op = ops;
   op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(4), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   // now the first send should match up with the first recv
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(4), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   cqv.Verify();
 
   // and the next recv should be ready immediately also (and empty)
@@ -203,11 +163,11 @@
   op->op = GRPC_OP_RECV_MESSAGE;
   op->data.recv_message.recv_message = &request_payload_recv2;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(104),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(104), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(104), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(104), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -219,8 +179,8 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(4),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(4), nullptr);
 
   memset(ops, 0, sizeof(ops));
   op = ops;
@@ -237,12 +197,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(105),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(105), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(105), true);
-  cqv.Expect(tag(4), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(105), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -263,12 +223,9 @@
 
   grpc_byte_buffer_destroy(request_payload);
   grpc_byte_buffer_destroy(request_payload_recv1);
-
-  end_test(&f);
-  config.tear_down_data(&f);
 }
 
-void write_buffering_at_end(grpc_end2end_test_config config) {
+void write_buffering_at_end(const CoreTestConfiguration& config) {
   test_invoke_request_with_payload(config);
 }
 
diff --git a/test/core/surface/lame_client_test.cc b/test/core/surface/lame_client_test.cc
index 20f10f9..c046c7d 100644
--- a/test/core/surface/lame_client_test.cc
+++ b/test/core/surface/lame_client_test.cc
@@ -16,7 +16,6 @@
 //
 //
 
-#include <stdint.h>
 #include <string.h>
 
 #include "absl/status/status.h"
@@ -49,8 +48,6 @@
   }
 };
 
-static void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 static grpc_closure transport_op_cb;
 
 static void do_nothing(void* /*arg*/, grpc_error_handle /*error*/) {}
@@ -121,14 +118,15 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(call, ops, static_cast<size_t>(op - ops),
-                                tag(1), nullptr);
+                                grpc_core::CqVerifier::tag(1), nullptr);
   ASSERT_EQ(GRPC_CALL_OK, error);
 
   // Filter stack code considers this a failed to receive initial metadata
   // result, where as promise based code interprets this as a trailers only
   // failed request. Both are rational interpretations, so we accept the one
   // that is implemented for each stack.
-  cqv.Expect(tag(1), grpc_core::IsPromiseBasedClientCallEnabled());
+  cqv.Expect(grpc_core::CqVerifier::tag(1),
+             grpc_core::IsPromiseBasedClientCallEnabled());
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -141,11 +139,11 @@
   op->reserved = nullptr;
   op++;
   error = grpc_call_start_batch(call, ops, static_cast<size_t>(op - ops),
-                                tag(2), nullptr);
+                                grpc_core::CqVerifier::tag(2), nullptr);
   ASSERT_EQ(GRPC_CALL_OK, error);
 
   // the call should immediately fail
-  cqv.Expect(tag(2), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify();
 
   peer = grpc_call_get_peer(call);
diff --git a/test/core/transport/chttp2/BUILD b/test/core/transport/chttp2/BUILD
index fb5d49a..88804c9 100644
--- a/test/core/transport/chttp2/BUILD
+++ b/test/core/transport/chttp2/BUILD
@@ -33,6 +33,18 @@
 )
 
 grpc_proto_fuzzer(
+    name = "hpack_sync_fuzzer",
+    srcs = ["hpack_sync_fuzzer.cc"],
+    corpus = "hpack_sync_corpus",
+    proto = "hpack_sync_fuzzer.proto",
+    tags = ["no_windows"],
+    deps = [
+        "//:grpc",
+        "//test/core/util:grpc_test_util",
+    ],
+)
+
+grpc_proto_fuzzer(
     name = "flow_control_fuzzer",
     srcs = ["flow_control_fuzzer.cc"],
     corpus = "flow_control_fuzzer_corpus",
@@ -48,6 +60,23 @@
 )
 
 grpc_fuzzer(
+    name = "hpack_parser_input_size_fuzzer",
+    srcs = ["hpack_parser_input_size_fuzzer.cc"],
+    corpus = "hpack_parser_input_size_corpus",
+    external_deps = [
+        "absl/cleanup",
+        "absl/status:statusor",
+        "absl/status",
+    ],
+    tags = ["no_windows"],
+    deps = [
+        "//:grpc",
+        "//test/core/util:grpc_test_util",
+        "//test/core/util:grpc_test_util_base",
+    ],
+)
+
+grpc_fuzzer(
     name = "decode_huff_fuzzer",
     srcs = ["decode_huff_fuzzer.cc"],
     corpus = "decode_huff_corpus",
diff --git a/test/core/transport/chttp2/MiXeD-CaSe.headers b/test/core/transport/chttp2/MiXeD-CaSe.headers
new file mode 100644
index 0000000..8fb8525
--- /dev/null
+++ b/test/core/transport/chttp2/MiXeD-CaSe.headers
@@ -0,0 +1 @@
+MiXeD-CaSe: should not parse
diff --git a/test/core/transport/chttp2/bad-base64.headers b/test/core/transport/chttp2/bad-base64.headers
new file mode 100644
index 0000000..bff6de9
--- /dev/null
+++ b/test/core/transport/chttp2/bad-base64.headers
@@ -0,0 +1 @@
+a.b.c-bin: luckily for us, it's tuesday
diff --git a/test/core/transport/chttp2/bad-te.headers b/test/core/transport/chttp2/bad-te.headers
new file mode 100644
index 0000000..a66f0aa
--- /dev/null
+++ b/test/core/transport/chttp2/bad-te.headers
@@ -0,0 +1 @@
+te: garbage
diff --git a/test/core/transport/chttp2/bin_encoder_test.cc b/test/core/transport/chttp2/bin_encoder_test.cc
index 83fe34a..22643e3 100644
--- a/test/core/transport/chttp2/bin_encoder_test.cc
+++ b/test/core/transport/chttp2/bin_encoder_test.cc
@@ -70,7 +70,9 @@
   grpc_slice input = grpc_slice_from_copied_buffer(s, len);
   grpc_slice base64 = grpc_chttp2_base64_encode(input);
   grpc_slice expect = grpc_chttp2_huffman_compress(base64);
-  grpc_slice got = grpc_chttp2_base64_encode_and_huffman_compress(input);
+  uint32_t wire_size;
+  grpc_slice got =
+      grpc_chttp2_base64_encode_and_huffman_compress(input, &wire_size);
   if (!grpc_slice_eq(expect, got)) {
     char* t = grpc_dump_slice(input, GPR_DUMP_HEX | GPR_DUMP_ASCII);
     char* e = grpc_dump_slice(expect, GPR_DUMP_HEX | GPR_DUMP_ASCII);
diff --git a/test/core/transport/chttp2/hpack_encoder_test.cc b/test/core/transport/chttp2/hpack_encoder_test.cc
index 671ca25..9153236 100644
--- a/test/core/transport/chttp2/hpack_encoder_test.cc
+++ b/test/core/transport/chttp2/hpack_encoder_test.cc
@@ -193,7 +193,7 @@
     bool is_eof, const char* expected,
     const std::vector<std::pair<std::string, std::string>>& header_fields) {
   const grpc_core::Slice merged(EncodeHeaderIntoBytes(is_eof, header_fields));
-  const grpc_core::Slice expect(parse_hexstring(expected));
+  const grpc_core::Slice expect(grpc_core::ParseHexstring(expected));
 
   EXPECT_EQ(merged, expect);
 }
@@ -343,6 +343,66 @@
   delete g_compressor;
 }
 
+TEST(HpackEncoderTest, EncodeBinaryAsBase64) {
+  grpc_core::MemoryAllocator memory_allocator =
+      grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
+                                     ->memory_quota()
+                                     ->CreateMemoryAllocator("test"));
+  auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
+  grpc_metadata_batch b(arena.get());
+  // Haiku by Bard
+  b.Append("grpc-trace-bin",
+           grpc_core::Slice::FromStaticString(
+               "Base64, a tool\nTo encode binary data into "
+               "text\nSo it can be shared."),
+           CrashOnAppendError);
+  grpc_transport_one_way_stats stats;
+  stats = {};
+  grpc_slice_buffer output;
+  grpc_slice_buffer_init(&output);
+  grpc_core::HPackCompressor::EncodeHeaderOptions hopt = {
+      0xdeadbeef,  // stream_id
+      true,        // is_eof
+      false,       // use_true_binary_metadata
+      150,         // max_frame_size
+      &stats};
+  grpc_core::HPackCompressor compressor;
+  compressor.EncodeHeaders(hopt, b, &output);
+  grpc_slice_buffer_destroy(&output);
+
+  EXPECT_EQ(compressor.test_only_table_size(), 136);
+}
+
+TEST(HpackEncoderTest, EncodeBinaryAsTrueBinary) {
+  grpc_core::MemoryAllocator memory_allocator =
+      grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
+                                     ->memory_quota()
+                                     ->CreateMemoryAllocator("test"));
+  auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
+  grpc_metadata_batch b(arena.get());
+  // Haiku by Bard
+  b.Append("grpc-trace-bin",
+           grpc_core::Slice::FromStaticString(
+               "Base64, a tool\nTo encode binary data into "
+               "text\nSo it can be shared."),
+           CrashOnAppendError);
+  grpc_transport_one_way_stats stats;
+  stats = {};
+  grpc_slice_buffer output;
+  grpc_slice_buffer_init(&output);
+  grpc_core::HPackCompressor::EncodeHeaderOptions hopt = {
+      0xdeadbeef,  // stream_id
+      true,        // is_eof
+      true,        // use_true_binary_metadata
+      150,         // max_frame_size
+      &stats};
+  grpc_core::HPackCompressor compressor;
+  compressor.EncodeHeaders(hopt, b, &output);
+  grpc_slice_buffer_destroy(&output);
+
+  EXPECT_EQ(compressor.test_only_table_size(), 114);
+}
+
 int main(int argc, char** argv) {
   grpc::testing::TestEnvironment env(&argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
diff --git a/test/core/transport/chttp2/hpack_parser_corpus/crash-39214285ae59b7193aad114858056cca7c21a8e5 b/test/core/transport/chttp2/hpack_parser_corpus/crash-39214285ae59b7193aad114858056cca7c21a8e5
new file mode 100644
index 0000000..3490435
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_parser_corpus/crash-39214285ae59b7193aad114858056cca7c21a8e5
@@ -0,0 +1,12 @@
+frames {
+  max_metadata_length: 4096
+  parse: "\244\244\020\007\360\244\017-bin\213c[)(-\'bin;\t!!?\244\037\333\360!\020\007\360{(-bin\360\t!\\\t!\345\037\351\033;?G\355[((!!\\\360"
+}
+frames {
+  max_metadata_length: 4096
+  parse: "*\244\020\007\360\244\017-bin\203c\035\037\000\'[\360i(bn-!?\244\037\333\360!(!\\\360\tc"
+  parse: "\244\244\020\007\360\244\017-bin\213c[)(-\'bin\t;!!?\244\037\333\360!\020\007\360{(-bin\360\t!\\\t!\345\037\351\033;?G\355[((!!\\\360"
+  parse: "\244\244\020\007\360\244\017-bin\213c[)(-\'bin\t;!!?\244\037\333\360!\020\007\360{(-bin\360\t!\\\t!\345\037\351\033;?G\355[((!!\\\360"
+  parse: "\244\244\020\007\360\244\017-bin\213c[)(-\'bin\t;!!?\244\037\333\360!\020\007\360{(-bin\360\t!\\\t!\345\037\351\033;?G\355[((!!\\\360"
+  absolute_max_metadata_length: 4096
+}
diff --git a/test/core/transport/chttp2/hpack_parser_corpus/crash-7f5f186fa8ac3950346da51bce3d76d0437d3b20 b/test/core/transport/chttp2/hpack_parser_corpus/crash-7f5f186fa8ac3950346da51bce3d76d0437d3b20
new file mode 100644
index 0000000..e9a303b
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_parser_corpus/crash-7f5f186fa8ac3950346da51bce3d76d0437d3b20
@@ -0,0 +1,38 @@
+frames {
+  end_of_headers: true
+}
+frames {
+  end_of_headers: true
+  end_of_stream: true
+  stop_buffering_after_segments: 4096
+  max_metadata_length: 16252928
+  parse: "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
+  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
+  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
+  parse: "\244\244\020\007\360\244\017-bin\213c*[)\244(\244-\017\333\360\'\360b\203cin\t!!"
+  absolute_max_metadata_length: 16252928
+}
+frames {
+  end_of_headers: true
+  end_of_stream: true
+  max_metadata_length: -4093
+  parse: "\261\261\261\261\261\261\261"
+  parse: "D\005:path"
+  parse: "\261\261\261\261\261\261\261"
+  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
+  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
+  absolute_max_metadata_length: 4096
+}
+frames {
+  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
+}
+frames {
+  end_of_headers: true
+  end_of_stream: true
+  stop_buffering_after_segments: 4096
+  max_metadata_length: 4096
+  parse: "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
+  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
+  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
+  parse: "\244\244\020\007\360\244\017-bin\213\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377c*[)\244(\244-\017\333\360\'\360b\203cin\t!!"
+}
diff --git a/test/core/transport/chttp2/hpack_parser_fuzzer.proto b/test/core/transport/chttp2/hpack_parser_fuzzer.proto
index da2eb62..2088f50 100644
--- a/test/core/transport/chttp2/hpack_parser_fuzzer.proto
+++ b/test/core/transport/chttp2/hpack_parser_fuzzer.proto
@@ -25,6 +25,7 @@
   int32 stop_buffering_after_segments = 4;
   int32 max_metadata_length = 5;
   repeated bytes parse = 6;
+  int32 absolute_max_metadata_length = 7;
 }
 
 message Msg {
diff --git a/test/core/transport/chttp2/hpack_parser_fuzzer_test.cc b/test/core/transport/chttp2/hpack_parser_fuzzer_test.cc
index 6b54900..45c103b 100644
--- a/test/core/transport/chttp2/hpack_parser_fuzzer_test.cc
+++ b/test/core/transport/chttp2/hpack_parser_fuzzer_test.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <memory>
 #include <string>
+#include <utility>
 
 #include <grpc/grpc.h>
 #include <grpc/slice.h>
@@ -69,12 +70,19 @@
         priority = grpc_core::HPackParser::Priority::Included;
       }
       int max_length = 1024;
+      int absolute_max_length = 1024;
+      if (absolute_max_length < max_length) {
+        std::swap(absolute_max_length, max_length);
+      }
       if (frame.max_metadata_length() != 0) {
         max_length = frame.max_metadata_length();
       }
+      if (frame.absolute_max_metadata_length() != 0) {
+        absolute_max_length = frame.absolute_max_metadata_length();
+      }
 
       parser->BeginFrame(
-          &b, max_length, boundary, priority,
+          &b, max_length, absolute_max_length, boundary, priority,
           grpc_core::HPackParser::LogInfo{
               1, grpc_core::HPackParser::LogInfo::kHeaders, false});
       int stop_buffering_ctr =
diff --git a/test/core/transport/chttp2/hpack_parser_input_size_corpus/empty b/test/core/transport/chttp2/hpack_parser_input_size_corpus/empty
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_parser_input_size_corpus/empty
@@ -0,0 +1 @@
+
diff --git a/test/core/transport/chttp2/hpack_parser_input_size_fuzzer.cc b/test/core/transport/chttp2/hpack_parser_input_size_fuzzer.cc
new file mode 100644
index 0000000..a675291
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_parser_input_size_fuzzer.cc
@@ -0,0 +1,151 @@
+// Copyright 2023 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// For all inputs, ensure parsing one byte at a time produces the same result as
+// parsing the entire input at once.
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <memory>
+#include <string>
+
+#include "absl/cleanup/cleanup.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+
+#include <grpc/event_engine/memory_allocator.h>
+#include <grpc/slice.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/time.h>
+
+#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/gprpp/status_helper.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/exec_ctx.h"
+#include "src/core/lib/resource_quota/arena.h"
+#include "src/core/lib/resource_quota/memory_quota.h"
+#include "src/core/lib/resource_quota/resource_quota.h"
+#include "src/core/lib/slice/slice.h"
+#include "src/core/lib/transport/metadata_batch.h"
+#include "test/core/util/slice_splitter.h"
+
+bool squelch = true;
+bool leak_check = true;
+
+namespace grpc_core {
+namespace {
+
+class TestEncoder {
+ public:
+  std::string result() { return out_; }
+
+  void Encode(const Slice& key, const Slice& value) {
+    out_.append(
+        absl::StrCat(key.as_string_view(), ": ", value.as_string_view(), "\n"));
+  }
+
+  template <typename T, typename V>
+  void Encode(T, const V& v) {
+    out_.append(absl::StrCat(T::key(), ": ", T::DisplayValue(v), "\n"));
+  }
+
+ private:
+  std::string out_;
+};
+
+bool IsStreamError(const absl::Status& status) {
+  intptr_t stream_id;
+  return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
+}
+
+absl::StatusOr<std::string> TestVector(grpc_slice_split_mode mode,
+                                       Slice input) {
+  MemoryAllocator memory_allocator = MemoryAllocator(
+      ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator("test"));
+  auto arena = MakeScopedArena(1024, &memory_allocator);
+  ExecCtx exec_ctx;
+  grpc_slice* slices;
+  size_t nslices;
+  size_t i;
+
+  grpc_metadata_batch b(arena.get());
+
+  HPackParser parser;
+  parser.BeginFrame(
+      &b, 1024, 1024, HPackParser::Boundary::None, HPackParser::Priority::None,
+      HPackParser::LogInfo{1, HPackParser::LogInfo::kHeaders, false});
+
+  grpc_split_slices(mode, const_cast<grpc_slice*>(&input.c_slice()), 1, &slices,
+                    &nslices);
+  auto cleanup_slices = absl::MakeCleanup([slices, nslices] {
+    for (size_t i = 0; i < nslices; i++) {
+      grpc_slice_unref(slices[i]);
+    }
+    gpr_free(slices);
+  });
+
+  absl::Status found_err;
+  for (i = 0; i < nslices; i++) {
+    ExecCtx exec_ctx;
+    auto err = parser.Parse(slices[i], i == nslices - 1);
+    if (!err.ok()) {
+      if (!IsStreamError(err)) return err;
+      if (found_err.ok()) found_err = err;
+    }
+  }
+  if (!found_err.ok()) return found_err;
+
+  TestEncoder encoder;
+  b.Encode(&encoder);
+  return encoder.result();
+}
+
+std::string Stringify(absl::StatusOr<std::string> result) {
+  if (result.ok()) {
+    return absl::StrCat("OK\n", result.value());
+  } else {
+    intptr_t stream_id;
+    bool has_stream = grpc_error_get_int(
+        result.status(), StatusIntProperty::kStreamId, &stream_id);
+    return absl::StrCat(
+        has_stream ? "STREAM" : "CONNECTION", " ERROR: ",
+        result.status().ToString(absl::StatusToStringMode::kWithNoExtraData));
+  }
+}
+
+}  // namespace
+}  // namespace grpc_core
+
+extern gpr_timespec (*gpr_now_impl)(gpr_clock_type clock_type);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  gpr_now_impl = [](gpr_clock_type clock_type) {
+    return gpr_timespec{10, 0, clock_type};
+  };
+  auto slice = grpc_core::Slice::FromCopiedBuffer(data, size);
+  auto full = grpc_core::Stringify(
+      grpc_core::TestVector(GRPC_SLICE_SPLIT_IDENTITY, slice.Ref()));
+  auto one_byte = grpc_core::Stringify(
+      grpc_core::TestVector(GRPC_SLICE_SPLIT_ONE_BYTE, slice.Ref()));
+  if (full != one_byte) {
+    fprintf(stderr, "MISMATCHED RESULTS\nFULL SLICE: %s\nONE BYTE: %s\n",
+            full.c_str(), one_byte.c_str());
+    abort();
+  }
+  return 0;
+}
diff --git a/test/core/transport/chttp2/hpack_parser_table_test.cc b/test/core/transport/chttp2/hpack_parser_table_test.cc
index cf1b24c..7b1dbd0 100644
--- a/test/core/transport/chttp2/hpack_parser_table_test.cc
+++ b/test/core/transport/chttp2/hpack_parser_table_test.cc
@@ -37,7 +37,7 @@
                  const char* value) {
   const auto* md = tbl->Lookup(idx);
   ASSERT_NE(md, nullptr);
-  EXPECT_EQ(md->DebugString(), absl::StrCat(key, ": ", value));
+  EXPECT_EQ(md->md.DebugString(), absl::StrCat(key, ": ", value));
 }
 }  // namespace
 
@@ -119,8 +119,12 @@
     std::string value = absl::StrCat("VALUE.", i);
     auto key_slice = Slice::FromCopiedString(key);
     auto value_slice = Slice::FromCopiedString(value);
-    auto memento =
-        HPackTable::Memento(std::move(key_slice), std::move(value_slice));
+    auto memento = HPackTable::Memento{
+        ParsedMetadata<grpc_metadata_batch>(
+            ParsedMetadata<grpc_metadata_batch>::FromSlicePair{},
+            std::move(key_slice), std::move(value_slice),
+            key.length() + value.length() + 32),
+        absl::OkStatus()};
     auto add_err = tbl.Add(std::move(memento));
     ASSERT_EQ(add_err, absl::OkStatus());
     AssertIndex(&tbl, 1 + hpack_constants::kLastStaticEntry, key.c_str(),
diff --git a/test/core/transport/chttp2/hpack_parser_test.cc b/test/core/transport/chttp2/hpack_parser_test.cc
index fde07a0..f8ebc4d 100644
--- a/test/core/transport/chttp2/hpack_parser_test.cc
+++ b/test/core/transport/chttp2/hpack_parser_test.cc
@@ -20,53 +20,64 @@
 
 #include <stdlib.h>
 
-#include <initializer_list>
 #include <memory>
 #include <string>
 
+#include "absl/cleanup/cleanup.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/str_cat.h"
-#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
 #include "absl/types/optional.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 #include <grpc/event_engine/memory_allocator.h>
 #include <grpc/grpc.h>
 #include <grpc/slice.h>
+#include <grpc/status.h>
 #include <grpc/support/alloc.h>
 
-#include "src/core/lib/gprpp/crash.h"
 #include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/gprpp/status_helper.h"
+#include "src/core/lib/gprpp/time.h"
 #include "src/core/lib/iomgr/exec_ctx.h"
 #include "src/core/lib/resource_quota/arena.h"
 #include "src/core/lib/resource_quota/memory_quota.h"
 #include "src/core/lib/resource_quota/resource_quota.h"
 #include "src/core/lib/slice/slice.h"
+#include "src/core/lib/transport/error_utils.h"
 #include "test/core/util/parse_hexstring.h"
 #include "test/core/util/slice_splitter.h"
 #include "test/core/util/test_config.h"
 
+namespace grpc_core {
+namespace {
+
+const uint32_t kFailureIsConnectionError = 1;
+const uint32_t kWithPriority = 2;
+const uint32_t kEndOfStream = 4;
+const uint32_t kEndOfHeaders = 8;
+
 struct TestInput {
-  const char* input;
-  const char* expected_parse;
+  absl::string_view input;
+  absl::StatusOr<absl::string_view> expected_parse;
+  uint32_t flags;
 };
 
 struct Test {
   absl::optional<size_t> table_size;
+  absl::optional<size_t> max_metadata_size;
   std::vector<TestInput> inputs;
 };
 
 class ParseTest : public ::testing::TestWithParam<Test> {
  public:
-  ParseTest() {
-    grpc_init();
-    parser_ = std::make_unique<grpc_core::HPackParser>();
-  }
+  ParseTest() { grpc_init(); }
 
   ~ParseTest() override {
     {
-      grpc_core::ExecCtx exec_ctx;
+      ExecCtx exec_ctx;
       parser_.reset();
     }
 
@@ -74,6 +85,7 @@
   }
 
   void SetUp() override {
+    parser_ = std::make_unique<HPackParser>();
     if (GetParam().table_size.has_value()) {
       parser_->hpack_table()->SetMaxBytes(GetParam().table_size.value());
       EXPECT_EQ(parser_->hpack_table()->SetCurrentTableSize(
@@ -82,15 +94,21 @@
     }
   }
 
-  void TestVector(grpc_slice_split_mode mode, const char* hexstring,
-                  std::string expect) {
-    grpc_core::MemoryAllocator memory_allocator =
-        grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
-                                       ->memory_quota()
-                                       ->CreateMemoryAllocator("test"));
-    auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
-    grpc_core::ExecCtx exec_ctx;
-    grpc_slice input = parse_hexstring(hexstring);
+  static bool IsStreamError(const absl::Status& status) {
+    intptr_t stream_id;
+    return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
+  }
+
+  void TestVector(grpc_slice_split_mode mode,
+                  absl::optional<size_t> max_metadata_size,
+                  absl::string_view hexstring,
+                  absl::StatusOr<absl::string_view> expect, uint32_t flags) {
+    MemoryAllocator memory_allocator = MemoryAllocator(
+        ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
+            "test"));
+    auto arena = MakeScopedArena(1024, &memory_allocator);
+    ExecCtx exec_ctx;
+    auto input = ParseHexstring(hexstring);
     grpc_slice* slices;
     size_t nslices;
     size_t i;
@@ -98,32 +116,59 @@
     grpc_metadata_batch b(arena.get());
 
     parser_->BeginFrame(
-        &b, 4096, grpc_core::HPackParser::Boundary::None,
-        grpc_core::HPackParser::Priority::None,
-        grpc_core::HPackParser::LogInfo{
-            1, grpc_core::HPackParser::LogInfo::kHeaders, false});
+        &b, max_metadata_size.value_or(4096), max_metadata_size.value_or(4096),
+        (flags & kEndOfStream)
+            ? HPackParser::Boundary::EndOfStream
+            : ((flags & kEndOfHeaders) ? HPackParser::Boundary::EndOfHeaders
+                                       : HPackParser::Boundary::None),
+        flags & kWithPriority ? HPackParser::Priority::Included
+                              : HPackParser::Priority::None,
+        HPackParser::LogInfo{1, HPackParser::LogInfo::kHeaders, false});
 
-    grpc_split_slices(mode, &input, 1, &slices, &nslices);
-    grpc_slice_unref(input);
+    grpc_split_slices(mode, const_cast<grpc_slice*>(&input.c_slice()), 1,
+                      &slices, &nslices);
+    auto cleanup_slices = absl::MakeCleanup([slices, nslices] {
+      for (size_t i = 0; i < nslices; i++) {
+        grpc_slice_unref(slices[i]);
+      }
+      gpr_free(slices);
+    });
 
+    bool saw_error = false;
     for (i = 0; i < nslices; i++) {
-      grpc_core::ExecCtx exec_ctx;
+      ExecCtx exec_ctx;
       auto err = parser_->Parse(slices[i], i == nslices - 1);
-      if (!err.ok()) {
-        grpc_core::Crash(
-            absl::StrFormat("Unexpected parse error: %s",
-                            grpc_core::StatusToString(err).c_str()));
+      if (!err.ok() && (flags & kFailureIsConnectionError) == 0) {
+        EXPECT_TRUE(IsStreamError(err)) << err;
+      }
+      if (!saw_error && !err.ok()) {
+        // one byte at a time mode might fail with a stream error early
+        if (mode == GRPC_SLICE_SPLIT_ONE_BYTE &&
+            (flags & kFailureIsConnectionError) && IsStreamError(err)) {
+          continue;
+        }
+        grpc_status_code code;
+        std::string message;
+        grpc_error_get_status(err, Timestamp::InfFuture(), &code, &message,
+                              nullptr, nullptr);
+        EXPECT_EQ(code, static_cast<grpc_status_code>(expect.status().code()))
+            << err;
+        EXPECT_THAT(message, ::testing::HasSubstr(expect.status().message()))
+            << err;
+        saw_error = true;
+        if (flags & kFailureIsConnectionError) return;
       }
     }
 
-    for (i = 0; i < nslices; i++) {
-      grpc_slice_unref(slices[i]);
+    if (!saw_error) {
+      EXPECT_TRUE(expect.ok()) << expect.status();
     }
-    gpr_free(slices);
 
-    TestEncoder encoder;
-    b.Encode(&encoder);
-    EXPECT_EQ(encoder.result(), expect);
+    if (expect.ok()) {
+      TestEncoder encoder;
+      b.Encode(&encoder);
+      EXPECT_EQ(encoder.result(), *expect) << "Input: " << hexstring;
+    }
   }
 
  private:
@@ -131,7 +176,7 @@
    public:
     std::string result() { return out_; }
 
-    void Encode(const grpc_core::Slice& key, const grpc_core::Slice& value) {
+    void Encode(const Slice& key, const Slice& value) {
       out_.append(absl::StrCat(key.as_string_view(), ": ",
                                value.as_string_view(), "\n"));
     }
@@ -146,41 +191,45 @@
     std::string out_;
   };
 
-  std::unique_ptr<grpc_core::HPackParser> parser_;
+  std::unique_ptr<HPackParser> parser_;
 };
 
 TEST_P(ParseTest, WholeSlices) {
   for (const auto& input : GetParam().inputs) {
-    TestVector(GRPC_SLICE_SPLIT_MERGE_ALL, input.input, input.expected_parse);
+    TestVector(GRPC_SLICE_SPLIT_MERGE_ALL, GetParam().max_metadata_size,
+               input.input, input.expected_parse, input.flags);
   }
 }
 
 TEST_P(ParseTest, OneByteAtATime) {
   for (const auto& input : GetParam().inputs) {
-    TestVector(GRPC_SLICE_SPLIT_ONE_BYTE, input.input, input.expected_parse);
+    TestVector(GRPC_SLICE_SPLIT_ONE_BYTE, GetParam().max_metadata_size,
+               input.input, input.expected_parse, input.flags);
   }
 }
 
 INSTANTIATE_TEST_SUITE_P(
     ParseTest, ParseTest,
     ::testing::Values(
-        Test{
-            {},
-            {
-                // D.2.1
-                {"400a 6375 7374 6f6d 2d6b 6579 0d63 7573"
-                 "746f 6d2d 6865 6164 6572",
-                 "custom-key: custom-header\n"},
-                // D.2.2
-                {"040c 2f73 616d 706c 652f 7061 7468", ":path: /sample/path\n"},
-                // D.2.3
-                {"1008 7061 7373 776f 7264 0673 6563 7265"
-                 "74",
-                 "password: secret\n"},
-                // D.2.4
-                {"82", ":method: GET\n"},
-            }},
         Test{{},
+             {},
+             {
+                 // D.2.1
+                 {"400a 6375 7374 6f6d 2d6b 6579 0d63 7573"
+                  "746f 6d2d 6865 6164 6572",
+                  "custom-key: custom-header\n", 0},
+                 // D.2.2
+                 {"040c 2f73 616d 706c 652f 7061 7468", ":path: /sample/path\n",
+                  0},
+                 // D.2.3
+                 {"1008 7061 7373 776f 7264 0673 6563 7265"
+                  "74",
+                  "password: secret\n", 0},
+                 // D.2.4
+                 {"82", ":method: GET\n", 0},
+             }},
+        Test{{},
+             {},
              {
                  // D.3.1
                  {"8286 8441 0f77 7777 2e65 7861 6d70 6c65"
@@ -188,14 +237,16 @@
                   ":path: /\n"
                   ":authority: www.example.com\n"
                   ":method: GET\n"
-                  ":scheme: http\n"},
+                  ":scheme: http\n",
+                  0},
                  // D.3.2
                  {"8286 84be 5808 6e6f 2d63 6163 6865",
                   ":path: /\n"
                   ":authority: www.example.com\n"
                   ":method: GET\n"
                   ":scheme: http\n"
-                  "cache-control: no-cache\n"},
+                  "cache-control: no-cache\n",
+                  0},
                  // D.3.3
                  {"8287 85bf 400a 6375 7374 6f6d 2d6b 6579"
                   "0c63 7573 746f 6d2d 7661 6c75 65",
@@ -203,9 +254,11 @@
                   ":authority: www.example.com\n"
                   ":method: GET\n"
                   ":scheme: https\n"
-                  "custom-key: custom-value\n"},
+                  "custom-key: custom-value\n",
+                  0},
              }},
         Test{{},
+             {},
              {
                  // D.4.1
                  {"8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4"
@@ -213,14 +266,16 @@
                   ":path: /\n"
                   ":authority: www.example.com\n"
                   ":method: GET\n"
-                  ":scheme: http\n"},
+                  ":scheme: http\n",
+                  0},
                  // D.4.2
                  {"8286 84be 5886 a8eb 1064 9cbf",
                   ":path: /\n"
                   ":authority: www.example.com\n"
                   ":method: GET\n"
                   ":scheme: http\n"
-                  "cache-control: no-cache\n"},
+                  "cache-control: no-cache\n",
+                  0},
                  // D.4.3
                  {"8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925"
                   "a849 e95b b8e8 b4bf",
@@ -228,9 +283,11 @@
                   ":authority: www.example.com\n"
                   ":method: GET\n"
                   ":scheme: https\n"
-                  "custom-key: custom-value\n"},
+                  "custom-key: custom-value\n",
+                  0},
              }},
         Test{{256},
+             {},
              {
                  // D.5.1
                  {"4803 3330 3258 0770 7269 7661 7465 611d"
@@ -241,13 +298,15 @@
                   ":status: 302\n"
                   "cache-control: private\n"
                   "date: Mon, 21 Oct 2013 20:13:21 GMT\n"
-                  "location: https://www.example.com\n"},
+                  "location: https://www.example.com\n",
+                  0},
                  // D.5.2
                  {"4803 3330 37c1 c0bf",
                   ":status: 307\n"
                   "cache-control: private\n"
                   "date: Mon, 21 Oct 2013 20:13:21 GMT\n"
-                  "location: https://www.example.com\n"},
+                  "location: https://www.example.com\n",
+                  0},
                  // D.5.3
                  {"88c1 611d 4d6f 6e2c 2032 3120 4f63 7420"
                   "3230 3133 2032 303a 3133 3a32 3220 474d"
@@ -262,9 +321,11 @@
                   "location: https://www.example.com\n"
                   "content-encoding: gzip\n"
                   "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; "
-                  "version=1\n"},
+                  "version=1\n",
+                  0},
              }},
         Test{{256},
+             {},
              {
                  // D.6.1
                  {"4882 6402 5885 aec3 771a 4b61 96d0 7abe"
@@ -274,13 +335,15 @@
                   ":status: 302\n"
                   "cache-control: private\n"
                   "date: Mon, 21 Oct 2013 20:13:21 GMT\n"
-                  "location: https://www.example.com\n"},
+                  "location: https://www.example.com\n",
+                  0},
                  // D.6.2
                  {"4883 640e ffc1 c0bf",
                   ":status: 307\n"
                   "cache-control: private\n"
                   "date: Mon, 21 Oct 2013 20:13:21 GMT\n"
-                  "location: https://www.example.com\n"},
+                  "location: https://www.example.com\n",
+                  0},
                  // D.6.3
                  {"88c1 6196 d07a be94 1054 d444 a820 0595"
                   "040b 8166 e084 a62d 1bff c05a 839b d9ab"
@@ -293,18 +356,356 @@
                   "location: https://www.example.com\n"
                   "content-encoding: gzip\n"
                   "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; "
-                  "version=1\n"},
+                  "version=1\n",
+                  0},
              }},
         Test{{},
+             {1024},
+             {{"3fc43fc4", absl::InternalError("Attempt to make hpack table"),
+               kFailureIsConnectionError}}},
+        Test{{},
+             {},
+             {{"3ba4a41007f0a40f2d62696e8b632a5b29a40fa4a4281007f0",
+               absl::InternalError("Invalid HPACK index received"),
+               kFailureIsConnectionError}}},
+        Test{{},
+             {},
+             {{"2aa41007f0a40f2d62696e8163a41f1f00275bf0692862a4dbf0f00963",
+               absl::InternalError(
+                   "More than two max table size changes in a single frame"),
+               kFailureIsConnectionError}}},
+        Test{{},
+             {},
+             {{"2aa41007f0a40f2d62696e8363271f00275bf06928626e2d213fa40fdbf0212"
+               "8215cf00963",
+               absl::InternalError("illegal base64 encoding"), 0}}},
+        Test{{},
+             {},
+             {{"a4a41007f0a40f2d62696e8b635b29282d2762696e3b0921213fa41fdbf0211"
+               "007f07b282d62696ef009215c0921e51fe91b3b3f47ed5b282821215cf0",
+               absl::InternalError(
+                   "More than two max table size changes in a single frame"),
+               kFailureIsConnectionError}}},
+        Test{
+            {},
+            {},
+            {{"696969696969696969696969696969696969696969696969696969696969696"
+              "969696969696969696969696969696969696969696969696969696969696969"
+              "6969696969696969696969696969bababababababababababababababababab"
+              "abababababababababababababababababababababababababababababababa"
+              "bababababababababababababababababababababababababababababababab"
+              "abababababaa4a41007f0a40f2d62696e8bffffffffffffffffffffffffffff"
+              "ffffffffffff632a5b29a428a42d0fdbf027f0628363696e092121",
+              absl::InternalError("integer overflow in hpack integer decoding"),
+              kEndOfHeaders | kFailureIsConnectionError}}},
+        Test{{},
+             {},
+             {{"0e 00 00 df",
+               absl::InternalError(
+                   "Error parsing ':status' metadata: error=not an integer"),
+               0}}},
+        Test{{},
+             {},
              {
                  // Binary metadata: created using:
                  // tools/codegen/core/gen_header_frame.py
-                 //    --compression inc --no_framing --hex
+                 //    --compression inc --no_framing --output hexstr
                  //    < test/core/transport/chttp2/binary-metadata.headers
                  {"40 09 61 2e 62 2e 63 2d 62 69 6e 0c 62 32 31 6e 4d 6a 41 79 "
                   "4d 51 3d 3d",
-                  "a.b.c-bin: omg2021\n"},
-             }}));
+                  "a.b.c-bin: omg2021\n", 0},
+             }},
+        Test{{},
+             {},
+             {// Binary metadata: created using:
+              // tools/codegen/core/gen_header_frame.py
+              //    --compression inc --no_framing --output hexstr
+              //    < test/core/transport/chttp2/bad-base64.headers
+              {"4009612e622e632d62696e1c6c75636b696c7920666f722075732c206974"
+               "27732074756573646179",
+               absl::InternalError("Error parsing 'a.b.c-bin' metadata: "
+                                   "error=illegal base64 encoding"),
+               0},
+              {"be",
+               absl::InternalError("Error parsing 'a.b.c-bin' metadata: "
+                                   "error=illegal base64 encoding"),
+               0}}},
+        Test{{},
+             {},
+             {// created using:
+              // tools/codegen/core/gen_header_frame.py
+              //    --compression inc --no_framing --output hexstr
+              //    < test/core/transport/chttp2/bad-te.headers
+              {"400274650767617262616765",
+               absl::InternalError("Error parsing 'te' metadata"), 0},
+              {"be", absl::InternalError("Error parsing 'te' metadata"), 0}}},
+        Test{{},
+             128,
+             {
+                 {// Generated with: tools/codegen/core/gen_header_frame.py
+                  // --compression inc --output hexstr --no_framing <
+                  // test/core/transport/chttp2/large-metadata.headers
+                  "40096164616c64726964610a6272616e64796275636b40086164616c6772"
+                  "696d04746f6f6b4008616d6172616e74680a6272616e64796275636b4008"
+                  "616e67656c6963610762616767696e73",
+                  absl::ResourceExhaustedError(
+                      "received metadata size exceeds hard limit"),
+                  0},
+                 // Should be able to look up the added elements individually
+                 // (do not corrupt the hpack table test!)
+                 {"be", "angelica: baggins\n", 0},
+                 {"bf", "amaranth: brandybuck\n", 0},
+                 {"c0", "adalgrim: took\n", 0},
+                 {"c1", "adaldrida: brandybuck\n", 0},
+                 // But not as a whole - that exceeds metadata limits for one
+                 // request again
+                 {"bebfc0c1",
+                  absl::ResourceExhaustedError(
+                      "received metadata size exceeds hard limit"),
+                  0},
+             }},
+        Test{
+            {},
+            {},
+            {{"be", absl::InternalError("Invalid HPACK index received"),
+              kFailureIsConnectionError}},
+        },
+        Test{
+            {},
+            {},
+            {{"80", absl::InternalError("Illegal hpack op code"),
+              kFailureIsConnectionError}},
+        },
+        Test{
+            {},
+            {},
+            {{"29", "", kFailureIsConnectionError}},
+        },
+        Test{
+            {},
+            {},
+            {{"", "", kWithPriority}},
+        },
+        Test{
+            {},
+            {},
+            {{"f5", absl::InternalError("Invalid HPACK index received"),
+              kFailureIsConnectionError}},
+        },
+        Test{
+            {},
+            {},
+            {{"0f", "", 0}},
+        },
+        Test{
+            {},
+            {},
+            {{"7f", "", 0}},
+        },
+        Test{
+            {},
+            {},
+            {{"1bffffff7c1b", "", 0}},
+        },
+        Test{
+            {},
+            {},
+            {{"ffffffffff00ff",
+              absl::InternalError("Invalid HPACK index received"),
+              kFailureIsConnectionError}},
+        },
+        Test{
+            {},
+            {},
+            {{"ff8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8"
+              "d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d"
+              "8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8"
+              "d8d8d8d8d8d8d8d",
+              absl::InternalError("integer overflow in hpack integer decoding"),
+              kFailureIsConnectionError}}},
+        Test{
+            {},
+            {9},
+            {{"3f6672616d6573207ba2020656e645f6f665f686561646572733a2074727565a"
+              "2020656e645f6f665f73747265616d3a2074727565a202073746f705f6275666"
+              "66572696e675f61667465725f7365676d656e74733a2039a202070617273653a"
+              "20225c3030305c3030305c3030305c3030305c3030305c3030305c3030305c30"
+              "30305c3030305c3030305c3030305c3030305c3030305c3030305c3030305c30"
+              "30305c3030305c3030305c3030305c3030305c3030305c3030305c3030305c",
+              absl::ResourceExhaustedError(
+                  "received metadata size exceeds hard limit"),
+              kWithPriority}}},
+        Test{{},
+             {},
+             {{"52046772706300073a737461747573033230300e7f",
+               ":status: 200\naccept-ranges: grpc\n", 0}}},
+        Test{{},
+             {},
+             {{"a4a41007f0a40f2d62696e8beda42d5b63272129a410626907",
+               absl::InternalError("illegal base64 encoding"), 0}}},
+        Test{
+            // haiku segment: 149bytes*2, a:a segment: 34 bytes
+            // So we arrange for one less than the total so we force a hpack
+            // table overflow
+            {149 * 2 + 34 - 1},
+            {},
+            {
+                {// Generated with: tools/codegen/core/gen_header_frame.py
+                 // --compression inc --output hexstr --no_framing <
+                 // test/core/transport/chttp2/long-base64.headers
+                 "4005782d62696e70516d467a5a545930494756755932396b6157356e4f67"
+                 "704a644342305957746c6379426961573568636e6b675a47463059534268"
+                 "626d5167625746725a584d6761585167644756346443344b56584e6c5a6e5"
+                 "67349475a766369427a644739796157356e49475a706247567a4c673d3d",
+                 // Haiku by Bard.
+                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
+                 "text.\nUseful for storing files.\n",
+                 0},
+                // Should go into the hpack table (x-bin: ... is 149 bytes long
+                // by hpack rules)
+                {"be",
+                 "x-bin: Base64 encoding:\nIt takes binary data and "
+                 "makes it text.\nUseful for storing files.\n",
+                 0},
+                // Add another copy
+                {"4005782d62696e70516d467a5a545930494756755932396b6157356e4f67"
+                 "704a644342305957746c6379426961573568636e6b675a47463059534268"
+                 "626d5167625746725a584d6761585167644756346443344b56584e6c5a6e5"
+                 "67349475a766369427a644739796157356e49475a706247567a4c673d3d",
+                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
+                 "text.\nUseful for storing files.\n",
+                 0},
+                // 149*2 == 298, so we should have two copies in the hpack table
+                {"bebf",
+                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
+                 "text.\nUseful for storing files.\n"
+                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
+                 "text.\nUseful for storing files.\n",
+                 0},
+                // Add some very short headers (should push the first long thing
+                // out)
+                // Generated with: tools/codegen/core/gen_header_frame.py
+                // --compression inc --output hexstr --no_framing <
+                // test/core/transport/chttp2/short.headers
+                {"4001610161", "a: a\n", 0},
+                // First two entries should be what was just pushed and then one
+                // long entry
+                {"bebf",
+                 "a: a\nx-bin: Base64 encoding:\nIt takes binary data and "
+                 "makes "
+                 "it text.\nUseful for storing files.\n",
+                 0},
+                // Third entry should be unprobable (it's no longer in the
+                // table!)
+                {"c0", absl::InternalError("Invalid HPACK index received"),
+                 kFailureIsConnectionError},
+            }},
+        Test{
+            // haiku segment: 149bytes*2, a:a segment: 34 bytes
+            // So we arrange for one less than the total so we force a hpack
+            // table overflow
+            {149 * 2 + 34 - 1},
+            {},
+            {
+                {// Generated with: tools/codegen/core/gen_header_frame.py
+                 // --compression inc --output hexstr --no_framing --huff <
+                 // test/core/transport/chttp2/long-base64.headers
+                 "4005782d62696edbd94e1f7fbbf983262e36f313fd47c9bab54d5e592f5d0"
+                 "73e49a09eae987c9b9c95759bf7161073dd7678e9d9347cb0d9fbf9a261fe"
+                 "6c9a4c5c5a92f359b8fe69a3f6ae28c98bf7b90d77dc989ff43e4dd59317e"
+                 "d71e2e3ef3cd041",
+                 // Haiku by Bard.
+                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
+                 "text.\nUseful for storing files.\n",
+                 0},
+                // Should go into the hpack table (x-bin: ... is 149 bytes long
+                // by hpack rules)
+                {"be",
+                 "x-bin: Base64 encoding:\nIt takes binary data and "
+                 "makes it text.\nUseful for storing files.\n",
+                 0},
+                // Add another copy
+                {"4005782d62696edbd94e1f7fbbf983262e36f313fd47c9bab54d5e592f5d0"
+                 "73e49a09eae987c9b9c95759bf7161073dd7678e9d9347cb0d9fbf9a261fe"
+                 "6c9a4c5c5a92f359b8fe69a3f6ae28c98bf7b90d77dc989ff43e4dd59317e"
+                 "d71e2e3ef3cd041",
+                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
+                 "text.\nUseful for storing files.\n",
+                 0},
+                // 149*2 == 298, so we should have two copies in the hpack table
+                {"bebf",
+                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
+                 "text.\nUseful for storing files.\n"
+                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
+                 "text.\nUseful for storing files.\n",
+                 0},
+                // Add some very short headers (should push the first long thing
+                // out)
+                // Generated with: tools/codegen/core/gen_header_frame.py
+                // --compression inc --output hexstr --no_framing <
+                // test/core/transport/chttp2/short.headers
+                {"4001610161", "a: a\n", 0},
+                // First two entries should be what was just pushed and then one
+                // long entry
+                {"bebf",
+                 "a: a\nx-bin: Base64 encoding:\nIt takes binary data and "
+                 "makes "
+                 "it text.\nUseful for storing files.\n",
+                 0},
+                // Third entry should be unprobable (it's no longer in the
+                // table!)
+                {"c0", absl::InternalError("Invalid HPACK index received"),
+                 kFailureIsConnectionError},
+            }},
+        Test{{}, {}, {{"7a", "", 0}}},
+        Test{{},
+             {},
+             {{"60",
+               absl::InternalError("Incomplete header at the end of a "
+                                   "header/continuation sequence"),
+               kEndOfStream | kFailureIsConnectionError}}},
+        Test{{},
+             {},
+             {{"89", ":status: 204\n", 0},
+              {"89", ":status: 204\n", 0},
+              {"393939393939393939393939393939393939393939",
+               absl::InternalError(
+                   "More than two max table size changes in a single frame"),
+               kFailureIsConnectionError}}},
+        Test{{},
+             {},
+             {{"4005782d62696edbd94e1f7fbbf983267e36a313fd47c9bab54d5e592f5d",
+               "", 0}}},
+        Test{{}, {}, {{"72656672657368", "", 0}}},
+        Test{{}, {}, {{"66e6645f74", "", 0}, {"66645f74", "", 0}}},
+        Test{
+            {},
+            {},
+            {{// Generated with: tools/codegen/core/gen_header_frame.py
+              // --compression inc --output hexstr --no_framing <
+              // test/core/transport/chttp2/MiXeD-CaSe.headers
+              "400a4d695865442d436153651073686f756c64206e6f74207061727365",
+              absl::InternalError("Illegal header key: MiXeD-CaSe"), 0},
+             {// Looking up with hpack indices should work, but also return
+              // error
+              "be", absl::InternalError("Illegal header key: MiXeD-CaSe"), 0}}},
+        Test{
+            {},
+            {},
+            {{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+              absl::InternalError("integer overflow in hpack integer decoding"),
+              kFailureIsConnectionError}}},
+        Test{{},
+             {},
+             {{"dadadadadadadadadadadadadadadadadadadadadadadadadadadadadadadad"
+               "adadadadadadadadadadadadadadadadadadadadadadadadadadadadadadada"
+               "dadadadadadadadadadadadadadadadadadadadadadadadadadadadadadadad"
+               "adadadadadadadadadadadadadadadadadadada",
+               absl::InternalError("Invalid HPACK index received"),
+               kWithPriority | kFailureIsConnectionError}}}));
+
+}  // namespace
+}  // namespace grpc_core
 
 int main(int argc, char** argv) {
   grpc::testing::TestEnvironment env(&argc, argv);
diff --git a/test/core/transport/chttp2/hpack_sync_corpus/clusterfuzz-testcase-minimized-hpack_sync_fuzzer-5224520566571008.fuzz b/test/core/transport/chttp2/hpack_sync_corpus/clusterfuzz-testcase-minimized-hpack_sync_fuzzer-5224520566571008.fuzz
new file mode 100644
index 0000000..9057bb6
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_sync_corpus/clusterfuzz-testcase-minimized-hpack_sync_fuzzer-5224520566571008.fuzz
@@ -0,0 +1,44 @@
+headers {
+}
+headers {
+}
+headers {
+  literal_inc_idx {
+    key: ":scheme"
+    value: "grpc-taOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO\"\n    value: \"`````````````````````````````````````````````````````````````````````\"\n  }\n}\nheaders {\n}\nheaders {\n  indexed: 2\n}\nheaders {\n  indexed: 2\n}\nheaders {\n  literal_inc_idx {\n    key: \"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              z                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       OOOOOOOOOOOOOOOOOOO\"\n    value: \"`````````````gs-bin"
+  }
+}
+headers {
+  literal_inc_idx {
+    key: "user-agent"
+    value: "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"
+  }
+}
+headers {
+  literal_not_idx {
+    value: "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"
+  }
+}
+headers {
+  indexed: 62
+}
+headers {
+  indexed: 62
+}
+headers {
+  indexed: 62
+}
+headers {
+  indexed: 62
+}
+headers {
+  literal_not_idx {
+    value: "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"
+  }
+}
+headers {
+  indexed: 8
+}
+headers {
+  indexed: 62
+}
diff --git a/test/core/transport/chttp2/hpack_sync_corpus/crash-0c85d3a3dad81ec97be1a3079ff93f17c25d9723 b/test/core/transport/chttp2/hpack_sync_corpus/crash-0c85d3a3dad81ec97be1a3079ff93f17c25d9723
new file mode 100644
index 0000000..de93bb7
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_sync_corpus/crash-0c85d3a3dad81ec97be1a3079ff93f17c25d9723
@@ -0,0 +1,10 @@
+headers {
+  literal_not_idx {
+    key: "\000\000\000\026"
+  }
+}
+headers {
+  literal_inc_idx {
+    value: "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+  }
+}
diff --git a/test/core/transport/chttp2/hpack_sync_corpus/crash-212b1a7ccb2034b7f21be3b413c2de51fcacef79 b/test/core/transport/chttp2/hpack_sync_corpus/crash-212b1a7ccb2034b7f21be3b413c2de51fcacef79
new file mode 100644
index 0000000..2e5ed89
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_sync_corpus/crash-212b1a7ccb2034b7f21be3b413c2de51fcacef79
@@ -0,0 +1,14 @@
+headers {
+  literal_not_idx_from_idx {
+  }
+}
+headers {
+  literal_not_idx_from_idx {
+    index: 18688
+  }
+}
+headers {
+  literal_inc_idx {
+    key: "\001\000\000\000\000\000\000\003"
+  }
+}
diff --git a/test/core/transport/chttp2/hpack_sync_corpus/crash-298b34c2c15ac7b7fe8174017265d7e3b3313804 b/test/core/transport/chttp2/hpack_sync_corpus/crash-298b34c2c15ac7b7fe8174017265d7e3b3313804
new file mode 100644
index 0000000..b62c811
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_sync_corpus/crash-298b34c2c15ac7b7fe8174017265d7e3b3313804
@@ -0,0 +1,12 @@
+headers {
+  literal_inc_idx {
+    value: "\013"
+  }
+}
+headers {
+}
+headers {
+  literal_inc_idx {
+    value: "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+  }
+}
diff --git a/test/core/transport/chttp2/hpack_sync_corpus/crash-5d27241617bb39dc456910aa4fa18431f2458dc4 b/test/core/transport/chttp2/hpack_sync_corpus/crash-5d27241617bb39dc456910aa4fa18431f2458dc4
new file mode 100644
index 0000000..80fb5f1
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_sync_corpus/crash-5d27241617bb39dc456910aa4fa18431f2458dc4
@@ -0,0 +1,9 @@
+headers {
+}
+headers {
+}
+headers {
+  literal_inc_idx {
+    key: "E"
+  }
+}
diff --git a/test/core/transport/chttp2/hpack_sync_corpus/crash-85f9f9c7c971ec3fa839df8b14b4bad15d13f4ea b/test/core/transport/chttp2/hpack_sync_corpus/crash-85f9f9c7c971ec3fa839df8b14b4bad15d13f4ea
new file mode 100644
index 0000000..89b8cb8
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_sync_corpus/crash-85f9f9c7c971ec3fa839df8b14b4bad15d13f4ea
@@ -0,0 +1,24 @@
+use_true_binary_metadata: true
+headers {
+  literal_not_idx {
+    key: "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"
+  }
+}
+headers {
+  literal_inc_idx {
+    key: "grpc-tags-bin"
+    value: "grpc-tags-bin"
+  }
+}
+headers {
+  literal_inc_idx {
+    key: "grpc-tags-bin"
+  }
+}
+headers {
+  literal_inc_idx {
+    key: "grpc-tags-bin"
+  }
+}
+headers {
+}
diff --git a/test/core/transport/chttp2/hpack_sync_fuzzer.cc b/test/core/transport/chttp2/hpack_sync_fuzzer.cc
new file mode 100644
index 0000000..3046d50
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_sync_fuzzer.cc
@@ -0,0 +1,173 @@
+// Copyright 2023 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/match.h"
+
+#include <grpc/support/log.h>
+
+#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
+#include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h"
+#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
+#include "src/core/ext/transport/chttp2/transport/hpack_parser_table.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/gprpp/status_helper.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/exec_ctx.h"
+#include "src/core/lib/resource_quota/arena.h"
+#include "src/core/lib/resource_quota/memory_quota.h"
+#include "src/core/lib/resource_quota/resource_quota.h"
+#include "src/core/lib/slice/slice.h"
+#include "src/core/lib/slice/slice_buffer.h"
+#include "src/core/lib/transport/metadata_batch.h"
+#include "src/libfuzzer/libfuzzer_macro.h"
+#include "test/core/transport/chttp2/hpack_sync_fuzzer.pb.h"
+
+bool squelch = true;
+bool leak_check = true;
+
+static void dont_log(gpr_log_func_args* /*args*/) {}
+
+namespace grpc_core {
+namespace {
+
+bool IsStreamError(const absl::Status& status) {
+  intptr_t stream_id;
+  return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
+}
+
+void FuzzOneInput(const hpack_sync_fuzzer::Msg& msg) {
+  // STAGE 1: Encode the fuzzing input into a buffer (encode_output)
+  HPackCompressor compressor;
+  SliceBuffer encode_output;
+  hpack_encoder_detail::Encoder encoder(
+      &compressor, msg.use_true_binary_metadata(), encode_output);
+  for (const auto& header : msg.headers()) {
+    switch (header.ty_case()) {
+      case hpack_sync_fuzzer::Header::TY_NOT_SET:
+        break;
+      case hpack_sync_fuzzer::Header::kIndexed:
+        if (header.indexed() == 0) continue;  // invalid encoding
+        encoder.EmitIndexed(header.indexed());
+        break;
+      case hpack_sync_fuzzer::Header::kLiteralIncIdx:
+        if (header.literal_inc_idx().key().length() +
+                header.literal_inc_idx().value().length() >
+            HPackEncoderTable::MaxEntrySize() / 2) {
+          // Not an interesting case to fuzz
+          continue;
+        }
+        if (absl::EndsWith(header.literal_inc_idx().value(), "-bin")) {
+          std::ignore = encoder.EmitLitHdrWithBinaryStringKeyIncIdx(
+              Slice::FromCopiedString(header.literal_inc_idx().key()),
+              Slice::FromCopiedString(header.literal_inc_idx().value()));
+        } else {
+          std::ignore = encoder.EmitLitHdrWithNonBinaryStringKeyIncIdx(
+              Slice::FromCopiedString(header.literal_inc_idx().key()),
+              Slice::FromCopiedString(header.literal_inc_idx().value()));
+        }
+        break;
+      case hpack_sync_fuzzer::Header::kLiteralNotIdx:
+        if (absl::EndsWith(header.literal_not_idx().value(), "-bin")) {
+          encoder.EmitLitHdrWithBinaryStringKeyNotIdx(
+              Slice::FromCopiedString(header.literal_not_idx().key()),
+              Slice::FromCopiedString(header.literal_not_idx().value()));
+        } else {
+          encoder.EmitLitHdrWithNonBinaryStringKeyNotIdx(
+              Slice::FromCopiedString(header.literal_not_idx().key()),
+              Slice::FromCopiedString(header.literal_not_idx().value()));
+        }
+        break;
+      case hpack_sync_fuzzer::Header::kLiteralNotIdxFromIdx:
+        if (header.literal_not_idx_from_idx().index() == 0) continue;
+        encoder.EmitLitHdrWithBinaryStringKeyNotIdx(
+            header.literal_not_idx_from_idx().index(),
+            Slice::FromCopiedString(header.literal_not_idx_from_idx().value()));
+        break;
+    }
+  }
+
+  // STAGE 2: Decode the buffer (encode_output) into a list of headers
+  HPackParser parser;
+  auto memory_allocator =
+      ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
+          "test-allocator");
+  auto arena = MakeScopedArena(1024, &memory_allocator);
+  ExecCtx exec_ctx;
+  grpc_metadata_batch read_metadata(arena.get());
+  parser.BeginFrame(
+      &read_metadata, 1024, 1024, HPackParser::Boundary::EndOfHeaders,
+      HPackParser::Priority::None,
+      HPackParser::LogInfo{1, HPackParser::LogInfo::kHeaders, false});
+  std::vector<std::pair<size_t, absl::Status>> seen_errors;
+  for (size_t i = 0; i < encode_output.Count(); i++) {
+    auto err = parser.Parse(encode_output.c_slice_at(i),
+                            i == (encode_output.Count() - 1));
+    if (!err.ok()) {
+      seen_errors.push_back(std::make_pair(i, err));
+      // If we get a connection error (i.e. not a stream error), stop parsing,
+      // return.
+      if (!IsStreamError(err)) return;
+    }
+  }
+
+  // STAGE 3: If we reached here we either had a stream error or no error
+  // parsing.
+  // Either way, the hpack tables should be of the same size between client and
+  // server.
+  const auto encoder_size = encoder.hpack_table().test_only_table_size();
+  const auto parser_size = parser.hpack_table()->test_only_table_size();
+  const auto encoder_elems = encoder.hpack_table().test_only_table_elems();
+  const auto parser_elems = parser.hpack_table()->num_entries();
+  if (encoder_size != parser_size || encoder_elems != parser_elems) {
+    fprintf(stderr, "Encoder size: %d Parser size: %d\n", encoder_size,
+            parser_size);
+    fprintf(stderr, "Encoder elems: %d Parser elems: %d\n", encoder_elems,
+            parser_elems);
+    if (!seen_errors.empty()) {
+      fprintf(stderr, "Seen errors during parse:\n");
+      for (const auto& error : seen_errors) {
+        fprintf(stderr, "  [slice %" PRIdPTR "] %s\n", error.first,
+                error.second.ToString().c_str());
+      }
+    }
+    fprintf(stderr, "Encoded data:\n");
+    for (size_t i = 0; i < encode_output.Count(); i++) {
+      fprintf(
+          stderr, "  [slice %" PRIdPTR "]: %s\n", i,
+          absl::BytesToHexString(encode_output[i].as_string_view()).c_str());
+    }
+    abort();
+  }
+}
+
+}  // namespace
+}  // namespace grpc_core
+
+DEFINE_PROTO_FUZZER(const hpack_sync_fuzzer::Msg& msg) {
+  if (squelch) gpr_set_log_function(dont_log);
+  grpc_core::FuzzOneInput(msg);
+}
diff --git a/test/core/transport/chttp2/hpack_sync_fuzzer.proto b/test/core/transport/chttp2/hpack_sync_fuzzer.proto
new file mode 100644
index 0000000..7cdacfe
--- /dev/null
+++ b/test/core/transport/chttp2/hpack_sync_fuzzer.proto
@@ -0,0 +1,43 @@
+// Copyright 2023 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package hpack_sync_fuzzer;
+
+message Empty {}
+
+message StringKeyValue {
+    string key = 1;
+    string value = 2;
+}
+
+message IndexedKeyValue {
+    uint32 index = 1;
+    string value = 2;
+}
+
+message Header {
+    oneof ty {
+        uint32 indexed = 1;
+        StringKeyValue literal_inc_idx = 2;
+        StringKeyValue literal_not_idx = 3;
+        IndexedKeyValue literal_not_idx_from_idx = 4;
+    }
+}
+
+message Msg {
+  bool use_true_binary_metadata = 1;
+  repeated Header headers = 2;
+}
diff --git a/test/core/transport/chttp2/large-metadata.headers b/test/core/transport/chttp2/large-metadata.headers
new file mode 100644
index 0000000..c3eb2bc
--- /dev/null
+++ b/test/core/transport/chttp2/large-metadata.headers
@@ -0,0 +1,8 @@
+# 19 -> 51
+adaldrida: brandybuck
+# 12 -> 95
+adalgrim: took
+# 18 -> 145
+amaranth: brandybuck
+# 15 -> 192
+angelica: baggins
diff --git a/test/core/transport/chttp2/long-base64.headers b/test/core/transport/chttp2/long-base64.headers
new file mode 100644
index 0000000..37a62b1
--- /dev/null
+++ b/test/core/transport/chttp2/long-base64.headers
@@ -0,0 +1 @@
+x-bin: QmFzZTY0IGVuY29kaW5nOgpJdCB0YWtlcyBiaW5hcnkgZGF0YSBhbmQgbWFrZXMgaXQgdGV4dC4KVXNlZnVsIGZvciBzdG9yaW5nIGZpbGVzLg==
diff --git a/test/core/transport/chttp2/short.headers b/test/core/transport/chttp2/short.headers
new file mode 100644
index 0000000..75dc0fc
--- /dev/null
+++ b/test/core/transport/chttp2/short.headers
@@ -0,0 +1 @@
+a: a
diff --git a/test/core/transport/chttp2/too_many_pings_test.cc b/test/core/transport/chttp2/too_many_pings_test.cc
index b23c279..3da4bb1 100644
--- a/test/core/transport/chttp2/too_many_pings_test.cc
+++ b/test/core/transport/chttp2/too_many_pings_test.cc
@@ -18,7 +18,6 @@
 
 #include <grpc/support/port_platform.h>
 
-#include <stdint.h>
 #include <string.h>
 
 #include <algorithm>
@@ -105,8 +104,6 @@
 
 int TransportCounter::count_ = 0;
 
-void* tag(intptr_t t) { return reinterpret_cast<void*>(t); }
-
 // Perform a simple RPC where the server cancels the request with
 // grpc_call_cancel_with_status
 grpc_status_code PerformCall(grpc_channel* channel, grpc_server* server,
@@ -152,18 +149,19 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
   // Request a call on the server
   error = grpc_server_request_call(server, &s, &call_details,
-                                   &request_metadata_recv, cq, cq, tag(101));
+                                   &request_metadata_recv, cq, cq,
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
   grpc_call_cancel_with_status(s, GRPC_STATUS_PERMISSION_DENIED, "test status",
                                nullptr);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
   // cleanup
   grpc_slice_unref(details);
@@ -281,14 +279,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
   // Request a call on the server
   error = grpc_server_request_call(server, &s, &call_details,
-                                   &request_metadata_recv, cq, cq, tag(101));
+                                   &request_metadata_recv, cq, cq,
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
   // Since the server is configured to allow only a single ping strike, it would
   // take 3 pings to trigger the GOAWAY frame with "too_many_pings" from the
@@ -297,7 +296,7 @@
   // GOAWAY.) If the client settings match with the server's settings, there
   // won't be a bad ping, and the call will end due to the deadline expiring
   // instead.
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   // The call will end after this
   cqv.Verify(grpc_core::Duration::Seconds(60));
   // cleanup
@@ -656,14 +655,15 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
-                                nullptr);
+  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(1), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
   error = grpc_server_request_call(server, &s, &call_details,
-                                   &request_metadata_recv, cq, cq, tag(101));
+                                   &request_metadata_recv, cq, cq,
+                                   grpc_core::CqVerifier::tag(101));
   GPR_ASSERT(GRPC_CALL_OK == error);
-  cqv.Expect(tag(101), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(101), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -673,11 +673,11 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(102), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(102), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(102), true);
   cqv.Verify();
 
   memset(ops, 0, sizeof(ops));
@@ -700,12 +700,12 @@
   op->flags = 0;
   op->reserved = nullptr;
   op++;
-  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
-                                nullptr);
+  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
+                                grpc_core::CqVerifier::tag(103), nullptr);
   GPR_ASSERT(GRPC_CALL_OK == error);
 
-  cqv.Expect(tag(103), true);
-  cqv.Expect(tag(1), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(103), true);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify();
 
   GPR_ASSERT(status == GRPC_STATUS_OK);
@@ -767,12 +767,12 @@
   grpc_core::CqVerifier cqv(cq);
   // Channel should be able to send two pings without disconnect if there was no
   // BDP sent.
-  grpc_channel_ping(channel, cq, tag(1), nullptr);
-  cqv.Expect(tag(1), true);
+  grpc_channel_ping(channel, cq, grpc_core::CqVerifier::tag(1), nullptr);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify(grpc_core::Duration::Seconds(5));
   // Second ping
-  grpc_channel_ping(channel, cq, tag(2), nullptr);
-  cqv.Expect(tag(2), true);
+  grpc_channel_ping(channel, cq, grpc_core::CqVerifier::tag(2), nullptr);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify(grpc_core::Duration::Seconds(5));
   ASSERT_EQ(grpc_channel_check_connectivity_state(channel, 0),
             GRPC_CHANNEL_READY);
@@ -782,12 +782,12 @@
   // The call with a response payload should have triggered a BDP ping.
   // Send two more pings to verify. The second ping should cause a disconnect.
   // If BDP was not sent, the second ping would not cause a disconnect.
-  grpc_channel_ping(channel, cq, tag(3), nullptr);
-  cqv.Expect(tag(3), true);
+  grpc_channel_ping(channel, cq, grpc_core::CqVerifier::tag(3), nullptr);
+  cqv.Expect(grpc_core::CqVerifier::tag(3), true);
   cqv.Verify(grpc_core::Duration::Seconds(5));
   // Second ping
-  grpc_channel_ping(channel, cq, tag(4), nullptr);
-  cqv.Expect(tag(4), true);
+  grpc_channel_ping(channel, cq, grpc_core::CqVerifier::tag(4), nullptr);
+  cqv.Expect(grpc_core::CqVerifier::tag(4), true);
   cqv.Verify(grpc_core::Duration::Seconds(5));
   // Make sure that the transports have been destroyed
   VerifyChannelDisconnected(channel, cq);
@@ -840,16 +840,16 @@
   EXPECT_EQ(TransportCounter::count(), 2 /* one each for server and client */);
   grpc_core::CqVerifier cqv(cq);
   // First ping
-  grpc_channel_ping(channel, cq, tag(1), nullptr);
-  cqv.Expect(tag(1), true);
+  grpc_channel_ping(channel, cq, grpc_core::CqVerifier::tag(1), nullptr);
+  cqv.Expect(grpc_core::CqVerifier::tag(1), true);
   cqv.Verify(grpc_core::Duration::Seconds(5));
   // Second ping
-  grpc_channel_ping(channel, cq, tag(2), nullptr);
-  cqv.Expect(tag(2), true);
+  grpc_channel_ping(channel, cq, grpc_core::CqVerifier::tag(2), nullptr);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify(grpc_core::Duration::Seconds(5));
   // Third ping caused disconnect
-  grpc_channel_ping(channel, cq, tag(2), nullptr);
-  cqv.Expect(tag(2), true);
+  grpc_channel_ping(channel, cq, grpc_core::CqVerifier::tag(2), nullptr);
+  cqv.Expect(grpc_core::CqVerifier::tag(2), true);
   cqv.Verify(grpc_core::Duration::Seconds(5));
   // Make sure that the transports have been destroyed
   VerifyChannelDisconnected(channel, cq);
diff --git a/test/core/transport/parsed_metadata_test.cc b/test/core/transport/parsed_metadata_test.cc
index 387226b..3bc263c 100644
--- a/test/core/transport/parsed_metadata_test.cc
+++ b/test/core/transport/parsed_metadata_test.cc
@@ -41,6 +41,9 @@
     return slice[0];
   }
   static std::string DisplayValue(char value) { return std::string(1, value); }
+  static std::string DisplayMemento(MementoType memento) {
+    return DisplayValue(memento);
+  }
 };
 
 struct Int32Trait {
@@ -58,6 +61,9 @@
   static std::string DisplayValue(int32_t value) {
     return std::to_string(value);
   }
+  static std::string DisplayMemento(MementoType memento) {
+    return DisplayValue(memento);
+  }
 };
 
 struct Int64Trait {
@@ -75,6 +81,9 @@
   static std::string DisplayValue(int64_t value) {
     return std::to_string(value);
   }
+  static std::string DisplayMemento(MementoType memento) {
+    return DisplayValue(memento);
+  }
 };
 
 struct IntptrTrait {
@@ -92,6 +101,9 @@
   static std::string DisplayValue(intptr_t value) {
     return std::to_string(value);
   }
+  static std::string DisplayMemento(MementoType memento) {
+    return DisplayValue(memento);
+  }
 };
 
 struct StringTrait {
@@ -108,6 +120,9 @@
     return std::string(view.begin(), view.end());
   }
   static std::string DisplayValue(const std::string& value) { return value; }
+  static std::string DisplayMemento(MementoType memento) {
+    return DisplayValue(memento);
+  }
 };
 
 class FakeContainer {
@@ -208,16 +223,17 @@
 TEST(KeyValueTest, Simple) {
   using PM = ParsedMetadata<grpc_metadata_batch>;
   using PMPtr = std::unique_ptr<PM>;
-  PMPtr p = std::make_unique<PM>(Slice::FromCopiedString("key"),
-                                 Slice::FromCopiedString("value"));
+  PMPtr p =
+      std::make_unique<PM>(PM::FromSlicePair{}, Slice::FromCopiedString("key"),
+                           Slice::FromCopiedString("value"), 40);
   EXPECT_EQ(p->DebugString(), "key: value");
   EXPECT_EQ(p->transport_size(), 40);
-  PM p2 = p->WithNewValue(Slice::FromCopiedString("some_other_value"),
-                          [](absl::string_view msg, const Slice& value) {
-                            ASSERT_TRUE(false)
-                                << "Should not be called: msg=" << msg
-                                << ", value=" << value.as_string_view();
-                          });
+  PM p2 = p->WithNewValue(
+      Slice::FromCopiedString("some_other_value"), strlen("some_other_value"),
+      [](absl::string_view msg, const Slice& value) {
+        ASSERT_TRUE(false) << "Should not be called: msg=" << msg
+                           << ", value=" << value.as_string_view();
+      });
   EXPECT_EQ(p->DebugString(), "key: value");
   EXPECT_EQ(p2.DebugString(), "key: some_other_value");
   EXPECT_EQ(p2.transport_size(), 51);
@@ -232,18 +248,19 @@
 TEST(KeyValueTest, LongKey) {
   using PM = ParsedMetadata<grpc_metadata_batch>;
   using PMPtr = std::unique_ptr<PM>;
-  PMPtr p = std::make_unique<PM>(Slice::FromCopiedString(std::string(60, 'a')),
-                                 Slice::FromCopiedString("value"));
+  PMPtr p = std::make_unique<PM>(PM::FromSlicePair{},
+                                 Slice::FromCopiedString(std::string(60, 'a')),
+                                 Slice::FromCopiedString("value"), 60 + 5 + 32);
   EXPECT_EQ(
       p->DebugString(),
       "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: value");
   EXPECT_EQ(p->transport_size(), 97);
-  PM p2 = p->WithNewValue(Slice::FromCopiedString("some_other_value"),
-                          [](absl::string_view msg, const Slice& value) {
-                            ASSERT_TRUE(false)
-                                << "Should not be called: msg=" << msg
-                                << ", value=" << value.as_string_view();
-                          });
+  PM p2 = p->WithNewValue(
+      Slice::FromCopiedString("some_other_value"), strlen("some_other_value"),
+      [](absl::string_view msg, const Slice& value) {
+        ASSERT_TRUE(false) << "Should not be called: msg=" << msg
+                           << ", value=" << value.as_string_view();
+      });
   EXPECT_EQ(
       p->DebugString(),
       "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: value");
diff --git a/test/core/util/parse_hexstring.cc b/test/core/util/parse_hexstring.cc
index 66b135a..5fb5876 100644
--- a/test/core/util/parse_hexstring.cc
+++ b/test/core/util/parse_hexstring.cc
@@ -21,17 +21,18 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <grpc/slice.h>
 #include <grpc/support/log.h>
 
-grpc_slice parse_hexstring(const char* hexstring) {
+namespace grpc_core {
+Slice ParseHexstring(absl::string_view hexstring) {
   size_t nibbles = 0;
-  const char* p = nullptr;
   uint8_t* out;
   uint8_t temp;
   grpc_slice slice;
 
-  for (p = hexstring; *p; p++) {
-    nibbles += (*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f');
+  for (auto c : hexstring) {
+    nibbles += (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
   }
 
   GPR_ASSERT((nibbles & 1) == 0);
@@ -41,13 +42,13 @@
 
   nibbles = 0;
   temp = 0;
-  for (p = hexstring; *p; p++) {
-    if (*p >= '0' && *p <= '9') {
-      temp = static_cast<uint8_t>(temp << 4) | static_cast<uint8_t>(*p - '0');
+  for (auto c : hexstring) {
+    if (c >= '0' && c <= '9') {
+      temp = static_cast<uint8_t>(temp << 4) | static_cast<uint8_t>(c - '0');
       nibbles++;
-    } else if (*p >= 'a' && *p <= 'f') {
+    } else if (c >= 'a' && c <= 'f') {
       temp =
-          static_cast<uint8_t>(temp << 4) | static_cast<uint8_t>(*p - 'a' + 10);
+          static_cast<uint8_t>(temp << 4) | static_cast<uint8_t>(c - 'a' + 10);
       nibbles++;
     }
     if (nibbles == 2) {
@@ -56,5 +57,6 @@
     }
   }
 
-  return slice;
+  return Slice(slice);
 }
+}  // namespace grpc_core
diff --git a/test/core/util/parse_hexstring.h b/test/core/util/parse_hexstring.h
index b342578..6870bfc 100644
--- a/test/core/util/parse_hexstring.h
+++ b/test/core/util/parse_hexstring.h
@@ -19,8 +19,12 @@
 #ifndef GRPC_TEST_CORE_UTIL_PARSE_HEXSTRING_H
 #define GRPC_TEST_CORE_UTIL_PARSE_HEXSTRING_H
 
-#include <grpc/slice.h>
+#include "absl/strings/string_view.h"
 
-grpc_slice parse_hexstring(const char* hexstring);
+#include "src/core/lib/slice/slice.h"
+
+namespace grpc_core {
+Slice ParseHexstring(absl::string_view hexstring);
+}
 
 #endif  // GRPC_TEST_CORE_UTIL_PARSE_HEXSTRING_H
diff --git a/test/cpp/end2end/grpclb_end2end_test.cc b/test/cpp/end2end/grpclb_end2end_test.cc
index 9a0b86d..9861e40 100644
--- a/test/cpp/end2end/grpclb_end2end_test.cc
+++ b/test/cpp/end2end/grpclb_end2end_test.cc
@@ -101,8 +101,8 @@
 using BackendService = CountedService<TestServiceImpl>;
 using BalancerService = CountedService<LoadBalancer::Service>;
 
-const char g_kCallCredsMdKey[] = "Balancer should not ...";
-const char g_kCallCredsMdValue[] = "... receive me";
+const char g_kCallCredsMdKey[] = "call-creds";
+const char g_kCallCredsMdValue[] = "should not be received by balancer";
 
 // A test user agent string sent by the client only to the grpclb loadbalancer.
 // The backend should not see this user-agent string.
diff --git a/test/cpp/end2end/rls_end2end_test.cc b/test/cpp/end2end/rls_end2end_test.cc
index c6045c2..862982b 100644
--- a/test/cpp/end2end/rls_end2end_test.cc
+++ b/test/cpp/end2end/rls_end2end_test.cc
@@ -100,7 +100,7 @@
                     ::testing::Pair(kCallCredsMdKey, kCallCredsMdValue)));
     IncreaseRequestCount();
     auto client_metadata = context->client_metadata();
-    auto range = client_metadata.equal_range("X-Google-RLS-Data");
+    auto range = client_metadata.equal_range("x-google-rls-data");
     {
       grpc::internal::MutexLock lock(&mu_);
       for (auto it = range.first; it != range.second; ++it) {
diff --git a/test/cpp/microbenchmarks/bm_chttp2_hpack.cc b/test/cpp/microbenchmarks/bm_chttp2_hpack.cc
index 4163b7e..f24810c 100644
--- a/test/cpp/microbenchmarks/bm_chttp2_hpack.cc
+++ b/test/cpp/microbenchmarks/bm_chttp2_hpack.cc
@@ -349,6 +349,7 @@
   grpc_core::ManualConstructor<grpc_metadata_batch> b;
   b.Init(arena);
   p.BeginFrame(&*b, std::numeric_limits<uint32_t>::max(),
+               std::numeric_limits<uint32_t>::max(),
                grpc_core::HPackParser::Boundary::None,
                grpc_core::HPackParser::Priority::None,
                grpc_core::HPackParser::LogInfo{
@@ -371,6 +372,7 @@
       arena = grpc_core::Arena::Create(kArenaSize, &memory_allocator);
       b.Init(arena);
       p.BeginFrame(&*b, std::numeric_limits<uint32_t>::max(),
+                   std::numeric_limits<uint32_t>::max(),
                    grpc_core::HPackParser::Boundary::None,
                    grpc_core::HPackParser::Priority::None,
                    grpc_core::HPackParser::LogInfo{
diff --git a/tools/codegen/core/gen_header_frame.py b/tools/codegen/core/gen_header_frame.py
index 4545f3e..a501330 100755
--- a/tools/codegen/core/gen_header_frame.py
+++ b/tools/codegen/core/gen_header_frame.py
@@ -24,36 +24,80 @@
 import sys
 
 
-def append_never_indexed(payload_line, n, count, key, value):
+def append_never_indexed(payload_line, n, count, key, value, value_is_huff):
     payload_line.append(0x10)
     assert (len(key) <= 126)
     payload_line.append(len(key))
     payload_line.extend(ord(c) for c in key)
     assert (len(value) <= 126)
-    payload_line.append(len(value))
-    payload_line.extend(ord(c) for c in value)
+    payload_line.append(len(value) + (0x80 if value_is_huff else 0))
+    payload_line.extend(value)
 
 
-def append_inc_indexed(payload_line, n, count, key, value):
+def append_inc_indexed(payload_line, n, count, key, value, value_is_huff):
     payload_line.append(0x40)
     assert (len(key) <= 126)
     payload_line.append(len(key))
     payload_line.extend(ord(c) for c in key)
     assert (len(value) <= 126)
-    payload_line.append(len(value))
-    payload_line.extend(ord(c) for c in value)
+    payload_line.append(len(value) + (0x80 if value_is_huff else 0))
+    payload_line.extend(value)
 
 
-def append_pre_indexed(payload_line, n, count, key, value):
+def append_pre_indexed(payload_line, n, count, key, value, value_is_huff):
+    assert not value_is_huff
     payload_line.append(0x80 + 61 + count - n)
 
 
+def esc_c(line):
+    out = "\""
+    last_was_hex = False
+    for c in line:
+        if 32 <= c < 127:
+            if c in hex_bytes and last_was_hex:
+                out += "\"\""
+            if c != ord('"'):
+                out += chr(c)
+            else:
+                out += "\\\""
+            last_was_hex = False
+        else:
+            out += "\\x%02x" % c
+            last_was_hex = True
+    return out + "\""
+
+
+def output_c(payload_bytes):
+    for line in payload_bytes:
+        print((esc_c(line)))
+
+
+def output_hex(payload_bytes):
+    all_bytes = []
+    for line in payload_bytes:
+        all_bytes.extend(line)
+    print(('{%s}' % ', '.join('0x%02x' % c for c in all_bytes)))
+
+
+def output_hexstr(payload_bytes):
+    all_bytes = []
+    for line in payload_bytes:
+        all_bytes.extend(line)
+    print(('%s' % ''.join('%02x' % c for c in all_bytes)))
+
+
 _COMPRESSORS = {
     'never': append_never_indexed,
     'inc': append_inc_indexed,
     'pre': append_pre_indexed,
 }
 
+_OUTPUTS = {
+    'c': output_c,
+    'hex': output_hex,
+    'hexstr': output_hexstr,
+}
+
 argp = argparse.ArgumentParser('Generate header frames')
 argp.add_argument('--set_end_stream',
                   default=False,
@@ -66,7 +110,8 @@
 argp.add_argument('--compression',
                   choices=sorted(_COMPRESSORS.keys()),
                   default='never')
-argp.add_argument('--hex', default=False, action='store_const', const=True)
+argp.add_argument('--huff', default=False, action='store_const', const=True)
+argp.add_argument('--output', default='c', choices=sorted(_OUTPUTS.keys()))
 args = argp.parse_args()
 
 # parse input, fill in vals
@@ -79,7 +124,13 @@
         continue
     key_tail, value = line[1:].split(':')
     key = (line[0] + key_tail).strip()
-    value = value.strip()
+    value = value.strip().encode('ascii')
+    if args.huff:
+        from hpack.huffman import HuffmanEncoder
+        from hpack.huffman_constants import REQUEST_CODES
+        from hpack.huffman_constants import REQUEST_CODES_LENGTH
+        value = HuffmanEncoder(REQUEST_CODES,
+                               REQUEST_CODES_LENGTH).encode(value)
     vals.append((key, value))
 
 # generate frame payload binary data
@@ -90,7 +141,8 @@
 n = 0
 for key, value in vals:
     payload_line = []
-    _COMPRESSORS[args.compression](payload_line, n, len(vals), key, value)
+    _COMPRESSORS[args.compression](payload_line, n, len(vals), key, value,
+                                   args.huff)
     n += 1
     payload_len += len(payload_line)
     payload_bytes.append(payload_line)
@@ -117,31 +169,5 @@
 
 hex_bytes = [ord(c) for c in "abcdefABCDEF0123456789"]
 
-
-def esc_c(line):
-    out = "\""
-    last_was_hex = False
-    for c in line:
-        if 32 <= c < 127:
-            if c in hex_bytes and last_was_hex:
-                out += "\"\""
-            if c != ord('"'):
-                out += chr(c)
-            else:
-                out += "\\\""
-            last_was_hex = False
-        else:
-            out += "\\x%02x" % c
-            last_was_hex = True
-    return out + "\""
-
-
 # dump bytes
-if args.hex:
-    all_bytes = []
-    for line in payload_bytes:
-        all_bytes.extend(line)
-    print(('{%s}' % ', '.join('0x%02x' % c for c in all_bytes)))
-else:
-    for line in payload_bytes:
-        print((esc_c(line)))
+_OUTPUTS[args.output](payload_bytes)
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index abeaaeb..afd184b 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -1987,6 +1987,8 @@
 src/core/lib/avl/avl.h \
 src/core/lib/backoff/backoff.cc \
 src/core/lib/backoff/backoff.h \
+src/core/lib/backoff/random_early_detection.cc \
+src/core/lib/backoff/random_early_detection.h \
 src/core/lib/channel/call_finalization.h \
 src/core/lib/channel/call_tracer.h \
 src/core/lib/channel/channel_args.cc \
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 9f561b2..3fe2d1c 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -1764,6 +1764,8 @@
 src/core/lib/avl/avl.h \
 src/core/lib/backoff/backoff.cc \
 src/core/lib/backoff/backoff.h \
+src/core/lib/backoff/random_early_detection.cc \
+src/core/lib/backoff/random_early_detection.h \
 src/core/lib/channel/README.md \
 src/core/lib/channel/call_finalization.h \
 src/core/lib/channel/call_tracer.h \
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index b28e728..9bdaa6e 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -176,30 +176,6 @@
     "flaky": false,
     "gtest": false,
     "language": "c",
-    "name": "inproc_callback_test",
-    "platforms": [
-      "linux",
-      "mac",
-      "posix",
-      "windows"
-    ],
-    "uses_polling": false
-  },
-  {
-    "args": [],
-    "benchmark": false,
-    "ci_platforms": [
-      "linux",
-      "mac",
-      "posix",
-      "windows"
-    ],
-    "cpu_cost": 1.0,
-    "exclude_configs": [],
-    "exclude_iomgrs": [],
-    "flaky": false,
-    "gtest": false,
-    "language": "c",
     "name": "invalid_call_argument_test",
     "platforms": [
       "linux",