This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tomcat-native.git
commit 80f264c3d4a7a0b75cf147e5e0f1a64858ca1f44 Author: Mark Thomas <ma...@apache.org> AuthorDate: Tue Jun 25 14:44:45 2019 +0100 Add Java test files --- test/org/apache/tomcat/jni/AbstractJniTest.java | 46 ++++ test/org/apache/tomcat/jni/TestFile.java | 67 ++++++ test/org/apache/tomcat/jni/TestSocketServer.java | 233 +++++++++++++++++++++ .../jni/TestSocketServerAnyLocalAddress.java | 207 ++++++++++++++++++ test/org/apache/tomcat/jni/TesterSSL.java | 58 +++++ 5 files changed, 611 insertions(+) diff --git a/test/org/apache/tomcat/jni/AbstractJniTest.java b/test/org/apache/tomcat/jni/AbstractJniTest.java new file mode 100644 index 0000000..84ed1ac --- /dev/null +++ b/test/org/apache/tomcat/jni/AbstractJniTest.java @@ -0,0 +1,46 @@ +/* + * 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.tomcat.jni; + +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; + +public abstract class AbstractJniTest { + + private boolean nativeLibraryPresent = false; + + @Before + public void initBase() throws Exception { + try { + Library.initialize(null); + nativeLibraryPresent = true; + } catch (LibraryNotFoundError lnfe) { + nativeLibraryPresent = false; + } + Assume.assumeTrue("APR Library not found", nativeLibraryPresent); + } + + + @After + public void destroyBase() { + if (nativeLibraryPresent) { + Library.terminate(); + } + } + +} diff --git a/test/org/apache/tomcat/jni/TestFile.java b/test/org/apache/tomcat/jni/TestFile.java new file mode 100644 index 0000000..433939f --- /dev/null +++ b/test/org/apache/tomcat/jni/TestFile.java @@ -0,0 +1,67 @@ +/* + * 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.tomcat.jni; + +import org.junit.Assert; +import org.junit.Test; + + +public class TestFile extends AbstractJniTest { + + @Test + public void testInfoGet() throws Exception { + String testFile = "test/org/apache/tomcat/jni/TestFile.java"; + java.io.File file = new java.io.File(testFile); + Assert.assertTrue("File " + testFile + " does not exist!", file.exists()); + + Library.initialize(null); + long pool = Pool.create(0L); + int openFlags = File.APR_FOPEN_READ | File.APR_FOPEN_BUFFERED | File.APR_FOPEN_XTHREAD; + int openPermissions = File.APR_FPROT_OS_DEFAULT; + int statFlags = File.APR_FINFO_MIN; + long fd = File.open(testFile, openFlags, openPermissions, pool); + FileInfo fileInfo = new FileInfo(); + for (int i = 0; i < 100000; i++) { + org.apache.tomcat.jni.File.infoGet(fileInfo, statFlags, fd); + @SuppressWarnings("unused") + String info = inspectFileInfo(fileInfo); + } + } + + public static String inspectFileInfo(FileInfo fileInfo) { + String result = fileInfo.toString() + " : {" + + String.format("\n pool : %d", Long.valueOf(fileInfo.pool)) + + String.format("\n valid : %d", Integer.valueOf(fileInfo.valid)) + + String.format("\n protection : %d", Integer.valueOf(fileInfo.protection)) + + String.format("\n filetype : %d", Integer.valueOf(fileInfo.filetype)) + + String.format("\n user : %d", Integer.valueOf(fileInfo.user)) + + String.format("\n group : %d", Integer.valueOf(fileInfo.group)) + + String.format("\n inode : %d", Integer.valueOf(fileInfo.inode)) + + String.format("\n device : %d", Integer.valueOf(fileInfo.device)) + + String.format("\n nlink : %d", Integer.valueOf(fileInfo.nlink)) + + String.format("\n size : %d", Long.valueOf(fileInfo.size)) + + String.format("\n csize : %d", Long.valueOf(fileInfo.csize)) + + String.format("\n atime : %d", Long.valueOf(fileInfo.atime)) + + String.format("\n mtime : %d", Long.valueOf(fileInfo.mtime)) + + String.format("\n ctime : %d", Long.valueOf(fileInfo.ctime)) + + String.format("\n fname : %s", fileInfo.fname) + + String.format("\n name : %s", fileInfo.name) + + String.format("\n filehand : %d", Long.valueOf(fileInfo.filehand)) + + "\n}"; + return result; + } +} \ No newline at end of file diff --git a/test/org/apache/tomcat/jni/TestSocketServer.java b/test/org/apache/tomcat/jni/TestSocketServer.java new file mode 100644 index 0000000..d1ea90d --- /dev/null +++ b/test/org/apache/tomcat/jni/TestSocketServer.java @@ -0,0 +1,233 @@ +/* + * 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.tomcat.jni; + +import java.io.OutputStream; +import java.util.concurrent.CountDownLatch; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for server-side sockets. + */ +public class TestSocketServer extends AbstractJniTest { + + private static final String HOST = "localhost"; + private static final long ERROR_MARGIN; + + private int port = 0; + private long serverSocket = 0; + private long clientSocket = 0; + + // Determine the resolution of System.nanoTime() so an appropriate error + // margin can be used in tests that use nanoTime() + static { + long start = System.nanoTime(); + long end = System.nanoTime(); + while (end == start) { + end = System.nanoTime(); + } + ERROR_MARGIN = 2 * (end - start); + } + + + @Before + public void init() throws Exception { + long serverPool = Pool.create(0); + long inetAddress = Address.info(HOST, Socket.APR_INET, + 0, 0, serverPool); + serverSocket = Socket.create(Socket.APR_INET, Socket.SOCK_STREAM, + Socket.APR_PROTO_TCP, serverPool); + if (OS.IS_UNIX) { + Socket.optSet(serverSocket, Socket.APR_SO_REUSEADDR, 1); + } + int rc = Socket.bind(serverSocket, inetAddress); + Assert.assertEquals("Can't bind: " + Error.strerror(rc), 0, rc); + Socket.listen(serverSocket, 5); + if (!OS.IS_UNIX) { + Socket.optSet(serverSocket, Socket.APR_SO_REUSEADDR, 1); + } + + long localAddress = Address.get(Socket.APR_LOCAL, serverSocket); + port = Address.getInfo(localAddress).port; + } + + + @After + public void destroy() { + if (clientSocket != 0) { + Socket.close(clientSocket); + Socket.destroy(clientSocket); + } + if (serverSocket != 0) { + Socket.close(serverSocket); + Socket.destroy(serverSocket); + } + } + + + @Test + public void testPort() { + Assert.assertTrue(port > 0); + } + + + @Test + public void testBlockingReadFromClientWithTimeout() throws Exception { + /* Start the client that connects to the server */ + Client client = new Client(port); + client.start(); + + /* Accept the client connection */ + clientSocket = Socket.accept(serverSocket); + + /* Configure a 1s timeout for reading from client */ + Socket.timeoutSet(clientSocket, 1000000); + long timeout = Socket.timeoutGet(clientSocket); + Assert.assertEquals("Socket.timeoutGet clientSocket failed", 1000000, timeout); + + byte [] buf = new byte[1]; + long start = System.nanoTime(); + while (Socket.recv(clientSocket, buf, 0, 1) == 1) { + } + long wait = System.nanoTime() - start; + Assert.assertFalse("Socket.timeoutSet failed (<1s) [" + wait + "] +-[" + ERROR_MARGIN + "]", + wait < 1000000000 - ERROR_MARGIN); + Assert.assertFalse("Socket.timeoutSet failed (>2s) [" + wait + "] +-[" + ERROR_MARGIN + "]", + wait > 2000000000 + ERROR_MARGIN); + + client.countDown(); + client.join(); + } + + + @Test + public void testNonBlockingReadFromClient() throws Exception { + /* Start the client that connects to the server */ + Client client = new Client(port); + client.start(); + + /* Accept the client connection */ + clientSocket = Socket.accept(serverSocket); + + /* Configure the connection for non-blocking */ + Socket.optSet(clientSocket, Socket.APR_SO_NONBLOCK, 1); + int val = Socket.optGet(clientSocket, Socket.APR_SO_NONBLOCK); + Assert.assertEquals("Socket.optGet clientSocket failed", 1, val); + + byte [] buf = new byte[1]; + long start = System.nanoTime(); + while (Socket.recv(clientSocket, buf, 0, 1) == 1) { + } + long wait = System.nanoTime() - start; + Assert.assertFalse("non_blocking client Socket.APR_SO_NONBLOCK failed (>2ms) [" + wait + + "] +-[" + ERROR_MARGIN + "]", wait > 2000000 + ERROR_MARGIN); + + client.countDown(); + client.join(); + } + + + @Test + public void testNonBlockingReadThenBlockingReadFromClient() throws Exception { + /* Start the client that connects to the server */ + Client client = new Client(port); + client.start(); + + /* Accept the client connection */ + clientSocket = Socket.accept(serverSocket); + + /* Configure the connection for non-blocking */ + Socket.optSet(clientSocket, Socket.APR_SO_NONBLOCK, 1); + + byte [] buf = new byte[1]; + long start = System.nanoTime(); + while (Socket.recv(clientSocket, buf, 0, 1) == 1) { + } + long wait = System.nanoTime() - start; + Assert.assertFalse("non_blocking client Socket.APR_SO_NONBLOCK failed (>1ms) [" + wait + + "] +-[" + ERROR_MARGIN + "]", wait > 1000000 + ERROR_MARGIN); + + /* Configure for blocking */ + Socket.optSet(clientSocket, Socket.APR_SO_NONBLOCK, 0); + Socket.timeoutSet(clientSocket, 2000); + start = System.nanoTime(); + while (Socket.recv(clientSocket, buf, 0, 1) == 1) { + } + wait = System.nanoTime() - start; + Assert.assertFalse("non_blocking client Socket.APR_SO_NONBLOCK false failed (<1ms) [" + + wait + "] +-[" + ERROR_MARGIN + "]", wait < 1000000 - ERROR_MARGIN); + + client.countDown(); + client.join(); + } + + + @Test + public void testNonBlockingAcceptWithNoClient() throws Exception { + Socket.optSet(serverSocket, Socket.APR_SO_NONBLOCK, 1); + int val = Socket.optGet(serverSocket, Socket.APR_SO_NONBLOCK); + Assert.assertEquals("Socket.optGet serverSocket failed", 1, val); + + long start = System.nanoTime(); + boolean ok = false; + try { + Socket.accept(serverSocket); + } catch (Exception ex) { + ok = true; + } + long wait = System.nanoTime() - start; + Assert.assertTrue("Timeout failed", ok); + Assert.assertFalse("non_blocking accept Socket.APR_SO_NONBLOCK failed (>10ms) [" + wait + + "] +-[" + ERROR_MARGIN + "]", wait > 10000000 + ERROR_MARGIN); + } + + + /** + * Simple client that connects, sends a single byte then closes the + * connection. + */ + private static class Client extends java.lang.Thread { + + private final int port; + private final CountDownLatch complete = new CountDownLatch(1); + + public Client(int port) throws Exception { + this.port = port; + } + + public void countDown() { + complete.countDown(); + } + + @Override + public void run() { + + try (java.net.Socket sock = new java.net.Socket(TestSocketServer.HOST, port)) { + OutputStream os = sock.getOutputStream(); + os.write('A'); + os.flush(); + complete.await(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } +} diff --git a/test/org/apache/tomcat/jni/TestSocketServerAnyLocalAddress.java b/test/org/apache/tomcat/jni/TestSocketServerAnyLocalAddress.java new file mode 100644 index 0000000..866ba77 --- /dev/null +++ b/test/org/apache/tomcat/jni/TestSocketServerAnyLocalAddress.java @@ -0,0 +1,207 @@ +/* + * 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.tomcat.jni; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for server-side sockets using any local address (0.0.0.0 or ::). + */ +public class TestSocketServerAnyLocalAddress extends AbstractJniTest { + + // Excessive but allows for slow systems + private static final int TIMEOUT_MICROSECONDS = 10 * 1000 * 1000; + + private long serverSocket = 0; + private long clientSocket = 0; + + + @Before + public void init() throws Exception { + long serverPool = Pool.create(0); + long inetAddress = Address.info(null, Socket.APR_UNSPEC, + 0, 0, serverPool); + serverSocket = Socket.create(Address.getInfo(inetAddress).family, Socket.SOCK_STREAM, + Socket.APR_PROTO_TCP, serverPool); + if (OS.IS_UNIX) { + Socket.optSet(serverSocket, Socket.APR_SO_REUSEADDR, 1); + } + int rc = Socket.bind(serverSocket, inetAddress); + Assert.assertEquals("Can't bind: " + Error.strerror(rc), 0, rc); + Socket.listen(serverSocket, 5); + if (!OS.IS_UNIX) { + Socket.optSet(serverSocket, Socket.APR_SO_REUSEADDR, 1); + } + } + + + @After + public void destroy() { + if (clientSocket != 0) { + Socket.close(clientSocket); + Socket.destroy(clientSocket); + } + if (serverSocket != 0) { + Socket.close(serverSocket); + Socket.destroy(serverSocket); + } + } + + + @Test + public void testWithClient() throws Exception { + /* Start the client that connects to the server */ + Client client = new Client(serverSocket); + client.start(); + + boolean running = true; + while (running) { + /* Accept the client connection */ + clientSocket = Socket.accept(serverSocket); + + /* Configure a 10s timeout for reading from client */ + Socket.timeoutSet(clientSocket, TIMEOUT_MICROSECONDS); + + byte [] buf = new byte[1]; + while (Socket.recv(clientSocket, buf, 0, 1) == 1) { + // If 'A' was read, echo back 'Z' + if (buf[0] == 'A') { + buf[0] = 'Z'; + Socket.send(clientSocket, buf, 0, 1); + } + } + if (buf[0] == 'E') { + running = false; + } else if (buf[0] == 'Z') { + // NO-OP - connection closing + } else { + Assert.fail("Unexpected data [" + (char) buf[0] + "]"); + } + } + + client.join(); + } + + + /** + * Simple client that connects, sends a single byte then closes the + * connection. + */ + private static class Client extends java.lang.Thread { + + private final long serverSocket; + + public Client(long serverSocket) throws Exception { + this.serverSocket = serverSocket; + } + + @Override + public void run() { + + try { + InetSocketAddress connectAddress = getConnectAddress(serverSocket); + java.net.Socket sock = new java.net.Socket(); + sock.connect(connectAddress, TIMEOUT_MICROSECONDS); + sock.setSoTimeout(TIMEOUT_MICROSECONDS); + OutputStream ou = sock.getOutputStream(); + InputStream in = sock.getInputStream(); + ou.write('A'); + ou.flush(); + int rep = in.read(); + sock.close(); + if (rep != 'Z') { + throw new Exception("Read wrong data [" + rep + "]"); + } + + sock = new java.net.Socket(); + sock.connect(connectAddress, TIMEOUT_MICROSECONDS); + sock.setSoTimeout(TIMEOUT_MICROSECONDS); + ou = sock.getOutputStream(); + ou.write('E'); + ou.flush(); + sock.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /* + * Assumes server is listening on any local address + */ + private static InetSocketAddress getConnectAddress(long serverSocket) throws Exception { + long sa = Address.get(Socket.APR_LOCAL, serverSocket); + Sockaddr addr = Address.getInfo(sa); + InetSocketAddress localAddress; + if (addr.family == Socket.APR_INET6) { + localAddress = new InetSocketAddress("::", addr.port); + } else { + localAddress = new InetSocketAddress("0.0.0.0", addr.port); + } + + // Need a local address of the same type (IPv4 or IPV6) as the + // configured bind address since the connector may be configured + // to not map between types. + InetAddress loopbackConnectAddress = null; + InetAddress linkLocalConnectAddress = null; + + Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddress = inetAddresses.nextElement(); + if (localAddress.getAddress().getClass().isAssignableFrom(inetAddress.getClass())) { + if (inetAddress.isLoopbackAddress()) { + if (loopbackConnectAddress == null) { + loopbackConnectAddress = inetAddress; + } + } else if (inetAddress.isLinkLocalAddress()) { + if (linkLocalConnectAddress == null) { + linkLocalConnectAddress = inetAddress; + } + } else { + // Use a non-link local, non-loop back address by default + return new InetSocketAddress(inetAddress, localAddress.getPort()); + } + } + } + } + // Prefer loop back over link local since on some platforms (e.g. + // OSX) some link local addresses are not included when listening on + // all local addresses. + if (loopbackConnectAddress != null) { + return new InetSocketAddress(loopbackConnectAddress, localAddress.getPort()); + } + if (linkLocalConnectAddress != null) { + return new InetSocketAddress(linkLocalConnectAddress, localAddress.getPort()); + } + // Fallback + return new InetSocketAddress("localhost", localAddress.getPort()); + } + } +} diff --git a/test/org/apache/tomcat/jni/TesterSSL.java b/test/org/apache/tomcat/jni/TesterSSL.java new file mode 100644 index 0000000..3ef57ee --- /dev/null +++ b/test/org/apache/tomcat/jni/TesterSSL.java @@ -0,0 +1,58 @@ +/* + * 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.tomcat.jni; + +import org.junit.Test; + +/* + * Helper class to investigate native memory leaks. Needs to be used with tools + * to monitor native memory usage. + * + * Note: Moving the Pool, SSLContext, SSL and BIO creation in/out of the loop + * can help identify where the memory is leaking. + */ +public class TesterSSL { + + @Test + public void testCreateDestroy() throws Exception { + Library.initialize(null); + SSL.initialize(null); + + long memoryPool = Pool.create(0); + long sslCtx = SSLContext.make(memoryPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); + + for (int i = 0; i < 10000000; i++) { + doNative(sslCtx); + if (i % 1000 == 0) { + System.gc(); + } + } + + SSLContext.free(sslCtx); + Pool.destroy(memoryPool); + + System.gc(); + } + + + private void doNative(long sslCtx) throws Exception { + long ssl = SSL.newSSL(sslCtx, true); + long bio = SSL.makeNetworkBIO(ssl); + SSL.freeBIO(bio); + SSL.freeSSL(ssl); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org