diff --git a/test/org/apache/catalina/valves/TestErrorReportValve.java b/test/org/apache/catalina/valves/TestErrorReportValve.java index d4bdee420f37..7849c7f61229 100644 --- a/test/org/apache/catalina/valves/TestErrorReportValve.java +++ b/test/org/apache/catalina/valves/TestErrorReportValve.java @@ -32,6 +32,7 @@ import org.junit.Test; import org.apache.catalina.Context; +import org.apache.catalina.Valve; import org.apache.catalina.Wrapper; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; @@ -263,4 +264,141 @@ public void testErrorPageServlet() throws Exception { } + @Test + public void testShowReportFalse() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + Context ctx = getProgrammaticRootContext(); + + Tomcat.addServlet(ctx, "error404", new Bug54220Servlet(true)); + ctx.addServletMappingDecoded("/", "error404"); + + tomcat.start(); + + // Find ErrorReportValve in the host pipeline + ErrorReportValve erv = findErrorReportValve(tomcat); + Assert.assertNotNull("ErrorReportValve should exist", erv); + erv.setShowReport(false); + + ByteChunk res = new ByteChunk(); + res.setCharset(StandardCharsets.UTF_8); + int rc = getUrl("http://localhost:" + getPort(), res, null); + + Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, rc); + String body = res.toString(); + if (body != null) { + Assert.assertFalse("Report should be hidden", + body.contains(sm.getString("errorReportValve.description"))); + } + Assert.assertFalse(erv.isShowReport()); + } + + + @Test + public void testShowServerInfoFalse() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + Context ctx = getProgrammaticRootContext(); + + Tomcat.addServlet(ctx, "error500", new ErrorServlet()); + ctx.addServletMappingDecoded("/", "error500"); + + tomcat.start(); + + ErrorReportValve erv = findErrorReportValve(tomcat); + Assert.assertNotNull("ErrorReportValve should exist", erv); + erv.setShowServerInfo(false); + + ByteChunk res = new ByteChunk(); + res.setCharset(StandardCharsets.UTF_8); + getUrl("http://localhost:" + getPort(), res, null); + + String body = res.toString(); + if (body != null) { + Assert.assertFalse("Server info should be hidden", + body.contains("Apache Tomcat")); + } + Assert.assertFalse(erv.isShowServerInfo()); + } + + + @Test + public void testSetPropertyErrorCode() { + ErrorReportValve valve = new ErrorReportValve(); + + Assert.assertTrue(valve.setProperty("errorCode.404", "/error404.html")); + Assert.assertEquals("/error404.html", valve.getProperty("errorCode.404")); + } + + + @Test + public void testSetPropertyExceptionType() { + ErrorReportValve valve = new ErrorReportValve(); + + Assert.assertTrue(valve.setProperty( + "exceptionType.java.lang.NullPointerException", "/npe.html")); + Assert.assertEquals("/npe.html", valve.getProperty( + "exceptionType.java.lang.NullPointerException")); + } + + + @Test + public void testGetPropertyNotFound() { + ErrorReportValve valve = new ErrorReportValve(); + + Assert.assertNull(valve.getProperty("errorCode.999")); + Assert.assertNull(valve.getProperty("exceptionType.com.example.Nope")); + } + + + @Test + public void testGetPropertyUnknownPrefix() { + ErrorReportValve valve = new ErrorReportValve(); + + Assert.assertFalse(valve.setProperty("unknownPrefix.something", "/x.html")); + Assert.assertNull(valve.getProperty("unknownPrefix.something")); + } + + + @Test + public void testExceptionWithRootCause() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + Context ctx = getProgrammaticRootContext(); + + Tomcat.addServlet(ctx, "nestedError", new HttpServlet() { + private static final long serialVersionUID = 1L; + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + Throwable root = new IllegalStateException("root cause"); + Throwable wrapper = new RuntimeException("wrapper", root); + req.setAttribute(RequestDispatcher.ERROR_EXCEPTION, wrapper); + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + }); + ctx.addServletMappingDecoded("/", "nestedError"); + + tomcat.start(); + + ByteChunk res = new ByteChunk(); + res.setCharset(StandardCharsets.UTF_8); + int rc = getUrl("http://localhost:" + getPort(), res, null); + + Assert.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, rc); + String body = res.toString(); + Assert.assertNotNull(body); + // Should contain root cause section + Assert.assertTrue(body.contains(sm.getString("errorReportValve.rootCause"))); + } + + + private static ErrorReportValve findErrorReportValve(Tomcat tomcat) { + for (Valve v : tomcat.getHost().getPipeline().getValves()) { + if (v instanceof ErrorReportValve) { + return (ErrorReportValve) v; + } + } + return null; + } } diff --git a/test/org/apache/catalina/valves/TestLoadBalancerDrainingValveUnit.java b/test/org/apache/catalina/valves/TestLoadBalancerDrainingValveUnit.java new file mode 100644 index 000000000000..67af4792a71c --- /dev/null +++ b/test/org/apache/catalina/valves/TestLoadBalancerDrainingValveUnit.java @@ -0,0 +1,135 @@ +/* + * 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.catalina.valves; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.buf.ByteChunk; + +/** + * Additional tests for {@link LoadBalancerDrainingValve} covering property + * getters/setters and URI edge cases not covered by the parameterized test. + */ +public class TestLoadBalancerDrainingValveUnit extends TomcatBaseTest { + + @Test + public void testGetSetProperties() { + LoadBalancerDrainingValve valve = new LoadBalancerDrainingValve(); + + valve.setRedirectStatusCode(302); + // No getter for redirectStatusCode, so just verify no exception + + valve.setIgnoreCookieName("testCookie"); + Assert.assertEquals("testCookie", valve.getIgnoreCookieName()); + + valve.setIgnoreCookieValue("testValue"); + Assert.assertEquals("testValue", valve.getIgnoreCookieValue()); + } + + + @Test + public void testActiveNodePassesThrough() throws Exception { + Tomcat tomcat = getTomcatInstance(); + Context ctx = getProgrammaticRootContext(); + + Tomcat.addServlet(ctx, "ok", new OkServlet()); + ctx.addServletMappingDecoded("/", "ok"); + + LoadBalancerDrainingValve valve = new LoadBalancerDrainingValve(); + ctx.getPipeline().addValve(valve); + + tomcat.start(); + + ByteChunk res = new ByteChunk(); + res.setCharset(StandardCharsets.UTF_8); + int rc = getUrl("http://localhost:" + getPort() + "/test", res, null); + + // No JK_LB_ACTIVATION attribute set, so request passes through + Assert.assertEquals(HttpServletResponse.SC_OK, rc); + Assert.assertEquals("OK", res.toString()); + } + + + @Test + public void testCollapseMultipleSlashesInURI() throws Exception { + Tomcat tomcat = getTomcatInstance(); + Context ctx = getProgrammaticRootContext(); + + Tomcat.addServlet(ctx, "ok", new OkServlet()); + ctx.addServletMappingDecoded("/", "ok"); + + LoadBalancerDrainingValve valve = new LoadBalancerDrainingValve(); + ctx.getPipeline().addValve(valve); + + tomcat.start(); + + // Request with multiple leading slashes but no DIS activation + // should pass through normally + ByteChunk res = new ByteChunk(); + res.setCharset(StandardCharsets.UTF_8); + int rc = getUrl("http://localhost:" + getPort() + "///test", res, null); + + Assert.assertEquals(HttpServletResponse.SC_OK, rc); + } + + + @Test + public void testSessionURIParam() throws Exception { + Tomcat tomcat = getTomcatInstance(); + Context ctx = getProgrammaticRootContext(); + + Tomcat.addServlet(ctx, "ok", new OkServlet()); + ctx.addServletMappingDecoded("/", "ok"); + + LoadBalancerDrainingValve valve = new LoadBalancerDrainingValve(); + ctx.getPipeline().addValve(valve); + + tomcat.start(); + + // Request with jsessionid parameter but no DIS activation + ByteChunk res = new ByteChunk(); + res.setCharset(StandardCharsets.UTF_8); + int rc = getUrl("http://localhost:" + getPort() + + "/test;jsessionid=abc123", res, null); + + Assert.assertEquals(HttpServletResponse.SC_OK, rc); + } + + + private static class OkServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/plain"); + resp.getWriter().print("OK"); + } + } +} diff --git a/test/org/apache/catalina/valves/TestRemoteCIDRValve.java b/test/org/apache/catalina/valves/TestRemoteCIDRValve.java new file mode 100644 index 000000000000..fb4ddc84f73e --- /dev/null +++ b/test/org/apache/catalina/valves/TestRemoteCIDRValve.java @@ -0,0 +1,180 @@ +/* + * 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.catalina.valves; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for {@link RemoteCIDRValve}. + */ +public class TestRemoteCIDRValve { + + @Test + public void testAllowSingleIPv4() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAllow("127.0.0.0/8"); + + Assert.assertTrue(valve.isAllowed("127.0.0.1")); + Assert.assertFalse(valve.isAllowed("192.168.1.1")); + } + + @Test + public void testDenySingleIPv4() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setDeny("10.0.0.0/8"); + + // When deny is set but allow is not, all non-denied addresses are allowed + Assert.assertTrue(valve.isAllowed("192.168.1.1")); + Assert.assertFalse(valve.isAllowed("10.0.0.1")); + } + + @Test + public void testDenyOverridesAllow() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAllow("192.168.0.0/16"); + valve.setDeny("192.168.1.0/24"); + + // Deny is checked first + Assert.assertFalse(valve.isAllowed("192.168.1.1")); + Assert.assertTrue(valve.isAllowed("192.168.2.1")); + } + + @Test + public void testAllowEmptyDenyNotEmpty() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setDeny("10.0.0.0/8"); + // allow is empty, deny is set → allow everything not in deny + + Assert.assertTrue(valve.isAllowed("192.168.1.1")); + Assert.assertTrue(valve.isAllowed("172.16.0.1")); + Assert.assertFalse(valve.isAllowed("10.1.2.3")); + } + + @Test + public void testDefaultDenyAll() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + // No allow, no deny → deny all + Assert.assertFalse(valve.isAllowed("127.0.0.1")); + Assert.assertFalse(valve.isAllowed("192.168.1.1")); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidAllow() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAllow("not-a-valid-cidr"); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidDeny() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setDeny("not-a-valid-cidr"); + } + + @Test + public void testNullAllow() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAllow("127.0.0.0/8"); + Assert.assertFalse(valve.getAllow().isEmpty()); + + // Setting null clears the list + valve.setAllow(null); + Assert.assertEquals("", valve.getAllow()); + } + + @Test + public void testEmptyAllow() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAllow("127.0.0.0/8"); + + valve.setAllow(""); + Assert.assertEquals("", valve.getAllow()); + } + + @Test + public void testNullDeny() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setDeny("10.0.0.0/8"); + Assert.assertFalse(valve.getDeny().isEmpty()); + + valve.setDeny(null); + Assert.assertEquals("", valve.getDeny()); + } + + @Test + public void testGetAllowGetDeny() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAllow("127.0.0.0/8, 192.168.0.0/16"); + valve.setDeny("10.0.0.0/8"); + + String allow = valve.getAllow(); + Assert.assertTrue(allow.contains("127.0.0.0/8")); + Assert.assertTrue(allow.contains("192.168.0.0/16")); + + String deny = valve.getDeny(); + Assert.assertTrue(deny.contains("10.0.0.0/8")); + } + + @Test + public void testMultipleAllowCIDRs() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAllow("127.0.0.0/8, 10.0.0.0/8"); + + Assert.assertTrue(valve.isAllowed("127.0.0.1")); + Assert.assertTrue(valve.isAllowed("10.1.2.3")); + Assert.assertFalse(valve.isAllowed("192.168.1.1")); + } + + @Test + public void testIPv6Allow() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAllow("::1/128"); + + Assert.assertTrue(valve.isAllowed("::1")); + Assert.assertFalse(valve.isAllowed("::2")); + } + + @Test + public void testIsAllowedWithPort() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAddConnectorPort(true); + valve.setAllow("127.0.0.0/8;8080"); + + Assert.assertTrue(valve.isAllowed("127.0.0.1;8080")); + Assert.assertFalse(valve.isAllowed("127.0.0.1;9090")); + } + + @Test + public void testIsAllowedNoPortWhenExpected() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAddConnectorPort(true); + valve.setAllow("127.0.0.0/8;8080"); + + // No port provided when addConnectorPort is true + Assert.assertFalse(valve.isAllowed("127.0.0.1")); + } + + @Test + public void testIsAllowedUnexpectedPort() { + RemoteCIDRValve valve = new RemoteCIDRValve(); + valve.setAddConnectorPort(false); + valve.setAllow("127.0.0.0/8"); + + // Port provided when addConnectorPort is false + Assert.assertFalse(valve.isAllowed("127.0.0.1;8080")); + } +} diff --git a/test/org/apache/catalina/valves/TestRemoteIpValve.java b/test/org/apache/catalina/valves/TestRemoteIpValve.java index d89a4ec60115..e313ee506359 100644 --- a/test/org/apache/catalina/valves/TestRemoteIpValve.java +++ b/test/org/apache/catalina/valves/TestRemoteIpValve.java @@ -1229,4 +1229,55 @@ private void doTestNetMaskSet(NetMaskSet netMaskSet, String remoteIp, boolean ex boolean match = netMaskSet.contains(remoteIp); Assert.assertEquals(remoteIp, Boolean.valueOf(expectedMatch), Boolean.valueOf(match)); } + + + @Test + public void testGetSetProperties() { + RemoteIpValve valve = new RemoteIpValve(); + + valve.setHostHeader("x-forwarded-host"); + Assert.assertEquals("x-forwarded-host", valve.getHostHeader()); + + valve.setChangeLocalName(true); + Assert.assertTrue(valve.isChangeLocalName()); + valve.setChangeLocalName(false); + Assert.assertFalse(valve.isChangeLocalName()); + + valve.setChangeLocalPort(true); + Assert.assertTrue(valve.isChangeLocalPort()); + valve.setChangeLocalPort(false); + Assert.assertFalse(valve.isChangeLocalPort()); + + valve.setHttpServerPort(80); + Assert.assertEquals(80, valve.getHttpServerPort()); + + valve.setHttpsServerPort(443); + Assert.assertEquals(443, valve.getHttpsServerPort()); + + valve.setPortHeader("x-forwarded-port"); + Assert.assertEquals("x-forwarded-port", valve.getPortHeader()); + + valve.setProtocolHeader("x-forwarded-proto"); + Assert.assertEquals("x-forwarded-proto", valve.getProtocolHeader()); + + valve.setProtocolHeaderHttpsValue("https"); + Assert.assertEquals("https", valve.getProtocolHeaderHttpsValue()); + + valve.setRemoteIpHeader("x-forwarded-for"); + Assert.assertEquals("x-forwarded-for", valve.getRemoteIpHeader()); + + valve.setProxiesHeader("x-forwarded-by"); + Assert.assertEquals("x-forwarded-by", valve.getProxiesHeader()); + + valve.setRequestAttributesEnabled(true); + Assert.assertTrue(valve.getRequestAttributesEnabled()); + valve.setRequestAttributesEnabled(false); + Assert.assertFalse(valve.getRequestAttributesEnabled()); + + valve.setTrustedProxies("10.0.0.0/8"); + Assert.assertEquals("10.0.0.0/8", valve.getTrustedProxies()); + + valve.setInternalProxies("192.168.0.0/16"); + Assert.assertEquals("192.168.0.0/16", valve.getInternalProxies()); + } } diff --git a/test/org/apache/catalina/valves/TestSSLValve.java b/test/org/apache/catalina/valves/TestSSLValve.java index 9a7993c9d2ae..f86e8fa7c67e 100644 --- a/test/org/apache/catalina/valves/TestSSLValve.java +++ b/test/org/apache/catalina/valves/TestSSLValve.java @@ -403,4 +403,98 @@ private void assertCertificateParsed() throws Exception { Assert.assertNotNull(certificates[0]); Assert.assertEquals(0, f.getMessageCount()); } + + + @Test + public void testGetSetProperties() { + SSLValve v = new SSLValve(); + + v.setSslClientCertHeader("custom_cert"); + Assert.assertEquals("custom_cert", v.getSslClientCertHeader()); + + v.setSslClientEscapedCertHeader("custom_escaped"); + Assert.assertEquals("custom_escaped", v.getSslClientEscapedCertHeader()); + + v.setSslSecureProtocolHeader("custom_protocol"); + Assert.assertEquals("custom_protocol", v.getSslSecureProtocolHeader()); + + v.setSslCipherHeader("custom_cipher"); + Assert.assertEquals("custom_cipher", v.getSslCipherHeader()); + + v.setSslSessionIdHeader("custom_session"); + Assert.assertEquals("custom_session", v.getSslSessionIdHeader()); + + v.setSslCipherUserKeySizeHeader("custom_keysize"); + Assert.assertEquals("custom_keysize", v.getSslCipherUserKeySizeHeader()); + } + + + @Test + public void testCustomHeaderNames() throws Exception { + SSLValve customValve = new SSLValve(); + customValve.setSslCipherHeader("X-Custom-Cipher"); + customValve.setSslSessionIdHeader("X-Custom-Session"); + customValve.setSslSecureProtocolHeader("X-Custom-Protocol"); + customValve.setSslCipherUserKeySizeHeader("X-Custom-KeySize"); + + Valve next = EasyMock.createMock(Valve.class); + customValve.setNext(next); + + Connector connector = EasyMock.createNiceMock(Connector.class); + EasyMock.replay(connector); + MockRequest req = new MockRequest(connector); + + next.invoke(req, null); + EasyMock.replay(next); + + req.setHeader("X-Custom-Cipher", "AES256"); + req.setHeader("X-Custom-Session", "sess123"); + req.setHeader("X-Custom-Protocol", "TLSv1.3"); + req.setHeader("X-Custom-KeySize", "256"); + + customValve.invoke(req, null); + + EasyMock.verify(next); + Assert.assertEquals("AES256", req.getAttribute(Globals.CIPHER_SUITE_ATTR)); + Assert.assertEquals("sess123", req.getAttribute(Globals.SSL_SESSION_ID_ATTR)); + Assert.assertEquals("TLSv1.3", req.getAttribute(Globals.SECURE_PROTOCOL_ATTR)); + Assert.assertEquals(Integer.valueOf(256), req.getAttribute(Globals.KEY_SIZE_ATTR)); + } + + + @Test + public void testNoCertHeaders() throws Exception { + // Use a fresh request to avoid stale headers from singleton + SSLValve freshValve = new SSLValve(); + Valve freshNext = EasyMock.createMock(Valve.class); + freshValve.setNext(freshNext); + + Connector connector = EasyMock.createNiceMock(Connector.class); + EasyMock.replay(connector); + MockRequest freshRequest = new MockRequest(connector); + + freshNext.invoke(freshRequest, null); + EasyMock.replay(freshNext); + + // No cert headers set, only an unrelated one + freshRequest.setHeader("some_unrelated_header", "value"); + + freshValve.invoke(freshRequest, null); + + EasyMock.verify(freshNext); + Assert.assertNull(freshRequest.getAttribute(Globals.CERTIFICATES_ATTR)); + } + + + @Test + public void testEscapedCertTakesPriority() throws Exception { + setUp(); + String escapedCert = certificateEscaped(); + mockRequest.setHeader(valve.getSslClientEscapedCertHeader(), escapedCert); + mockRequest.setHeader(valve.getSslClientCertHeader(), "short"); + + valve.invoke(mockRequest, null); + + assertCertificateParsed(); + } } diff --git a/test/org/apache/catalina/valves/TestStuckThreadDetectionValve.java b/test/org/apache/catalina/valves/TestStuckThreadDetectionValve.java index 5b1139b9d105..6fd5292787ef 100644 --- a/test/org/apache/catalina/valves/TestStuckThreadDetectionValve.java +++ b/test/org/apache/catalina/valves/TestStuckThreadDetectionValve.java @@ -155,4 +155,112 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } } + + + @Test + public void testThresholdDisabled() throws Exception { + StickingServlet stickingServlet = new StickingServlet(100L); + Wrapper servlet = Tomcat.addServlet(context, "myservlet", stickingServlet); + servlet.addMapping("/myservlet"); + + StuckThreadDetectionValve valve = new StuckThreadDetectionValve(); + valve.setThreshold(0); // Disabled + context.addValve(valve); + tomcat.start(); + + ByteChunk result = new ByteChunk(); + getUrl("http://localhost:" + getPort() + "/myservlet", result, null); + + Assert.assertTrue(result.toString().startsWith("OK")); + Assert.assertEquals(0, valve.getStuckThreadCount()); + Assert.assertEquals(0, valve.getStuckThreadIds().length); + Assert.assertEquals(0, valve.getStuckThreadNames().length); + } + + + @Test + public void testGetStuckThreadNames() throws Exception { + StickingServlet stickingServlet = new StickingServlet(8000L); + Wrapper servlet = Tomcat.addServlet(context, "myservlet", stickingServlet); + servlet.addMapping("/myservlet"); + + StuckThreadDetectionValve valve = new StuckThreadDetectionValve(); + valve.setThreshold(2); + context.addValve(valve); + context.setBackgroundProcessorDelay(1); + tomcat.start(); + + final ByteChunk result = new ByteChunk(); + Thread asyncThread = new Thread() { + @Override + public void run() { + try { + getUrl("http://localhost:" + getPort() + "/myservlet", result, null); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + }; + asyncThread.start(); + try { + Thread.sleep(5000L); + Assert.assertEquals(1, valve.getStuckThreadCount()); + String[] names = valve.getStuckThreadNames(); + Assert.assertEquals(1, names.length); + Assert.assertNotNull(names[0]); + } finally { + asyncThread.join(20000); + Assert.assertFalse(asyncThread.isAlive()); + } + } + + + @Test + public void testGetSetProperties() { + StuckThreadDetectionValve valve = new StuckThreadDetectionValve(); + + valve.setThreshold(300); + Assert.assertEquals(300, valve.getThreshold()); + + valve.setInterruptThreadThreshold(600); + Assert.assertEquals(600, valve.getInterruptThreadThreshold()); + } + + + @Test + public void testGetInterruptedThreadsCount() throws Exception { + StickingServlet stickingServlet = new StickingServlet(TimeUnit.SECONDS.toMillis(20L)); + Wrapper servlet = Tomcat.addServlet(context, "myservlet", stickingServlet); + servlet.addMapping("/myservlet"); + + StuckThreadDetectionValve valve = new StuckThreadDetectionValve(); + valve.setThreshold(2); + valve.setInterruptThreadThreshold(5); + context.addValve(valve); + context.setBackgroundProcessorDelay(1); + tomcat.start(); + + Assert.assertEquals(0, valve.getInterruptedThreadsCount()); + + final ByteChunk result = new ByteChunk(); + Thread asyncThread = new Thread() { + @Override + public void run() { + try { + getUrl("http://localhost:" + getPort() + "/myservlet", result, null); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + }; + asyncThread.start(); + try { + asyncThread.join(20000); + Assert.assertFalse(asyncThread.isAlive()); + } finally { + // Interrupted thread count should now be > 0 + Assert.assertTrue(stickingServlet.wasInterrupted); + Assert.assertTrue(valve.getInterruptedThreadsCount() > 0); + } + } }