You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/10/26 14:46:38 UTC
(juneau) branch master updated: Make RestChildren.destroyChild skip a child whose resource bean the container already removed, so the remaining children are still destroyed, with a regression test; have seven tests capture and assert on their expected stack traces instead of printing them to the build log.
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 ec6d5b1ce7 Make RestChildren.destroyChild skip a child whose resource bean the container already removed, so the remaining children are still destroyed, with a regression test; have seven tests capture and assert on their expected stack traces instead of printing them to the build log.
ec6d5b1ce7 is described below
commit ec6d5b1ce790e26b43dac1a16df83ff132a10db4
Author: James Bognar <ja...@apache.org>
AuthorDate: Mon Oct 26 10:46:32 2026 -0400
Make RestChildren.destroyChild skip a child whose resource bean the container already removed, so the remaining children are still destroyed, with a regression test; have seven tests capture and assert on their expected stack traces instead of printing them to the build log.
---
.../commons/logging/LogRecordCapture_Test.java | 2 +-
.../juneau/commons/logging/RichLogger_Test.java | 4 +-
.../juneau/commons/utils/ThrowableUtils_Test.java | 10 +++-
.../server/DebugMarshalling_Behavior_Test.java | 4 +-
.../server/Rest_MethodOverrideAgreement_Test.java | 9 ++-
.../rest/mock/RequestIdDebugCapture_Test.java | 2 +-
.../juneau/rest/mock/RestDebugCapture_Test.java | 4 +-
.../apache/juneau/rest/server/RestChildren.java | 10 +++-
.../rest/server/RestChildren_Destroy_Test.java | 70 ++++++++++++++++++++++
9 files changed, 102 insertions(+), 13 deletions(-)
diff --git a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/LogRecordCapture_Test.java b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/LogRecordCapture_Test.java
index 548a25b884..9d34738354 100644
--- a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/LogRecordCapture_Test.java
+++ b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/LogRecordCapture_Test.java
@@ -383,7 +383,7 @@ class LogRecordCapture_Test extends TestBase {
@Test void j03_lastAndAssertHelpers_returnRawValues() {
var logger = RichLogger.getLogger("j03");
var ex = new RuntimeException("x");
- try (var capture = logger.captureEvents()) {
+ try (var quiet = LogRecordCapture.quiet("j03"); var capture = logger.captureEvents()) {
logger.info("a");
logger.warning(ex, "b");
diff --git a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/RichLogger_Test.java b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/RichLogger_Test.java
index f01c9e035e..c14b7bc961 100644
--- a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/RichLogger_Test.java
+++ b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/RichLogger_Test.java
@@ -900,7 +900,9 @@ class RichLogger_Test extends TestBase {
rec.setLoggerName(name);
logger.log(rec);
logger.info("commons");
- logger.log(Level.WARNING, new RuntimeException("ignored"), () -> "supplier");
+ var ignored = new RuntimeException("ignored");
+ ignored.setStackTrace(new StackTraceElement[0]); // Keep the console handler from printing a trace.
+ logger.log(Level.WARNING, ignored, () -> "supplier");
assertSize(3, capture.getRecords());
assertEquals(3, published.get());
diff --git a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/utils/ThrowableUtils_Test.java b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/utils/ThrowableUtils_Test.java
index b2de0bb1f4..97f3cbb188 100644
--- a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/utils/ThrowableUtils_Test.java
+++ b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/utils/ThrowableUtils_Test.java
@@ -25,6 +25,7 @@ import java.sql.*;
import java.util.*;
import org.apache.juneau.commons.*;
+import org.apache.juneau.commons.logging.*;
import org.apache.juneau.commons.reflect.*;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.parallel.*;
@@ -538,10 +539,13 @@ class ThrowableUtils_Test extends TestBase {
//====================================================================================================
@Test
void a032_printStackTrace_integer() {
- // Redirects to System.err — just verify it doesn't throw
+ // Logged as a warning on the ThrowableUtils logger; capture it so the trace stays off the console.
var ex = new RuntimeException("test error");
- assertDoesNotThrow(() -> printStackTrace(ex, (Integer)null));
- assertDoesNotThrow(() -> printStackTrace(ex, 2));
+ var records = LogRecordCapture.quietly(ThrowableUtils.class, () -> {
+ assertDoesNotThrow(() -> printStackTrace(ex, (Integer)null));
+ assertDoesNotThrow(() -> printStackTrace(ex, 2));
+ });
+ assertEquals(2, records.size());
}
//====================================================================================================
diff --git a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/DebugMarshalling_Behavior_Test.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/DebugMarshalling_Behavior_Test.java
index 9be5604206..b5cfc73e71 100644
--- a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/DebugMarshalling_Behavior_Test.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/DebugMarshalling_Behavior_Test.java
@@ -21,6 +21,7 @@ import static org.apache.juneau.http.classic.header.ContentType.*;
import java.util.logging.*;
import org.apache.juneau.*;
+import org.apache.juneau.commons.logging.*;
import org.apache.juneau.http.*;
import org.apache.juneau.marshall.json.*;
import org.apache.juneau.rest.mock.classic.*;
@@ -75,7 +76,8 @@ class DebugMarshalling_Behavior_Test extends TestBase {
var logger = Logger.getLogger(resourceClass.getName());
var prev = logger.getLevel();
logger.setLevel(level);
- try {
+ // At FINE the server logs the failed request with its stack trace; keep that off the console.
+ try (var quiet = LogRecordCapture.quiet(resourceClass)) {
action.run();
} finally {
logger.setLevel(prev);
diff --git a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/Rest_MethodOverrideAgreement_Test.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/Rest_MethodOverrideAgreement_Test.java
index 01af088103..3ee0cf6a8e 100644
--- a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/Rest_MethodOverrideAgreement_Test.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/Rest_MethodOverrideAgreement_Test.java
@@ -24,6 +24,7 @@ import java.util.concurrent.*;
import java.util.stream.*;
import org.apache.juneau.*;
+import org.apache.juneau.commons.logging.*;
import org.apache.juneau.rest.mock.classic.*;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.*;
@@ -282,9 +283,11 @@ class Rest_MethodOverrideAgreement_Test extends TestBase {
}
@Test void b13_conflict_withCallLoggerAndDebug_stays400() throws Exception {
- var c = MockRestClient.create(Star.class).ignoreErrors().debug().build();
- var r = c.get("/x").header("X-Method", "DELETE").header("X-HTTP-Method", "PUT").run();
- assertEquals(400, r.getStatusCode());
+ try (var quiet = LogRecordCapture.quiet(Star.class)) { // The debug logger would otherwise print the 400's stack trace.
+ var c = MockRestClient.create(Star.class).ignoreErrors().debug().build();
+ var r = c.get("/x").header("X-Method", "DELETE").header("X-HTTP-Method", "PUT").run();
+ assertEquals(400, r.getStatusCode());
+ }
}
@Test void b14_nonConflictingHeaders_stillDispatchWithTrailingSpace405() throws Exception {
diff --git a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RequestIdDebugCapture_Test.java b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RequestIdDebugCapture_Test.java
index 32382ec9cb..38584f6a45 100644
--- a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RequestIdDebugCapture_Test.java
+++ b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RequestIdDebugCapture_Test.java
@@ -106,7 +106,7 @@ class RequestIdDebugCapture_Test {
//------------------------------------------------------------------------------------------------------------------
@Test void c01_early404RecordCarriesRequestId() throws Exception {
- try (var c = RichLogger.getLogger(A_Resource.class).captureEvents(Level.FINEST)) {
+ try (var quiet = LogRecordCapture.quiet(A_Resource.class); var c = RichLogger.getLogger(A_Resource.class).captureEvents(Level.FINEST)) {
var client = org.apache.juneau.rest.mock.classic.MockRestClient.create(A_Resource.class).ignoreErrors().debug().build();
var res = client.get("/does-not-exist").run().assertStatus(404);
var echoed = res.getHeader("X-Request-Id").asString().orElseThrow();
diff --git a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RestDebugCapture_Test.java b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RestDebugCapture_Test.java
index 2b02a08e19..c05d5f6b9f 100644
--- a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RestDebugCapture_Test.java
+++ b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RestDebugCapture_Test.java
@@ -437,7 +437,7 @@ class RestDebugCapture_Test {
* headers and bodies are never rendered when no operation was resolved.
*/
@Test void g01_noOpPath_atFinestTier_neverRendersHeadersOrBody() throws Exception {
- try (var c = RichLogger.getLogger(A_Resource.class).captureEvents(Level.FINEST)) {
+ try (var quiet = LogRecordCapture.quiet(A_Resource.class); var c = RichLogger.getLogger(A_Resource.class).captureEvents(Level.FINEST)) {
var client = org.apache.juneau.rest.mock.classic.MockRestClient.create(A_Resource.class).debug().build();
client.get("/does-not-exist").header("X-Secret-Header", "leak-test-value").ignoreErrors().run()
@@ -527,7 +527,7 @@ class RestDebugCapture_Test {
@Test void a05_captureByHostName_observesOpAndNoOpLoggerPaths() throws Exception {
var hostName = A05_HostResource.class.getName();
- try (var c = RichLogger.getLogger(A05_HostResource.class).captureEvents(Level.FINEST)) {
+ try (var quiet = LogRecordCapture.quiet(A05_HostResource.class); var c = RichLogger.getLogger(A05_HostResource.class).captureEvents(Level.FINEST)) {
var client = org.apache.juneau.rest.mock.classic.MockRestClient
.create(A05_HostResource.class)
.debug()
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestChildren.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestChildren.java
index 61ec4a4155..982946c5a4 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestChildren.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestChildren.java
@@ -656,7 +656,15 @@ public class RestChildren {
// Servlet-backed resources delegate Servlet.destroy() through to RestContext.destroy() (see RestServlet.destroy),
// so we must avoid double-destruction in that case — call Servlet.destroy() and let it transitively call
// RestContext.destroy() exactly once.
- if (child.getResource() instanceof Servlet s)
+ Object resource;
+ try {
+ resource = child.getResource();
+ } catch (NoSuchElementException e) {
+ // The resource bean was already removed by the owning container (e.g. a closed Spring context),
+ // so there is no instance left to destroy.
+ return;
+ }
+ if (resource instanceof Servlet s)
s.destroy();
else
child.destroy();
diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/RestChildren_Destroy_Test.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/RestChildren_Destroy_Test.java
new file mode 100644
index 0000000000..1b9d616ecb
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/RestChildren_Destroy_Test.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+import java.util.concurrent.atomic.*;
+import java.util.function.*;
+
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests {@link RestChildren#destroy()} when a child's resource is no longer available from its supplier.
+ *
+ * @since 10.0.0
+ */
+@SuppressWarnings({
+ "resource" // RestContext instances are owned and destroyed by the test via RestChildren.destroy().
+})
+class RestChildren_Destroy_Test extends org.apache.juneau.TestBase {
+
+ static final AtomicInteger DESTROYED = new AtomicInteger();
+
+ @Rest(path="/gone")
+ public static class Gone {
+ @RestDestroy public void destroy() { DESTROYED.addAndGet(100); }
+ }
+
+ @Rest(path="/alive")
+ public static class Alive {
+ @RestDestroy public void destroy() { DESTROYED.incrementAndGet(); }
+ }
+
+ private static RestContext ctx(Class<?> type, Supplier<?> supplier) throws Exception {
+ return new RestContext(new RestContext.Args(type, null, null, supplier, null, null, null, null, null, null));
+ }
+
+ // A child whose bean was removed by its container (e.g. a closed Spring context) must be skipped, not abort the parent's destroy.
+ @Test void a01_destroy_childResourceGone_otherChildrenStillDestroyed() throws Exception {
+ DESTROYED.set(0);
+ var present = new AtomicBoolean(true);
+ var gone = ctx(Gone.class, () -> {
+ if (! present.get())
+ throw new NoSuchElementException("bean removed");
+ return new Gone();
+ });
+ var alive = ctx(Alive.class, Alive::new);
+ var children = RestChildren.create(null, null, null).add(gone).add(alive).build();
+
+ present.set(false);
+ assertDoesNotThrow(children::destroy);
+
+ assertEquals(1, DESTROYED.get());
+ }
+}