This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 9969df0d7a Align juneau-rest-client family tests to repo conventions; 
clarify MCP duplex Mcp-Name contract; add HTT-aware coverage waiver to 
coverage.py
9969df0d7a is described below

commit 9969df0d7a74449a7e51eaa08c2c718874144ce0
Author: James Bognar <[email protected]>
AuthorDate: Mon Aug 10 21:04:08 2026 -0400

    Align juneau-rest-client family tests to repo conventions; clarify MCP 
duplex Mcp-Name contract; add HTT-aware coverage waiver to coverage.py
    
    - juneau-rest-client(+mcp/-v20250618/-v20260728): extend TestBase, 
AtomicBoolean->Flag, assertThrows->assertThrowsWithMessage; add 
juneau-test/juneau-test-utils test deps (TODO-322)
    - McpClient (v20260728): clarify that Mcp-Name is empty by design on the 
mcp/clientResult duplex return channel (TODO-327)
    - scripts/coverage.py: honor // HTT coverage-waiver markers (exclude from 
missed/uncovered counts, surface reasons)
---
 .../juneau-rest-client-mcp-v20250618/pom.xml       |  12 ++
 .../rest/client/mcp/v20250618/McpClient_Test.java  |  10 +-
 .../juneau-rest-client-mcp-v20260728/pom.xml       |  12 ++
 .../rest/client/mcp/v20260728/McpClient.java       |  11 +-
 .../mcp/v20260728/ElicitationRequests_Test.java    |   8 +-
 .../mcp/v20260728/ElicitationResponses_Test.java   |  20 +--
 .../McpClientSubscriptionsListen_Test.java         |   3 +-
 .../mcp/v20260728/McpClient_CacheHints_Test.java   |   3 +-
 .../mcp/v20260728/McpClient_CallRaw_Test.java      |   3 +-
 .../mcp/v20260728/McpClient_Capabilities_Test.java |   3 +-
 .../mcp/v20260728/McpClient_Complete_Test.java     |   3 +-
 .../mcp/v20260728/McpClient_Connect_Test.java      |  29 ++--
 .../mcp/v20260728/McpClient_Elicitation_Test.java  |  29 ++--
 .../v20260728/McpClient_ErrorHandling_Test.java    |   7 +-
 .../mcp/v20260728/McpClient_MetaHeaders_Test.java  |   3 +-
 .../mcp/v20260728/McpClient_Methods_Test.java      |   3 +-
 .../mcp/v20260728/McpClient_Surface_Test.java      |   3 +-
 .../mcp/v20260728/McpClient_TraceContext_Test.java |   3 +-
 .../mcp/v20260728/McpDuplexChannel_Test.java       |  16 +-
 .../v20260728/McpSubscriptionListener_Test.java    |   3 +-
 juneau-rest/juneau-rest-client-mcp/pom.xml         |  12 ++
 .../rest/client/mcp/AbstractMcpClient_Test.java    |  84 +++++------
 .../client/mcp/InMemoryMcpResponseCache_Test.java  |   3 +-
 .../rest/client/mcp/McpAuthInterceptor_Test.java   |  10 +-
 .../rest/client/mcp/McpDuplexDispatcher_Test.java  |  11 +-
 juneau-rest/juneau-rest-client/pom.xml             |  12 ++
 .../juneau/rest/client/JavaHttpTransport_Test.java |  11 +-
 .../juneau/rest/client/RecordStreamBody_Test.java  |   3 +-
 .../juneau/rest/client/RedirectSecurity_Test.java  |   3 +-
 .../juneau/rest/client/RemoteClient_Test.java      |   3 +-
 .../juneau/rest/client/ResponseAssertion_Test.java |   3 +-
 .../client/ResponseBody_AsEventStream_Test.java    |  31 ++--
 .../rest/client/ResponseBody_Cursor_Test.java      |   3 +-
 .../rest/client/RestClient_Negotiation_Test.java   |   3 +-
 .../client/RestRequest_OpenEventStream_Test.java   |  21 +--
 .../juneau/rest/client/RestRequest_Run_Test.java   |  19 +--
 scripts/coverage.py                                | 162 ++++++++++++++++++---
 37 files changed, 388 insertions(+), 190 deletions(-)

diff --git a/juneau-rest/juneau-rest-client-mcp-v20250618/pom.xml 
b/juneau-rest/juneau-rest-client-mcp-v20250618/pom.xml
index 86a857be4d..7c330c9828 100644
--- a/juneau-rest/juneau-rest-client-mcp-v20250618/pom.xml
+++ b/juneau-rest/juneau-rest-client-mcp-v20250618/pom.xml
@@ -51,6 +51,18 @@
                        <version>${junit.version}</version>
                        <scope>test</scope>
                </dependency>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-test</artifactId>
+                       <version>${project.version}</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-test-utils</artifactId>
+                       <version>${project.version}</version>
+                       <scope>test</scope>
+               </dependency>
        </dependencies>
 
        <build>
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20250618/src/test/java/org/apache/juneau/rest/client/mcp/v20250618/McpClient_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20250618/src/test/java/org/apache/juneau/rest/client/mcp/v20250618/McpClient_Test.java
index cfdbcb4f0a..10ffc60f00 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20250618/src/test/java/org/apache/juneau/rest/client/mcp/v20250618/McpClient_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20250618/src/test/java/org/apache/juneau/rest/client/mcp/v20250618/McpClient_Test.java
@@ -16,6 +16,7 @@
  */
 package org.apache.juneau.rest.client.mcp.v20250618;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.*;
@@ -23,6 +24,7 @@ import java.nio.charset.*;
 import java.util.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.rest.client.*;
@@ -32,7 +34,7 @@ import org.junit.jupiter.api.*;
  * Unit tests for {@link McpClient}.
  */
 @SuppressWarnings("resource") // mock transports/clients are in-memory no-op 
