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