closeables; test bodies close what matters via try-with-resources.
-class McpClient_Test {
+class McpClient_Test extends TestBase {
 
        private static String bodyOf(TransportRequest req) {
                try {
@@ -93,9 +95,8 @@ class McpClient_Test {
        void a03_initialize_errorResponse_throwsMcpException() throws Exception 
{
                var wire = 
"{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601,\"message\":\"Method 
not found: initialize\"}}";
                try (var client = client(ok(wire))) {
-                       var e = assertThrows(McpException.class, 
client::initialize);
+                       var e = assertThrowsWithMessage(McpException.class, 
"Method not found: initialize", client::initialize);
                        assertEquals(-32601, e.getCode());
-                       assertEquals("Method not found: initialize", 
e.getMessage());
                }
        }
 
@@ -204,9 +205,8 @@ class McpClient_Test {
        void d03_callTool_unknownTool_throwsMcpException() throws Exception {
                var wire = 
"{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-32601,\"message\":\"Tool 
not found: no-such-tool\"}}";
                try (var client = client(ok(wire))) {
-                       var e = assertThrows(McpException.class, () -> 
client.callTool("no-such-tool", Map.of()));
+                       var e = assertThrowsWithMessage(McpException.class, 
"Tool not found: no-such-tool", () -> client.callTool("no-such-tool", 
Map.of()));
                        assertEquals(-32601, e.getCode());
-                       assertEquals("Tool not found: no-such-tool", 
e.getMessage());
                }
        }
 
diff --git a/juneau-rest/juneau-rest-client-mcp-v20260728/pom.xml 
b/juneau-rest/juneau-rest-client-mcp-v20260728/pom.xml
index f31f67c5c4..64a8386e9e 100644
--- a/juneau-rest/juneau-rest-client-mcp-v20260728/pom.xml
+++ b/juneau-rest/juneau-rest-client-mcp-v20260728/pom.xml
@@ -65,6 +65,18 @@
                        <version>${junit.version}</version>
                        <scope>test</scope>
                </dependency>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-test</artifactId>
+                       <version>${project.version}</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-test-utils</artifactId>
+                       <version>${project.version}</version>
+                       <scope>test</scope>
+               </dependency>
        </dependencies>
 
        <build>
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/main/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/main/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient.java
index 984505e86e..ccb5631683 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/main/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/main/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient.java
@@ -872,10 +872,13 @@ public final class McpClient extends AbstractMcpClient {
                }
        }
 
-       // C5/C6 duplex return-channel header contract remains unsettled: 
Mcp-Name is always empty here because
-       // DUPLEX_RETURN_METHOD has no routing-name mapping in McpRoutingNames. 
It is unclear whether the real
-       // contract instead wants it to echo the correlated inbound request's 
tool/prompt/resource name; that
-       // requires spec clarification and is deliberately left open rather 
than guessed at here.
+       // C5/C6 duplex return-channel Mcp-Name contract (TODO-327, resolved): 
Mcp-Name is empty by design on this
+       // channel. Per SEP-2243, Mcp-Name is a body-mirror routing header 
defined only for the three forward
+       // client->server named methods (tools/call, prompts/get, 
resources/read); the spec does not define it for
+       // server->client requests or return channels. So McpRoutingNames 
intentionally has no case for
+       // DUPLEX_RETURN_METHOD (mcp/clientResult) - it falls to the default 
empty arm - and the response envelope has
+       // no name/uri to mirror anyway. If correlating a server-initiated 
request back to its originating client
+       // request is ever needed, key it on the JSON-RPC id (SEP-2260 
relatedRequestId), not on Mcp-Name.
        //
        // payload must be routed through toWireParams(...) exactly like every 
other outbound envelope (see #call):
        // AbstractMcpClient.send(...) posts through restClient, whose 
serializer is JsonSerializer.DEFAULT
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/ElicitationRequests_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/ElicitationRequests_Test.java
index be3b50602e..0f91272f39 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/ElicitationRequests_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/ElicitationRequests_Test.java
@@ -16,17 +16,19 @@
  */
 package org.apache.juneau.rest.client.mcp.v20260728;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.marshall.collections.*;
 import org.junit.jupiter.api.*;
 
 /**
  * Coverage for {@link ElicitationRequests}.
  */
-class ElicitationRequests_Test {
+class ElicitationRequests_Test extends TestBase {
 
        @Test void a01_isInputRequired_trueOnInputRequiredResultType() {
                
assertTrue(ElicitationRequests.isInputRequired(Map.of("resultType", 
"input_required")));
@@ -37,8 +39,8 @@ class ElicitationRequests_Test {
        }
 
        @Test void a03_isInputRequired_nullRawThrows() {
-               var e = assertThrows(IllegalArgumentException.class, () -> 
ElicitationRequests.isInputRequired(null));
-               assertEquals("Argument 'raw' cannot be null.", e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'raw' cannot be null.",
+                       () -> ElicitationRequests.isInputRequired(null));
        }
 
        @Test void a04_requests_decodesTypedElicitRequestsFromRawMap() {
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/ElicitationResponses_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/ElicitationResponses_Test.java
index 5900f34b41..f9e8d75863 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/ElicitationResponses_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/ElicitationResponses_Test.java
@@ -16,10 +16,12 @@
  */
 package org.apache.juneau.rest.client.mcp.v20260728;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.apache.juneau.marshall.collections.*;
 import org.junit.jupiter.api.*;
@@ -27,7 +29,7 @@ import org.junit.jupiter.api.*;
 /**
  * Coverage for {@link ElicitationResponses}.
  */
-class ElicitationResponses_Test {
+class ElicitationResponses_Test extends TestBase {
 
        @Test void a01_toInputResponse_singleAnswer_encodesAcceptWithContent() {
                var result = new 
ElicitResult().setAction(ElicitAction.ACCEPT).putContent("choice", "red");
@@ -54,26 +56,26 @@ class ElicitationResponses_Test {
 
        @Test void a03_toInputResponse_nullIdThrows() {
                var result = new ElicitResult();
-               var e = assertThrows(IllegalArgumentException.class, () -> 
ElicitationResponses.toInputResponse(null, result));
-               assertEquals("Argument 'id' cannot be null.", e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'id' cannot be null.",
+                       () -> ElicitationResponses.toInputResponse(null, 
result));
        }
 
        @Test void a04_toInputResponse_nullResultThrows() {
-               var e = assertThrows(IllegalArgumentException.class, () -> 
ElicitationResponses.toInputResponse("q1", null));
-               assertEquals("Argument 'result' cannot be null.", 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'result' cannot be null.",
+                       () -> ElicitationResponses.toInputResponse("q1", null));
        }
 
        @Test void a05_toInputResponses_nullMapThrows() {
-               var e = assertThrows(IllegalArgumentException.class, () -> 
ElicitationResponses.toInputResponses(null));
-               assertEquals("Argument 'results' cannot be null.", 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'results' cannot be null.",
+                       () -> ElicitationResponses.toInputResponses(null));
        }
 
        @Test void a06_toInputResponses_nullEntryThrows() {
                var results = new LinkedHashMap<String,ElicitResult>();
                results.put("q1", new 
ElicitResult().setAction(ElicitAction.ACCEPT));
                results.put("q2", null);
-               var e = assertThrows(IllegalArgumentException.class, () -> 
ElicitationResponses.toInputResponses(results));
-               assertEquals("Argument 'results[q2]' cannot be null.", 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'results[q2]' cannot be null.",
+                       () -> ElicitationResponses.toInputResponses(results));
        }
 
        /**
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClientSubscriptionsListen_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClientSubscriptionsListen_Test.java
index 80d025e669..61f2d8d46f 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClientSubscriptionsListen_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClientSubscriptionsListen_Test.java
@@ -24,6 +24,7 @@ import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.rest.client.*;
@@ -32,7 +33,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambdas and McpSubscriptionHandle test 
doubles are exercised/closed via cancel()/close() or the try-with-resources 
client elsewhere in each test; suppressing resource-leak noise class-wide 
rather than per call site.
 })
-class McpClientSubscriptionsListen_Test {
+class McpClientSubscriptionsListen_Test extends TestBase {
 
        @Test
        void 
a01_listen_singleStream_deliversAckThenNotificationsThenComplete_inOrder() 
throws Exception {
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_CacheHints_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_CacheHints_Test.java
index 05ba6ac815..726e25fd75 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_CacheHints_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_CacheHints_Test.java
@@ -23,6 +23,7 @@ import java.nio.charset.*;
 import java.util.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.rest.client.*;
 import org.apache.juneau.rest.client.mcp.*;
 import org.junit.jupiter.api.*;
@@ -30,7 +31,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambdas are short-lived test fixtures 
whose clients are already closed via try-with-resources at each call site.
 })
-class McpClient_CacheHints_Test {
+class McpClient_CacheHints_Test extends TestBase {
 
        static final class RecordingCache implements McpResponseCache {
                String lastScope;
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_CallRaw_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_CallRaw_Test.java
index 874c49a318..334491e10d 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_CallRaw_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_CallRaw_Test.java
@@ -23,6 +23,7 @@ import java.nio.charset.*;
 import java.util.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.apache.juneau.rest.client.*;
@@ -37,7 +38,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambdas and the client(...) 
test-helper factory (@Owning; callers close via try-with-resources) are 
short-lived test fixtures.
 })
-class McpClient_CallRaw_Test {
+class McpClient_CallRaw_Test extends TestBase {
 
        private static HttpTransport ok(String wireJson) {
                return tReq -> TransportResponse.builder()
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Capabilities_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Capabilities_Test.java
index ccf18d867b..e67946f4e8 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Capabilities_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Capabilities_Test.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.io.*;
 import java.nio.charset.*;
 import java.util.concurrent.atomic.*;
+import org.apache.juneau.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.apache.juneau.rest.client.*;
 import org.junit.jupiter.api.*;
@@ -28,7 +29,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambda is a short-lived test fixture 
whose client is already closed via try-with-resources.
 })
-class McpClient_Capabilities_Test {
+class McpClient_Capabilities_Test extends TestBase {
 
        @Test
        void a01_builderCapabilities_areStampedIntoRequestMeta() throws 
Exception {
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Complete_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Complete_Test.java
index e521dd2f68..e461969c5c 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Complete_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Complete_Test.java
@@ -22,6 +22,7 @@ import java.io.*;
 import java.nio.charset.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.apache.juneau.rest.client.*;
 import org.junit.jupiter.api.*;
@@ -29,7 +30,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambda is a short-lived test fixture 
whose client is already closed via try-with-resources.
 })
-class McpClient_Complete_Test {
+class McpClient_Complete_Test extends TestBase {
 
        @Test
        void a01_complete_serializesRefTypeDiscriminator() throws Exception {
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Connect_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Connect_Test.java
index b7baff5cee..9ee6599904 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Connect_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Connect_Test.java
@@ -16,12 +16,15 @@
  */
 package org.apache.juneau.rest.client.mcp.v20260728;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.*;
 import java.nio.charset.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
+import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.rest.client.*;
 import org.junit.jupiter.api.*;
 
@@ -35,7 +38,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport (including the deliberately-`failing` 
implementations used to pin close-failure/suppressed-exception behavior) and 
countingTransport(...) instances are short-lived test fixtures; some are 
intentionally unassigned/never closed since these tests pin 
McpClient.connect(...)'s own close behavior, not the mock transport's.
 })
-class McpClient_Connect_Test {
+class McpClient_Connect_Test extends TestBase {
 
        private static final String DISCOVER_WIRE =
                
"{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"result\":{\"supportedVersions\":[\"2026-07-28\"],\"capabilities\":{}}}";
@@ -96,20 +99,19 @@ class McpClient_Connect_Test {
 
        @Test
        void b01_connect_closesClientAndPropagatesOnHandshakeFailure() {
-               var closed = new AtomicBoolean();
+               var closed = Flag.create();
                HttpTransport failing = new HttpTransport() {
                        @Override public TransportResponse 
execute(TransportRequest request) throws TransportException {
                                throw new TransportException("boom");
                        }
 
                        @Override public void close() {
-                               closed.set(true);
+                               closed.set();
                        }
                };
                var builder = 
McpClient.builder().endpoint("http://x/mcp";).transport(failing);
-               var ex = assertThrows(IOException.class, () -> 
McpClient.connect(builder));
-               assertEquals("boom", ex.getMessage());
-               assertTrue(closed.get());
+               assertThrowsWithMessage(IOException.class, "boom", () -> 
McpClient.connect(builder));
+               assertTrue(closed.isSet());
        }
 
        @Test
@@ -124,8 +126,7 @@ class McpClient_Connect_Test {
                        }
                };
                var builder = 
McpClient.builder().endpoint("http://x/mcp";).transport(failing);
-               var ex = assertThrows(IOException.class, () -> 
McpClient.connect(builder));
-               assertEquals("boom", ex.getMessage());
+               var ex = assertThrowsWithMessage(IOException.class, "boom", () 
-> McpClient.connect(builder));
                assertEquals(1, ex.getSuppressed().length);
                assertEquals("close-boom", ex.getSuppressed()[0].getMessage());
        }
@@ -142,28 +143,26 @@ class McpClient_Connect_Test {
                        }
                };
                var builder = 
McpClient.builder().endpoint("http://x/mcp";).transport(failing);
-               var ex = assertThrows(IOException.class, () -> 
McpClient.connect(builder));
-               assertEquals("boom", ex.getMessage());
+               var ex = assertThrowsWithMessage(IOException.class, "boom", () 
-> McpClient.connect(builder));
                assertEquals(1, ex.getSuppressed().length);
                assertEquals("close-boom", ex.getSuppressed()[0].getMessage());
        }
 
        @Test
        void b04_connect_closesClientAndPropagatesOnUncheckedHandshakeError() {
-               var closed = new AtomicBoolean();
+               var closed = Flag.create();
                HttpTransport failing = new HttpTransport() {
                        @Override public TransportResponse 
execute(TransportRequest request) {
                                throw new StackOverflowError("boom");
                        }
 
                        @Override public void close() {
-                               closed.set(true);
+                               closed.set();
                        }
                };
                var builder = 
McpClient.builder().endpoint("http://x/mcp";).transport(failing);
-               var err = assertThrows(StackOverflowError.class, () -> 
McpClient.connect(builder));
-               assertEquals("boom", err.getMessage());
-               assertTrue(closed.get());
+               assertThrowsWithMessage(StackOverflowError.class, "boom", () -> 
McpClient.connect(builder));
+               assertTrue(closed.isSet());
        }
 
        @Test
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Elicitation_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Elicitation_Test.java
index 3910f051ca..66409a8448 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Elicitation_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Elicitation_Test.java
@@ -16,6 +16,7 @@
  */
 package org.apache.juneau.rest.client.mcp.v20260728;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.*;
@@ -23,8 +24,10 @@ import java.nio.charset.*;
 import java.util.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
+import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.rest.client.*;
 import org.junit.jupiter.api.*;
@@ -40,7 +43,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // The Recorder HttpTransport test double (`t`) and the 
client(...) test-helper factory (@Owning; callers close via try-with-resources) 
are short-lived test fixtures.
 })
-class McpClient_Elicitation_Test {
+class McpClient_Elicitation_Test extends TestBase {
 
        /**
         * A stub transport that returns a fixed sequence of canned JSON-RPC 
wire responses (the last one repeats for
@@ -187,15 +190,15 @@ class McpClient_Elicitation_Test {
 
        @Test void 
a06_callTool_noPause_terminalResultReturnedWithoutInvokingHandler() throws 
Exception {
                var t = new Recorder(completeTool("done"));
-               var invoked = new AtomicBoolean();
+               var invoked = Flag.create();
                try (var c = client(t)) {
                        var result = c.callToolWithElicitation("ask", null, 
requests -> {
-                               invoked.set(true);
+                               invoked.set();
                                return acceptAll().elicit(requests);
                        });
                        assertEquals("done", ((TextContent) 
result.getContent().get(0)).getText());
                }
-               assertFalse(invoked.get());
+               assertFalse(invoked.isSet());
                assertEquals(1, t.requests.size());
        }
 
@@ -282,40 +285,38 @@ class McpClient_Elicitation_Test {
 
        @Test void d01_nullHandlerThrows() throws Exception {
                try (var c = client(new Recorder(completeTool("done")))) {
-                       var e = assertThrows(IllegalArgumentException.class, () 
-> c.callToolWithElicitation("ask", null, null));
-                       assertEquals("Argument 'handler' cannot be null.", 
e.getMessage());
+                       assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'handler' cannot be null.",
+                               () -> c.callToolWithElicitation("ask", null, 
null));
                }
        }
 
        @Test void d02_nonPositiveMaxRoundsThrows() throws Exception {
                try (var c = client(new Recorder(completeTool("done")))) {
                        var handler = acceptAll();
-                       var eZero = 
assertThrows(IllegalArgumentException.class, () -> 
c.callToolWithElicitation("ask", null, handler, 0));
-                       assertEquals("maxRounds must be >= 1 (was 0).", 
eZero.getMessage());
-                       var eNegative = 
assertThrows(IllegalArgumentException.class, () -> 
c.callToolWithElicitation("ask", null, handler, -1));
-                       assertEquals("maxRounds must be >= 1 (was -1).", 
eNegative.getMessage());
+                       assertThrowsWithMessage(IllegalArgumentException.class, 
"maxRounds must be >= 1 (was 0).",
+                               () -> c.callToolWithElicitation("ask", null, 
handler, 0));
+                       assertThrowsWithMessage(IllegalArgumentException.class, 
"maxRounds must be >= 1 (was -1).",
+                               () -> c.callToolWithElicitation("ask", null, 
handler, -1));
                }
        }
 
        @Test void d03_handlerReturnsNullResultThrows() throws Exception {
                var t = new Recorder(inputRequired("tok1", "q1"), 
completeTool("done"));
                try (var c = client(t)) {
-                       var e = assertThrows(IllegalArgumentException.class,
+                       assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'handler result' cannot be null.",
                                () -> c.callToolWithElicitation("ask", null, 
requests -> null));
-                       assertEquals("Argument 'handler result' cannot be 
null.", e.getMessage());
                }
        }
 
        @Test void d04_handlerReturnsMapWithNullValueForRequestedIdThrows() 
throws Exception {
                var t = new Recorder(inputRequired("tok1", "q1"), 
completeTool("done"));
                try (var c = client(t)) {
-                       var e = assertThrows(IllegalArgumentException.class,
+                       assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'results[q1]' cannot be null.",
                                () -> c.callToolWithElicitation("ask", null, 
requests -> {
                                        var out = new 
LinkedHashMap<String,ElicitResult>();
                                        out.put("q1", null);
                                        return out;
                                }));
-                       assertEquals("Argument 'results[q1]' cannot be null.", 
e.getMessage());
                }
        }
 
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_ErrorHandling_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_ErrorHandling_Test.java
index 8d7903f13d..7baa61ba86 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_ErrorHandling_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_ErrorHandling_Test.java
@@ -16,10 +16,12 @@
  */
 package org.apache.juneau.rest.client.mcp.v20260728;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.*;
 import java.nio.charset.*;
+import org.apache.juneau.*;
 import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.rest.client.*;
 import org.junit.jupiter.api.*;
@@ -27,7 +29,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambda is a short-lived test fixture 
whose client is already closed via try-with-resources.
 })
-class McpClient_ErrorHandling_Test {
+class McpClient_ErrorHandling_Test extends TestBase {
 
        @Test
        void a01_jsonRpcError_throwsMcpException() throws Exception {
@@ -40,9 +42,8 @@ class McpClient_ErrorHandling_Test {
                                .build();
                };
                try (var c = 
McpClient.builder().endpoint("http://x/mcp";).transport(transport).build()) {
-                       var ex = assertThrows(McpException.class, c::ping);
+                       var ex = assertThrowsWithMessage(McpException.class, 
"Method not found", c::ping);
                        assertEquals(-32601, ex.getCode());
-                       assertTrue(ex.getMessage().contains("Method not 
found"));
                }
        }
 }
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_MetaHeaders_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_MetaHeaders_Test.java
index 333c42500d..56ca49cf9b 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_MetaHeaders_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_MetaHeaders_Test.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.io.*;
 import java.nio.charset.*;
 import java.util.concurrent.atomic.*;
+import org.apache.juneau.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.apache.juneau.rest.client.*;
 import org.junit.jupiter.api.*;
@@ -28,7 +29,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambda is a short-lived test fixture 
whose client is already closed via try-with-resources.
 })
-class McpClient_MetaHeaders_Test {
+class McpClient_MetaHeaders_Test extends TestBase {
 
        @Test
        void a01_resourcesRead_stampsMetaAndHeaders() throws Exception {
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Methods_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Methods_Test.java
index 68b66199b2..e53eafeac7 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Methods_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Methods_Test.java
@@ -23,6 +23,7 @@ import java.nio.charset.*;
 import java.util.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.apache.juneau.rest.client.*;
 import org.apache.juneau.rest.client.mcp.*;
@@ -31,7 +32,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambdas and the ok(...)/client(...) 
test-helper factories (@Owning; callers close via try-with-resources) are 
short-lived test fixtures.
 })
-class McpClient_Methods_Test {
+class McpClient_Methods_Test extends TestBase {
 
        private static HttpTransport ok(String wireJson) {
                return tReq -> TransportResponse.builder()
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Surface_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Surface_Test.java
index 896928625a..2785a406c6 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Surface_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_Surface_Test.java
@@ -18,10 +18,11 @@ package org.apache.juneau.rest.client.mcp.v20260728;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.junit.jupiter.api.*;
 
-class McpClient_Surface_Test {
+class McpClient_Surface_Test extends TestBase {
 
        @Test
        void a01_surface_hasServerDiscoverAndNoInitialize() throws Exception {
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_TraceContext_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_TraceContext_Test.java
index e9a5de23fc..522f7e35dc 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_TraceContext_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpClient_TraceContext_Test.java
@@ -22,6 +22,7 @@ import java.io.*;
 import java.nio.charset.*;
 import java.util.*;
 import java.util.concurrent.atomic.*;
+import org.apache.juneau.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.apache.juneau.rest.client.*;
 import org.apache.juneau.rest.client.mcp.*;
@@ -30,7 +31,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambdas are short-lived test fixtures 
whose clients are already closed via try-with-resources.
 })
-class McpClient_TraceContext_Test {
+class McpClient_TraceContext_Test extends TestBase {
 
        @Test
        void a01_traceFieldsSupplier_stampsRequestMeta() throws Exception {
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpDuplexChannel_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpDuplexChannel_Test.java
index 517278ee45..c384ad3830 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpDuplexChannel_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpDuplexChannel_Test.java
@@ -22,8 +22,10 @@ import java.io.*;
 import java.nio.charset.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
+import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.marshall.collections.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.marshall.marshaller.Json;
@@ -33,15 +35,15 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Mock HttpTransport lambdas (`transport`) and the client 
`c` are short-lived test fixtures; `c` is deliberately left open (no 
try-with-resources) across these tests since only pumpNextServerMessage()'s 
single-shot duplex behavior is under test, not client lifecycle.
 })
-class McpDuplexChannel_Test {
+class McpDuplexChannel_Test extends TestBase {
 
        @Test
        void 
a01_openDuplexChannel_dispatchesOpaqueRequest_andPostsRawResponse() throws 
Exception {
                var inbound = "data: 
{\"jsonrpc\":\"2.0\",\"id\":\"42\",\"method\":\"sampling/createMessage\",\"params\":{\"name\":\"opaque\",\"experimental\":{\"x\":1}}}\n\n";
                var posted = new AtomicReference<String>();
-               var first = new AtomicBoolean(true);
+               var first = Flag.of(true);
                HttpTransport transport = tReq -> {
-                       if (first.getAndSet(false))
+                       if (first.getAndUnset())
                                return 
TransportResponse.builder().statusCode(200).header("Content-Type", 
"text/event-stream")
                                        .body(new 
ByteArrayInputStream(inbound.getBytes(StandardCharsets.UTF_8))).build();
                        try {
@@ -73,9 +75,9 @@ class McpDuplexChannel_Test {
        void a02_handlerFailure_postsJsonRpcErrorEnvelope() throws Exception {
                var inbound = "data: 
{\"jsonrpc\":\"2.0\",\"id\":\"77\",\"method\":\"sampling/createMessage\",\"params\":{\"name\":\"opaque\"}}\n\n";
                var posted = new AtomicReference<String>();
-               var first = new AtomicBoolean(true);
+               var first = Flag.of(true);
                HttpTransport transport = tReq -> {
-                       if (first.getAndSet(false))
+                       if (first.getAndUnset())
                                return 
TransportResponse.builder().statusCode(200).header("Content-Type", 
"text/event-stream")
                                        .body(new 
ByteArrayInputStream(inbound.getBytes(StandardCharsets.UTF_8))).build();
                        try {
@@ -112,9 +114,9 @@ class McpDuplexChannel_Test {
                var inbound = "data: 
{\"jsonrpc\":\"2.0\",\"id\":\"99\",\"method\":\"" + 
McpMethods.SAMPLING_CREATE_MESSAGE + "\",\"params\":"
                        + wireJson + "}\n\n";
                var posted = new AtomicReference<String>();
-               var first = new AtomicBoolean(true);
+               var first = Flag.of(true);
                HttpTransport transport = tReq -> {
-                       if (first.getAndSet(false))
+                       if (first.getAndUnset())
                                return 
TransportResponse.builder().statusCode(200).header("Content-Type", 
"text/event-stream")
                                        .body(new 
ByteArrayInputStream(inbound.getBytes(StandardCharsets.UTF_8))).build();
                        try {
diff --git 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpSubscriptionListener_Test.java
 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpSubscriptionListener_Test.java
index 20a277cc5b..fcb108427f 100644
--- 
a/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpSubscriptionListener_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp-v20260728/src/test/java/org/apache/juneau/rest/client/mcp/v20260728/McpSubscriptionListener_Test.java
@@ -18,10 +18,11 @@ package org.apache.juneau.rest.client.mcp.v20260728;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.mcp.v20260728.*;
 import org.junit.jupiter.api.*;
 
-class McpSubscriptionListener_Test {
+class McpSubscriptionListener_Test extends TestBase {
 
        @Test
        void a01_defaultMethods_areNoOpsAndDoNotThrow() {
diff --git a/juneau-rest/juneau-rest-client-mcp/pom.xml 
b/juneau-rest/juneau-rest-client-mcp/pom.xml
index 29d1ae15a3..15c90d88fd 100644
--- a/juneau-rest/juneau-rest-client-mcp/pom.xml
+++ b/juneau-rest/juneau-rest-client-mcp/pom.xml
@@ -51,6 +51,18 @@
                        <version>${junit.version}</version>
                        <scope>test</scope>
                </dependency>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-test</artifactId>
+                       <version>${project.version}</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-test-utils</artifactId>
+                       <version>${project.version}</version>
+                       <scope>test</scope>
+               </dependency>
        </dependencies>
 
        <build>
diff --git 
a/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/AbstractMcpClient_Test.java
 
b/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/AbstractMcpClient_Test.java
index 93a94606af..ae351220e2 100644
--- 
a/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/AbstractMcpClient_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/AbstractMcpClient_Test.java
@@ -16,6 +16,7 @@
  */
 package org.apache.juneau.rest.client.mcp;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.*;
@@ -23,7 +24,9 @@ import java.nio.charset.*;
 import java.util.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.jsonrpc.*;
+import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.marshall.sse.*;
 import org.apache.juneau.rest.client.*;
 import org.junit.jupiter.api.*;
@@ -32,7 +35,7 @@ import org.junit.jupiter.api.*;
  * Unit tests for {@link AbstractMcpClient#send(JsonRpcRequest)}.
  */
 @SuppressWarnings("resource") // mock transports/clients are in-memory no-op 
closeables; test bodies close what matters via try-with-resources.
-class AbstractMcpClient_Test {
+class AbstractMcpClient_Test extends TestBase {
 
        /** Minimal concrete subclass so the abstract neutral core can be 
instantiated for testing. */
        static class TestClient extends AbstractMcpClient {
@@ -159,51 +162,51 @@ class AbstractMcpClient_Test {
 
        @Test
        void a06_send_closesTransportResponse_onAllPaths() throws Exception {
-               var successClosed = new AtomicBoolean();
+               var successClosed = Flag.create();
                HttpTransport successTransport = tReq -> 
TransportResponse.builder()
                        .statusCode(200)
                        .header("Content-Type", "application/json")
                        .body(new 
ByteArrayInputStream("{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"result\":{}}".getBytes(StandardCharsets.UTF_8)))
-                       .closeCallback(() -> successClosed.set(true))
+                       .closeCallback(successClosed::set)
                        .build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(successTransport).build())
 {
                        client.send(new 
JsonRpcRequest().setJsonrpc("2.0").setId("1").setMethod("ping"));
                }
-               assertTrue(successClosed.get());
+               assertTrue(successClosed.isSet());
 
-               var errorClosed = new AtomicBoolean();
+               var errorClosed = Flag.create();
                HttpTransport errorTransport = tReq -> 
TransportResponse.builder()
                        .statusCode(500)
                        .header("Content-Type", "application/json")
                        .body(new 
ByteArrayInputStream("{\"jsonrpc\":\"2.0\",\"id\":\"2\",\"error\":{\"code\":-32000,\"message\":\"boom\"}}".getBytes(StandardCharsets.UTF_8)))
-                       .closeCallback(() -> errorClosed.set(true))
+                       .closeCallback(errorClosed::set)
                        .build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(errorTransport).build())
 {
                        client.send(new 
JsonRpcRequest().setJsonrpc("2.0").setId("2").setMethod("ping"));
                }
-               assertTrue(errorClosed.get());
+               assertTrue(errorClosed.isSet());
 
-               var notificationClosed = new AtomicBoolean();
+               var notificationClosed = Flag.create();
                HttpTransport notificationTransport = tReq -> 
TransportResponse.builder()
                        .statusCode(202)
-                       .closeCallback(() -> notificationClosed.set(true))
+                       .closeCallback(notificationClosed::set)
                        .build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(notificationTransport).build())
 {
                        client.send(new 
JsonRpcRequest().setJsonrpc("2.0").setMethod("notifications/x"));
                }
-               assertTrue(notificationClosed.get());
+               assertTrue(notificationClosed.isSet());
 
-               var parseFailureClosed = new AtomicBoolean();
+               var parseFailureClosed = Flag.create();
                HttpTransport parseFailureTransport = tReq -> 
TransportResponse.builder()
                        .statusCode(200)
                        .header("Content-Type", "application/json")
                        .body(new 
ByteArrayInputStream("{".getBytes(StandardCharsets.UTF_8)))
-                       .closeCallback(() -> parseFailureClosed.set(true))
+                       .closeCallback(parseFailureClosed::set)
                        .build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(parseFailureTransport).build())
 {
                        assertThrows(IOException.class, () -> client.send(new 
JsonRpcRequest().setJsonrpc("2.0").setId("3").setMethod("ping")));
                }
-               assertTrue(parseFailureClosed.get());
+               assertTrue(parseFailureClosed.isSet());
        }
 
        @Test
@@ -214,8 +217,8 @@ class AbstractMcpClient_Test {
                        .body(new ByteArrayInputStream("server 
down".getBytes(StandardCharsets.UTF_8)))
                        .build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(transport).build()) {
-                       var e = assertThrows(IOException.class, () -> 
client.send(new 
JsonRpcRequest().setJsonrpc("2.0").setId("7").setMethod("ping")));
-                       assertEquals("MCP server returned HTTP 500 and the body 
was not a JSON-RPC envelope.", e.getMessage());
+                       assertThrowsWithMessage(IOException.class, "MCP server 
returned HTTP 500 and the body was not a JSON-RPC envelope.",
+                               () -> client.send(new 
JsonRpcRequest().setJsonrpc("2.0").setId("7").setMethod("ping")));
                }
        }
 
@@ -243,8 +246,7 @@ class AbstractMcpClient_Test {
                        .build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(transport).build()) {
                        var req = new 
JsonRpcRequest().setJsonrpc("2.0").setId("9").setMethod("ping");
-                       var e = assertThrows(IOException.class, () -> 
client.send(req));
-                       assertEquals("No response body received for JSON-RPC 
request id '9' (HTTP 200).", e.getMessage());
+                       assertThrowsWithMessage(IOException.class, "No response 
body received for JSON-RPC request id '9' (HTTP 200).", () -> client.send(req));
                }
        }
 
@@ -291,8 +293,7 @@ class AbstractMcpClient_Test {
                        .build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(transport).build()) {
                        var req = new 
JsonRpcRequest().setJsonrpc("2.0").setId("12").setMethod("ping");
-                       var e = assertThrows(IOException.class, () -> 
client.send(req));
-                       assertTrue(e.getMessage().contains("MCP server returned 
HTTP 199"));
+                       assertThrowsWithMessage(IOException.class, "MCP server 
returned HTTP 199", () -> client.send(req));
                }
        }
 
@@ -360,8 +361,7 @@ class AbstractMcpClient_Test {
        void c02_openEventStream_nullRequest_throwsIllegalArgumentException() 
throws Exception {
                HttpTransport transport = tReq -> 
TransportResponse.builder().statusCode(200).build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(transport).build()) {
-                       var e = assertThrows(IllegalArgumentException.class, () 
-> client.openStream(null));
-                       assertEquals("Argument 'request' cannot be null.", 
e.getMessage());
+                       assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'request' cannot be null.", () -> client.openStream(null));
                }
        }
 
@@ -434,9 +434,8 @@ class AbstractMcpClient_Test {
                        .build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(transport).build()) {
                        var req = new 
JsonRpcRequest().setJsonrpc("2.0").setId("1").setMethod("subscriptions/listen");
-                       var e = assertThrows(McpException.class, () -> 
client.openStream(req));
+                       var e = assertThrowsWithMessage(McpException.class, 
"subscriptions/listen requires Accept: text/event-stream", () -> 
client.openStream(req));
                        assertEquals(-32600, e.getCode());
-                       assertEquals("subscriptions/listen requires Accept: 
text/event-stream", e.getMessage());
                }
        }
 
@@ -453,9 +452,8 @@ class AbstractMcpClient_Test {
                        .build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(transport).build()) {
                        var req = new 
JsonRpcRequest().setJsonrpc("2.0").setId("1").setMethod("subscriptions/listen");
-                       var e = assertThrows(McpException.class, () -> 
client.openStream(req));
+                       var e = assertThrowsWithMessage(McpException.class, 
"Too many concurrent subscriptions", () -> client.openStream(req));
                        assertEquals(-32000, e.getCode());
-                       assertEquals("Too many concurrent subscriptions", 
e.getMessage());
                }
        }
 
@@ -480,60 +478,52 @@ class AbstractMcpClient_Test {
        void 
c05_openEventStream_headersOverload_nullRequest_throwsIllegalArgumentException()
 throws Exception {
                HttpTransport transport = tReq -> 
TransportResponse.builder().statusCode(200).build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(transport).build()) {
-                       var e = assertThrows(IllegalArgumentException.class, () 
-> client.openStream(null, Map.of()));
-                       assertEquals("Argument 'request' cannot be null.", 
e.getMessage());
+                       assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'request' cannot be null.", () -> client.openStream(null, Map.of()));
                }
        }
 
        @Test
        void b01_constructor_nullBuilder_throwsIllegalArgumentException() {
-               var e = assertThrows(IllegalArgumentException.class, () -> new 
TestClient(null));
-               assertEquals("Argument 'builder' cannot be null.", 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'builder' cannot be null.", () -> new TestClient(null));
        }
 
        @Test
        void b02_builder_endpoint_null_throwsIllegalArgumentException() {
                var builder = TestClient.builder();
-               var e = assertThrows(IllegalArgumentException.class, () -> 
builder.endpoint(null));
-               assertEquals("Argument 'endpoint' cannot be null.", 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'endpoint' cannot be null.", () -> builder.endpoint(null));
        }
 
        @Test
        void b03_builder_noEndpoint_throwsIllegalArgumentException() {
                var builder = TestClient.builder();
-               var e = assertThrows(IllegalArgumentException.class, () -> 
builder.build());
-               assertEquals("Argument 'endpoint' cannot be null.", 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'endpoint' cannot be null.", builder::build);
        }
 
        @Test
        void b04_builder_endpoint_blank_throwsIllegalArgumentException() {
                var builder = TestClient.builder();
-               var e = assertThrows(IllegalArgumentException.class, () -> 
builder.endpoint(""));
-               assertEquals("Argument 'endpoint' cannot be blank.", 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'endpoint' cannot be blank.", () -> builder.endpoint(""));
        }
 
        /** A non-http(s)-scheme endpoint is rejected at construction, not just 
non-blank. */
        @Test
        void b08_build_ftpSchemeEndpoint_throwsIllegalArgumentException() {
                var builder = TestClient.builder().endpoint("ftp://x/mcp";);
-               var e = assertThrows(IllegalArgumentException.class, () -> 
builder.build());
-               assertTrue(e.getMessage().contains("http or https"), 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, "http 
or https", builder::build);
        }
 
        /** A scheme-less (relative) endpoint is rejected at construction. */
        @Test
        void b09_build_schemelessEndpoint_throwsIllegalArgumentException() {
                var builder = TestClient.builder().endpoint("x/mcp");
-               var e = assertThrows(IllegalArgumentException.class, () -> 
builder.build());
-               assertTrue(e.getMessage().contains("http or https"), 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, "http 
or https", builder::build);
        }
 
        /** A syntactically malformed endpoint is rejected at construction with 
a clear message. */
        @Test
        void b10_build_malformedEndpoint_throwsIllegalArgumentException() {
                var builder = TestClient.builder().endpoint("http://x y/mcp");
-               var e = assertThrows(IllegalArgumentException.class, () -> 
builder.build());
-               assertTrue(e.getMessage().contains("Invalid MCP endpoint URL"), 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Invalid MCP endpoint URL", builder::build);
        }
 
        /** https is accepted alongside http. */
@@ -549,8 +539,7 @@ class AbstractMcpClient_Test {
        void b05_send_nullRequest_throwsIllegalArgumentException() throws 
Exception {
                HttpTransport transport = tReq -> 
TransportResponse.builder().statusCode(200).build();
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(transport).build()) {
-                       var e = assertThrows(IllegalArgumentException.class, () 
-> client.send(null));
-                       assertEquals("Argument 'request' cannot be null.", 
e.getMessage());
+                       assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'request' cannot be null.", () -> client.send(null));
                }
        }
 
@@ -569,7 +558,7 @@ class AbstractMcpClient_Test {
 
        @Test
        void b07_builder_interceptor_allowsFluentChaining() throws Exception {
-               var called = new AtomicBoolean();
+               var called = Flag.create();
                HttpTransport transport = tReq -> TransportResponse.builder()
                        .statusCode(200)
                        .header("Content-Type", "application/json")
@@ -578,20 +567,19 @@ class AbstractMcpClient_Test {
                var interceptor = new RestCallInterceptor() {
                        @Override
                        public void onInit(RestRequest req) {
-                               called.set(true);
+                               called.set();
                        }
                };
                try (var client = 
TestClient.builder().endpoint("http://x/mcp";).transport(transport).interceptor(interceptor).build())
 {
                        client.send(new 
JsonRpcRequest().setJsonrpc("2.0").setId("1").setMethod("ping"));
-                       assertTrue(called.get());
+                       assertTrue(called.isSet());
                }
        }
 
        @Test
        void b08_builder_interceptor_null_throwsIllegalArgumentException() {
                var builder = TestClient.builder();
-               var e = assertThrows(IllegalArgumentException.class, () -> 
builder.interceptor(null));
-               assertEquals("Argument 'interceptor' cannot be null.", 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'interceptor' cannot be null.", () -> builder.interceptor(null));
        }
 
 }
diff --git 
a/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/InMemoryMcpResponseCache_Test.java
 
b/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/InMemoryMcpResponseCache_Test.java
index 73740acd03..037113338f 100644
--- 
a/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/InMemoryMcpResponseCache_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/InMemoryMcpResponseCache_Test.java
@@ -20,13 +20,14 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.jsonrpc.*;
 import org.junit.jupiter.api.*;
 
 /**
  * Unit tests for {@link InMemoryMcpResponseCache}.
  */
-class InMemoryMcpResponseCache_Test {
+class InMemoryMcpResponseCache_Test extends TestBase {
 
        // 
==========================================================================
        // a — basic get/put
diff --git 
a/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/McpAuthInterceptor_Test.java
 
b/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/McpAuthInterceptor_Test.java
index 265a8aa679..f66838b59a 100644
--- 
a/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/McpAuthInterceptor_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/McpAuthInterceptor_Test.java
@@ -20,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
+import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.rest.client.*;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.params.*;
@@ -29,7 +31,7 @@ import org.junit.jupiter.params.provider.*;
  * Unit tests for {@link McpAuthInterceptor}.
  */
 @SuppressWarnings("resource") // mock transports are in-memory no-op 
closeables; test bodies close the RestClient/RestResponse that matters via 
try-with-resources.
-class McpAuthInterceptor_Test {
+class McpAuthInterceptor_Test extends TestBase {
 
        @Test
        void a01_onInit_setsAuthorizationHeader_fromTokenSupplier() throws 
Exception {
@@ -73,9 +75,9 @@ class McpAuthInterceptor_Test {
        @NullSource
        @ValueSource(strings = {"", "   "})
        void a03_onInit_blankToken_omitsAuthorizationHeader(String token) 
throws Exception {
-               var sawHeader = new AtomicBoolean(true);
+               var sawHeader = Flag.of(true);
                HttpTransport transport = tReq -> {
-                       sawHeader.set(tReq.getFirstHeader("Authorization") != 
null);
+                       if (tReq.getFirstHeader("Authorization") != null) 
sawHeader.set(); else sawHeader.unset();
                        return 
TransportResponse.builder().statusCode(204).build();
                };
                var interceptor = new McpAuthInterceptor(() -> token);
@@ -84,7 +86,7 @@ class McpAuthInterceptor_Test {
                                assertEquals(204, res.getStatusCode());
                        }
                }
-               assertFalse(sawHeader.get());
+               assertFalse(sawHeader.isSet());
        }
 
        @Test
diff --git 
a/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/McpDuplexDispatcher_Test.java
 
b/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/McpDuplexDispatcher_Test.java
index 9eecbc487a..4fd040e949 100644
--- 
a/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/McpDuplexDispatcher_Test.java
+++ 
b/juneau-rest/juneau-rest-client-mcp/src/test/java/org/apache/juneau/rest/client/mcp/McpDuplexDispatcher_Test.java
@@ -16,16 +16,18 @@
  */
 package org.apache.juneau.rest.client.mcp;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.commons.inject.BasicBeanStore;
 import org.apache.juneau.marshall.collections.*;
 import org.junit.jupiter.api.*;
 
-class McpDuplexDispatcher_Test {
+class McpDuplexDispatcher_Test extends TestBase {
 
        @Test
        void a01_dispatch_passesRawRequestWithoutTypedProjection() {
@@ -89,17 +91,14 @@ class McpDuplexDispatcher_Test {
                        throw new IllegalStateException("boom");
                });
                var req = new 
JsonRpcRequest().setJsonrpc("2.0").setId("8").setMethod("sampling/createMessage").setParams(JsonMap.of());
-               var e = assertThrows(McpException.class, () -> d.dispatch(req, 
BasicBeanStore.INSTANCE));
+               var e = assertThrowsWithMessage(McpException.class, 
"sampling/createMessage", () -> d.dispatch(req, BasicBeanStore.INSTANCE));
                assertEquals(-32603, e.getCode());
-               assertNotNull(e.getMessage());
-               assertTrue(e.getMessage().contains("sampling/createMessage"));
                assertInstanceOf(IllegalStateException.class, e.getCause());
        }
 
        @Test
        void a06_dispatch_nullRequest_throwsIllegalArgumentException() {
                var d = new McpDuplexDispatcher();
-               var e = assertThrows(IllegalArgumentException.class, () -> 
d.dispatch(null, BasicBeanStore.INSTANCE));
-               assertEquals("Argument 'request' cannot be null.", 
e.getMessage());
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'request' cannot be null.", () -> d.dispatch(null, 
BasicBeanStore.INSTANCE));
        }
 }
diff --git a/juneau-rest/juneau-rest-client/pom.xml 
b/juneau-rest/juneau-rest-client/pom.xml
index b1f86a8e38..e3278855a3 100644
--- a/juneau-rest/juneau-rest-client/pom.xml
+++ b/juneau-rest/juneau-rest-client/pom.xml
@@ -46,6 +46,18 @@
                        <version>${junit.version}</version>
                        <scope>test</scope>
                </dependency>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-test</artifactId>
+                       <version>${project.version}</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-test-utils</artifactId>
+                       <version>${project.version}</version>
+                       <scope>test</scope>
+               </dependency>
        </dependencies>
 
        <build>
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/JavaHttpTransport_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/JavaHttpTransport_Test.java
index ffed9944c8..338523c4ea 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/JavaHttpTransport_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/JavaHttpTransport_Test.java
@@ -27,10 +27,11 @@ import java.security.cert.*;
 import java.time.*;
 import java.util.*;
 import java.util.concurrent.*;
-import java.util.concurrent.atomic.*;
 
 import javax.net.ssl.*;
 
+import org.apache.juneau.*;
+import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.http.entity.*;
 import org.junit.jupiter.api.*;
 
@@ -42,7 +43,7 @@ import com.sun.net.httpserver.*;
 @SuppressWarnings({
        "resource" // Transport/client instances are short-lived test fixtures; 
closing is irrelevant to these assertions.
 })
-class JavaHttpTransport_Test {
+class JavaHttpTransport_Test extends TestBase {
 
        private static HttpServer server;
        private static int port;
@@ -287,11 +288,11 @@ class JavaHttpTransport_Test {
 
        @Test
        void g01_close_closesUnderlyingJdkBodyStream() throws Exception {
-               var bodyClosed = new AtomicBoolean();
+               var bodyClosed = Flag.create();
                var body = new 
ByteArrayInputStream("ignored".getBytes(StandardCharsets.UTF_8)) {
                        @Override /* ByteArrayInputStream */
                        public void close() throws IOException {
-                               bodyClosed.set(true);
+                               bodyClosed.set();
                                super.close();
                        }
                };
@@ -392,6 +393,6 @@ class JavaHttpTransport_Test {
                                assertEquals(200, res.getStatusCode());
                        }
                }
-               assertTrue(bodyClosed.get());
+               assertTrue(bodyClosed.isSet());
        }
 }
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RecordStreamBody_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RecordStreamBody_Test.java
index 76bf2e991e..3921e88ec3 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RecordStreamBody_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RecordStreamBody_Test.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.io.*;
 import java.nio.charset.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.marshall.plaintext.*;
 import org.junit.jupiter.api.*;
@@ -28,7 +29,7 @@ import org.junit.jupiter.api.*;
 /**
  * Unit tests for the next-generation {@link RecordStreamBody} streaming 
request body.
  */
-class RecordStreamBody_Test {
+class RecordStreamBody_Test extends TestBase {
 
        public static class Bean {
                public String name;
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RedirectSecurity_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RedirectSecurity_Test.java
index faab670fd1..03d84c456f 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RedirectSecurity_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RedirectSecurity_Test.java
@@ -20,9 +20,10 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import java.net.*;
 
+import org.apache.juneau.*;
 import org.junit.jupiter.api.*;
 
-class RedirectSecurity_Test {
+class RedirectSecurity_Test extends TestBase {
 
        @Test void a01_sameOrigin_exactMatch_forwards() {
                var from = URI.create("https://example.com/api";);
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RemoteClient_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RemoteClient_Test.java
index 2af781b8b6..fcde9d7c6b 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RemoteClient_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RemoteClient_Test.java
@@ -23,6 +23,7 @@ import java.net.*;
 import java.nio.charset.*;
 import java.util.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.http.remote.*;
 import org.apache.juneau.rest.client.remote.*;
@@ -40,7 +41,7 @@ import com.sun.net.httpserver.*;
 @SuppressWarnings({
        "resource" // RestClient/RestResponse instances used inline; closed via 
try-with-resources where needed.
 })
-class RemoteClient_Test {
+class RemoteClient_Test extends TestBase {
 
        // 
-----------------------------------------------------------------------
        // Embedded server — shared across all tests
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseAssertion_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseAssertion_Test.java
index c4e5ad2e7b..9b0f459774 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseAssertion_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseAssertion_Test.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.io.*;
 import java.nio.charset.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.rest.client.assertion.*;
 import org.junit.jupiter.api.*;
 
@@ -36,7 +37,7 @@ import org.junit.jupiter.api.*;
        "resource",   // RestResponse is created for assertion testing; closed 
inline or not needed.
        "java:S5778", // assertThrows lambdas with chained calls; intermediate 
invocations do not throw in practice.
 })
-class ResponseAssertion_Test {
+class ResponseAssertion_Test extends TestBase {
 
        // 
-----------------------------------------------------------------------
        // Helpers
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_AsEventStream_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_AsEventStream_Test.java
index 56d67ca1f6..ea78abeb40 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_AsEventStream_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_AsEventStream_Test.java
@@ -16,14 +16,16 @@
  */
 package org.apache.juneau.rest.client;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.*;
 import java.nio.charset.*;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
+import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.marshall.sse.*;
 import org.junit.jupiter.api.*;
 
@@ -31,7 +33,7 @@ import org.junit.jupiter.api.*;
  * Unit tests for {@link ResponseBody#asEventStream()}.
  */
 @SuppressWarnings("resource") // 'tr'/TransportResponse values below are 
handed to (and closed by) the enclosing RestResponse; test helpers return a 
RestResponse the caller closes via try-with-resources.
-class ResponseBody_AsEventStream_Test {
+class ResponseBody_AsEventStream_Test extends TestBase {
 
        private static RestResponse response(String sseText, Closeable 
closeCallback) {
                return response(new 
ByteArrayInputStream(sseText.getBytes(StandardCharsets.UTF_8)), closeCallback);
@@ -84,12 +86,12 @@ class ResponseBody_AsEventStream_Test {
 
        @Test
        void b01_close_alsoClosesParentResponse() throws Exception {
-               var closed = new AtomicBoolean();
-               try (var resp = response("event: x\ndata: y\n\n", () -> 
closed.set(true))) {
+               var closed = Flag.create();
+               try (var resp = response("event: x\ndata: y\n\n", closed::set)) 
{
                        try (SseEventReader r = resp.body().asEventStream()) {
-                               assertFalse(closed.get());
+                               assertFalse(closed.isSet());
                                r.close();
-                               assertTrue(closed.get());
+                               assertTrue(closed.isSet());
                        }
                }
        }
@@ -97,18 +99,18 @@ class ResponseBody_AsEventStream_Test {
        @Test
        void c01_close_closesResponseBeforeReader() throws Exception {
                var closed = new ArrayList<String>();
-               var responseClosed = new AtomicBoolean();
-               var readerClosed = new AtomicBoolean();
+               var responseClosed = Flag.create();
+               var readerClosed = Flag.create();
                var a = new ByteArrayInputStream("event: x\ndata: 
y\n\n".getBytes(StandardCharsets.UTF_8)) {
                        @Override
                        public void close() throws IOException {
-                               if (readerClosed.compareAndSet(false, true))
+                               if (! readerClosed.getAndSet())
                                        closed.add("reader");
                                super.close();
                        }
                };
                try (var b = response(a, () -> {
-                       if (responseClosed.compareAndSet(false, true))
+                       if (! responseClosed.getAndSet())
                                closed.add("response");
                })) {
                        try (var c = b.body().asEventStream()) {
@@ -120,11 +122,11 @@ class ResponseBody_AsEventStream_Test {
 
        @Test
        void c02_close_responseCloseThrows_stillClosesReader() throws Exception 
{
-               var readerClosed = new AtomicBoolean();
+               var readerClosed = Flag.create();
                var a = new ByteArrayInputStream("event: x\ndata: 
y\n\n".getBytes(StandardCharsets.UTF_8)) {
                        @Override
                        public void close() throws IOException {
-                               readerClosed.set(true);
+                               readerClosed.set();
                                super.close();
                        }
                };
@@ -133,9 +135,8 @@ class ResponseBody_AsEventStream_Test {
                });
                var c = b.body().asEventStream();
                try {
-                       var d = assertThrows(IOException.class, c::close);
-                       assertEquals("boom", d.getMessage());
-                       assertTrue(readerClosed.get());
+                       assertThrowsWithMessage(IOException.class, "boom", 
c::close);
+                       assertTrue(readerClosed.isSet());
                } finally {
                        try {
                                c.close();
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_Cursor_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_Cursor_Test.java
index d69c5fe0af..42c8c0495e 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_Cursor_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/ResponseBody_Cursor_Test.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.io.*;
 import java.nio.charset.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.marshall.jsonl.*;
 import org.apache.juneau.marshall.stream.*;
@@ -29,7 +30,7 @@ import org.junit.jupiter.api.*;
 /**
  * Unit tests for {@link ResponseBody#asCursor(Class)} / {@link 
ResponseBody#asCursor(org.apache.juneau.marshall.parser.Parser, Class)}.
  */
-class ResponseBody_Cursor_Test {
+class ResponseBody_Cursor_Test extends TestBase {
 
        public static class Bean {
                public String name;
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestClient_Negotiation_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestClient_Negotiation_Test.java
index ff6ea3a05d..856d7b460b 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestClient_Negotiation_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestClient_Negotiation_Test.java
@@ -18,6 +18,7 @@ package org.apache.juneau.rest.client;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.marshall.jsonl.*;
 import org.apache.juneau.marshall.parser.*;
@@ -32,7 +33,7 @@ import org.junit.jupiter.api.*;
  * resolved only by an exact media-type match or by an explicitly-configured 
{@code defaultSerializer(...)} /
  * {@code defaultParser(...)}; otherwise the resolver is genuinely empty (and 
callers throw 415 / a client-side error).
  */
-class RestClient_Negotiation_Test {
+class RestClient_Negotiation_Test extends TestBase {
 
        @Test
        void a01_unconfigured_resolvesEmpty() throws Exception {
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestRequest_OpenEventStream_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestRequest_OpenEventStream_Test.java
index 0e9c0ff578..8dd046d2b3 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestRequest_OpenEventStream_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestRequest_OpenEventStream_Test.java
@@ -16,12 +16,15 @@
  */
 package org.apache.juneau.rest.client;
 
+import static org.apache.juneau.BasicTestUtils.assertThrowsWithMessage;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.*;
 import java.nio.charset.*;
 import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
+import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.marshall.sse.*;
 import org.junit.jupiter.api.*;
 
@@ -31,7 +34,7 @@ import org.junit.jupiter.api.*;
 @SuppressWarnings({
        "resource" // Test helpers return Closeables; Eclipse JDT @Owning 
warning is by design.
 })
-class RestRequest_OpenEventStream_Test {
+class RestRequest_OpenEventStream_Test extends TestBase {
 
        @Test
        void a01_get_readsEvents() throws Exception {
@@ -123,31 +126,31 @@ class RestRequest_OpenEventStream_Test {
 
        @Test
        void b01_noResponseBody_closesResponseAndThrows() throws Exception {
-               var closed = new AtomicBoolean();
+               var closed = Flag.create();
                HttpTransport transport = tReq -> TransportResponse.builder()
                        .statusCode(204)
-                       .closeCallback(() -> closed.set(true))
+                       .closeCallback(closed::set)
                        .build();
                try (var client = 
RestClient.builder().transport(transport).build()) {
-                       var a = assertThrows(IOException.class, () -> 
client.get("http://x/events";).openEventStream());
-                       assertEquals("Response has no body to open an event 
stream over.", a.getMessage());
+                       assertThrowsWithMessage(IOException.class, "Response 
has no body to open an event stream over.",
+                               () -> 
client.get("http://x/events";).openEventStream());
                }
-               assertTrue(closed.get());
+               assertTrue(closed.isSet());
        }
 
        @Test
        void b02_non2xxResponse_closesResponseAndThrows() throws Exception {
-               var closed = new AtomicBoolean();
+               var closed = Flag.create();
                HttpTransport transport = tReq -> TransportResponse.builder()
                        .statusCode(500)
                        .reasonPhrase("Internal Server Error")
                        .header("Content-Type", "application/json")
                        .body(new 
ByteArrayInputStream("{\"error\":\"nope\"}".getBytes(StandardCharsets.UTF_8)))
-                       .closeCallback(() -> closed.set(true))
+                       .closeCallback(closed::set)
                        .build();
                try (var client = 
RestClient.builder().transport(transport).build()) {
                        assertThrows(RestCallException.class, () -> 
client.get("http://x/events";).openEventStream());
                }
-               assertTrue(closed.get());
+               assertTrue(closed.isSet());
        }
 }
diff --git 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestRequest_Run_Test.java
 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestRequest_Run_Test.java
index f2d93b0172..0e12015bd8 100644
--- 
a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestRequest_Run_Test.java
+++ 
b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/RestRequest_Run_Test.java
@@ -19,15 +19,16 @@ package org.apache.juneau.rest.client;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.*;
-import java.util.concurrent.atomic.*;
 
+import org.apache.juneau.*;
+import org.apache.juneau.commons.lang.*;
 import org.junit.jupiter.api.*;
 
 /**
  * Unit tests for {@link RestRequest#run()}.
  */
 @SuppressWarnings("resource") // 'transport' lambdas build a TransportResponse 
per call; the built response is handed to (and closed by) the enclosing 
RestResponse/RestClient under test.
-class RestRequest_Run_Test {
+class RestRequest_Run_Test extends TestBase {
 
        /**
         * Ensures a thrown {@code onConnect} interceptor does not leave the 
already-assigned {@link RestResponse}
@@ -36,10 +37,10 @@ class RestRequest_Run_Test {
         */
        @Test
        void a01_throwingOnConnectInterceptor_closesTheAssignedResponse() 
throws Exception {
-               var closed = new AtomicBoolean();
+               var closed = Flag.create();
                HttpTransport transport = tReq -> TransportResponse.builder()
                        .statusCode(200)
-                       .closeCallback(() -> closed.set(true))
+                       .closeCallback(closed::set)
                        .build();
                RestCallInterceptor faulty = new RestCallInterceptor() {
                        @Override public void onConnect(RestRequest req, 
RestResponse res) throws Exception {
@@ -48,7 +49,7 @@ class RestRequest_Run_Test {
                };
                try (var client = 
RestClient.builder().transport(transport).interceptors(faulty).build()) {
                        assertThrows(TransportException.class, () -> 
client.get("http://x/";).run());
-                       assertTrue(closed.get(), "a throwing onConnect 
interceptor must not leak the assigned response");
+                       assertTrue(closed.isSet(), "a throwing onConnect 
interceptor must not leak the assigned response");
                }
        }
 
@@ -58,16 +59,16 @@ class RestRequest_Run_Test {
         */
        @Test
        void a02_successfulRun_leavesTheResponseOpenForTheCaller() throws 
Exception {
-               var closed = new AtomicBoolean();
+               var closed = Flag.create();
                HttpTransport transport = tReq -> TransportResponse.builder()
                        .statusCode(200)
-                       .closeCallback(() -> closed.set(true))
+                       .closeCallback(closed::set)
                        .build();
                try (var client = 
RestClient.builder().transport(transport).build()) {
                        try (var res = client.get("http://x/";).run()) {
-                               assertFalse(closed.get(), "run() must not close 
a response it successfully returns to the caller");
+                               assertFalse(closed.isSet(), "run() must not 
close a response it successfully returns to the caller");
                        }
                }
-               assertTrue(closed.get(), "the caller's own try-with-resources 
close must still work as normal");
+               assertTrue(closed.isSet(), "the caller's own try-with-resources 
close must still work as normal");
        }
 }
diff --git a/scripts/coverage.py b/scripts/coverage.py
index 61df5a2129..cc9130e7a5 100755
--- a/scripts/coverage.py
+++ b/scripts/coverage.py
@@ -25,6 +25,25 @@ every module's target/jacoco.exec and feeds them all -- 
together with each
 owning module's target/classes -- to the JaCoCo CLI to produce one combined,
 repo-wide report.
 
+Hard-To-Test (HTT) exclusion:
+    A source line can be marked as intentionally out of coverage scope by
+    adding a "HTT" token to a comment on that line, e.g.:
+        // HTT
+        x++;  // HTT: cannot reach on this platform
+        /* HTT */
+    The marker must be the standalone word "HTT" (word-boundaried, so it does
+    NOT match "HTTP"/"HTTPS"), and must appear inside a comment -- not inside
+    a string literal. Lines marked this way are dropped from the "Uncovered
+    lines" list and their missed/covered branch and instruction counts are
+    subtracted from the file's (and the total summary's) totals, so they no
+    longer affect the reported percentages. Any file with >=1 excluded line
+    is flagged "(HTT-adjusted)", and the excluded line numbers are printed
+    for transparency along with their reason (the text after "HTT:", or
+    "(no reason given)" for a bare marker) -- e.g.:
+        Excluded 2 hard-to-test line(s) (HTT):
+          L123: cannot reach on this platform
+          L145: (no reason given)
+
 Usage:
     ./scripts/coverage.py <path> [options]
 
@@ -154,6 +173,90 @@ def collect_jacoco_inputs() -> tuple[list[Path], 
list[Path], list[Path]]:
     return execfiles, classdirs, srcdirs
 
 
+# Hard-To-Test (HTT) exclusion marker: a standalone "HTT" word in a comment.
+# Word-boundaried so it does not match "HTTP"/"HTTPS".
+HTT_TOKEN_RE = re.compile(r"\bHTT\b")
+
+
+def extract_comment_portion(line: str) -> str | None:
+    """
+    Best-effort isolation of the comment portion of a single physical source 
line,
+    for HTT marker detection.
+
+    Heuristic only, and intentionally simple: returns the text after the first
+    '//' on the line, or (if no '//') the text inside the first '/* ... */' on
+    the line. Limitations: it does not track block-comment state across lines
+    (a "HTT" token on its own line inside a multi-line /* ... */ block, with no
+    '//' or '/*' on that same physical line, will NOT be detected), and it does
+    not know about string/char literals, so a '//' or '/*' occurring inside a
+    string literal earlier on the line would be mistaken for the start of a
+    comment. This is acceptable for the intended use (short, single-line HTT
+    annotations) but is not a real Java lexer.
+    """
+    slash_slash = line.find("//")
+    if slash_slash != -1:
+        return line[slash_slash + 2:]
+    block_start = line.find("/*")
+    if block_start != -1:
+        block_end = line.find("*/", block_start + 2)
+        end = block_end if block_end != -1 else len(line)
+        return line[block_start + 2:end]
+    return None
+
+
+def htt_marker_reason(line: str) -> str | None:
+    """
+    If the line's comment portion (see extract_comment_portion) carries a 
standalone HTT
+    marker, return its reason: the trimmed text following "HTT:" (e.g. "// 
HTT: cannot
+    reach on this platform" -> "cannot reach on this platform"), or the 
literal string
+    "(no reason given)" for a bare marker ("// HTT", "/* HTT */") or one with 
an empty
+    reason after the colon. Returns None if the line has no HTT marker at all.
+    """
+    comment = extract_comment_portion(line)
+    if comment is None:
+        return None
+    m = HTT_TOKEN_RE.search(comment)
+    if m is None:
+        return None
+    remainder = comment[m.end():].lstrip()
+    if remainder.startswith(":"):
+        reason = remainder[1:].strip()
+        if reason:
+            return reason
+    return "(no reason given)"
+
+
+def is_htt_marked(line: str) -> bool:
+    """True if the line's comment portion (see extract_comment_portion) 
contains a standalone HTT token."""
+    return htt_marker_reason(line) is not None
+
+
+def find_source_file(pkg_name: str, fname: str, srcdirs: list[Path]) -> Path | 
None:
+    """Locate the source file for a JaCoCo package+filename among the given 
src/main/java dirs."""
+    for src in srcdirs:
+        candidate = src / pkg_name / fname
+        if candidate.is_file():
+            return candidate
+    return None
+
+
+def find_htt_lines(pkg_name: str, fname: str, srcdirs: list[Path]) -> 
dict[int, str]:
+    """Return {1-based physical line number: reason} for every HTT-marked line 
in the owning source file."""
+    src_file = find_source_file(pkg_name, fname, srcdirs)
+    if src_file is None:
+        return {}
+    try:
+        lines = src_file.read_text(encoding="utf-8").splitlines()
+    except OSError:
+        return {}
+    result = {}
+    for i, text in enumerate(lines):
+        reason = htt_marker_reason(text)
+        if reason is not None:
+            result[i + 1] = reason
+    return result
+
+
 def jacoco_version() -> str:
     """Read <jacoco.plugin.version> from the root pom (fallback to a 
known-good default)."""
     pom = (REPO_ROOT / "pom.xml").read_text(encoding="utf-8")
@@ -262,8 +365,8 @@ def pct(covered, total):
     return f"{covered / total * 100:.0f}%"
 
 
-def report(xml_path: Path, pkg_filter: str, file_filter: str | None, 
branches_only: bool):  # NOSONAR python:S3776 -- Cognitive complexity is 
acceptable for XML report parsing and output formatting
-    """Parse jacoco.xml and print coverage for the matching package/file."""
+def report(xml_path: Path, pkg_filter: str, file_filter: str | None, 
branches_only: bool, srcdirs: list[Path]):  # NOSONAR python:S3776 -- Cognitive 
complexity is acceptable for XML report parsing and output formatting
+    """Parse jacoco.xml and print coverage for the matching package/file, 
excluding HTT-marked lines (see find_htt_lines)."""
     if not xml_path.exists():
         die(f"JaCoCo report not found at {xml_path}. Run with --run to 
generate it.")
 
@@ -282,8 +385,8 @@ def report(xml_path: Path, pkg_filter: str, file_filter: 
str | None, branches_on
             "Make sure the module is built and the exec file is up to date 
(use --run).")
 
     # Collect per-file data
-    files_data = []  # list of (pkg_name, fname, lines_with_issues)
-    total_mb = total_cb = total_mi = total_ci = 0
+    files_data = []  # list of (pkg_name, fname, mb, cb, mi, ci, uncovered, 
excluded_lines)
+    total_mb = total_cb = total_mi = total_ci = total_excluded = 0
 
     for pkg in matched_packages:
         pkg_name = pkg.get("name", "")
@@ -303,38 +406,58 @@ def report(xml_path: Path, pkg_filter: str, file_filter: 
str | None, branches_on
                 elif t == "INSTRUCTION":
                     mi, ci = m, c
 
-            total_mb += mb
-            total_cb += cb
-            total_mi += mi
-            total_ci += ci
+            # Hard-To-Test (HTT) exclusion: lines marked "HTT" in a comment in 
the
+            # owning source file are dropped from "uncovered", and their 
per-line
+            # counter contributions are subtracted from this file's mb/cb/mi/ci
+            # totals so the reported percentages no longer reflect them.
+            htt_lines = find_htt_lines(pkg_name, fname, srcdirs)
 
-            # Collect uncovered lines
             uncovered = []
+            excluded_lines = []
             for line in sf.findall("line"):
                 ln = int(line.get("nr", 0))
                 lmb = int(line.get("mb", 0))
+                lcb = int(line.get("cb", 0))
                 lmi = int(line.get("mi", 0))
+                lci = int(line.get("ci", 0))
+                if ln in htt_lines and (lmb or lcb or lmi or lci):
+                    mb -= lmb
+                    cb -= lcb
+                    mi -= lmi
+                    ci -= lci
+                    excluded_lines.append((ln, htt_lines[ln]))
+                    continue
                 if lmb > 0 or (not branches_only and lmi > 0):
-                    lcb = int(line.get("cb", 0))
                     uncovered.append((ln, lmb, lmb + lcb, lmi))
 
-            files_data.append((pkg_name, fname, mb, cb, mi, ci, uncovered))
+            total_mb += mb
+            total_cb += cb
+            total_mi += mi
+            total_ci += ci
+            total_excluded += len(excluded_lines)
+
+            files_data.append((pkg_name, fname, mb, cb, mi, ci, uncovered, 
excluded_lines))
 
     if not files_data:
         print("No data found for the specified path.")
         return
 
     # Print per-file results
-    for pkg_name, fname, mb, cb, mi, ci, uncovered in sorted(files_data):
+    for pkg_name, fname, mb, cb, mi, ci, uncovered, excluded_lines in 
sorted(files_data):
         branch_total = mb + cb
         instr_total = mi + ci
         branch_pct = pct(cb, branch_total)
         instr_pct = pct(ci, instr_total)
+        htt_note = "  (HTT-adjusted)" if excluded_lines else ""
         print(f"\n{'='*70}")
         print(f"  {pkg_name.replace('/', '.')}.{fname.removesuffix('.java')}")
         print(f"{'='*70}")
-        print(f"  Branches:     {bar(cb, branch_total)}  {branch_pct:>4}  
({cb}/{branch_total} covered, {mb} missed)")
-        print(f"  Instructions: {bar(ci, instr_total)}  {instr_pct:>4}  
({ci}/{instr_total} covered, {mi} missed)")
+        print(f"  Branches:     {bar(cb, branch_total)}  {branch_pct:>4}  
({cb}/{branch_total} covered, {mb} missed){htt_note}")
+        print(f"  Instructions: {bar(ci, instr_total)}  {instr_pct:>4}  
({ci}/{instr_total} covered, {mi} missed){htt_note}")
+        if excluded_lines:
+            print(f"  Excluded {len(excluded_lines)} hard-to-test line(s) 
(HTT):")
+            for ln, reason in sorted(excluded_lines):
+                print(f"    L{ln}: {reason}")
         if uncovered:
             print("\n  Uncovered lines:")
             for ln, lmb, ltotal, lmi in sorted(uncovered):
@@ -351,11 +474,15 @@ def report(xml_path: Path, pkg_filter: str, file_filter: 
str | None, branches_on
     if len(files_data) > 1:
         branch_total = total_mb + total_cb
         instr_total = total_mi + total_ci
+        total_htt_note = "  (HTT-adjusted)" if total_excluded else ""
         print(f"\n{'='*70}")
         print("  TOTAL SUMMARY")
         print(f"{'='*70}")
-        print(f"  Branches:     {bar(total_cb, branch_total)}  {pct(total_cb, 
branch_total):>4}  ({total_cb}/{branch_total} covered, {total_mb} missed)")
-        print(f"  Instructions: {bar(total_ci, instr_total)}  {pct(total_ci, 
instr_total):>4}  ({total_ci}/{instr_total} covered, {total_mi} missed)")
+        print(f"  Branches:     {bar(total_cb, branch_total)}  {pct(total_cb, 
branch_total):>4}  ({total_cb}/{branch_total} covered, {total_mb} 
missed){total_htt_note}")
+        print(f"  Instructions: {bar(total_ci, instr_total)}  {pct(total_ci, 
instr_total):>4}  ({total_ci}/{instr_total} covered, {total_mi} 
missed){total_htt_note}")
+        if total_excluded:
+            files_with_excl = sum(1 for f in files_data if f[7])
+            print(f"  Excluded {total_excluded} hard-to-test line(s) (HTT) 
across {files_with_excl} file(s)")
         print()
 
 
@@ -403,7 +530,8 @@ def main():  # NOSONAR: always returns 0 by design — 
standard POSIX exit code
 
     xml_path = generate_combined_report()
 
-    report(xml_path, pkg_filter, file_filter, branches_only)
+    _, _, srcdirs = collect_jacoco_inputs()
+    report(xml_path, pkg_filter, file_filter, branches_only, srcdirs)
     return 0
 
 

Reply via email to