/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.kernel.process.local; import com.liferay.portal.kernel.concurrent.NoticeableFuture; import com.liferay.portal.kernel.concurrent.ThreadPoolExecutor; import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.process.ProcessCallable; import com.liferay.portal.kernel.process.ProcessChannel; import com.liferay.portal.kernel.process.ProcessConfig; import com.liferay.portal.kernel.process.ProcessConfig.Builder; import com.liferay.portal.kernel.process.ProcessException; import com.liferay.portal.kernel.process.ProcessExecutor; import com.liferay.portal.kernel.process.TerminationProcessException; import com.liferay.portal.kernel.process.local.LocalProcessLauncher.ProcessContext; import com.liferay.portal.kernel.process.local.LocalProcessLauncher.ShutdownHook; import com.liferay.portal.kernel.process.log.ProcessOutputStream; import com.liferay.portal.kernel.test.CaptureHandler; import com.liferay.portal.kernel.test.JDKLoggerTestUtil; import com.liferay.portal.kernel.test.ReflectionTestUtil; import com.liferay.portal.kernel.test.SyncThrowableThread; import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor; import com.liferay.portal.kernel.util.CharPool; import com.liferay.portal.kernel.util.InetAddressUtil; import com.liferay.portal.kernel.util.SocketUtil; import com.liferay.portal.kernel.util.SocketUtil.ServerSocketConfigurator; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.SystemProperties; import com.liferay.portal.kernel.util.Validator; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.NotSerializableException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.Serializable; import java.io.WriteAbortedException; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.URL; import java.net.URLClassLoader; import java.nio.channels.ServerSocketChannel; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import org.junit.After; import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; /** * @author Shuyang Zhou */ public class LocalProcessExecutorTest { @ClassRule public static final CodeCoverageAssertor codeCoverageAssertor = new CodeCoverageAssertor() { @Override public void appendAssertClasses(List<Class<?>> assertClasses) { assertClasses.add(ProcessConfig.class); Collections.addAll( assertClasses, ProcessConfig.class.getDeclaredClasses()); assertClasses.add(LocalProcessLauncher.class); Collections.addAll( assertClasses, LocalProcessLauncher.class.getDeclaredClasses()); } }; @After public void tearDown() throws Exception { ExecutorService executorService = _getThreadPoolExecutor(); if (executorService != null) { executorService.shutdownNow(); executorService.awaitTermination(10, TimeUnit.SECONDS); _nullOutThreadPoolExecutor(); } } @Test public void testAttach1() throws Exception { // No attach ServerSocketChannel serverSocketChannel = SocketUtil.createServerSocketChannel( InetAddressUtil.getLoopbackInetAddress(), 12342, _serverSocketConfigurator); try (ServerSocket serverSocket = serverSocketChannel.socket()) { int port = serverSocket.getLocalPort(); _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), new AttachParentProcessCallable( AttachChildProcessCallable1.class.getName(), port)); Socket parentSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(parentSocket)); Socket childSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(childSocket)); // Kill parent ServerThread.exit(parentSocket); // Test alive 10 times for child process for (int i = 0; i < 10; i++) { Thread.sleep(100); Assert.assertTrue(ServerThread.isAlive(childSocket)); } // Kill child ServerThread.exit(childSocket); } } @Test public void testAttach2() throws Exception { // Attach ServerSocketChannel serverSocketChannel = SocketUtil.createServerSocketChannel( InetAddressUtil.getLoopbackInetAddress(), 12342, _serverSocketConfigurator); try (ServerSocket serverSocket = serverSocketChannel.socket()) { int port = serverSocket.getLocalPort(); _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), new AttachParentProcessCallable( AttachChildProcessCallable2.class.getName(), port)); Socket parentSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(parentSocket)); Socket childSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(childSocket)); // Kill parent ServerThread.exit(parentSocket); if (_log.isInfoEnabled()) { _log.info("Waiting subprocess to exit"); } long startTime = System.currentTimeMillis(); while (true) { Thread.sleep(10); if (!ServerThread.isAlive(childSocket)) { if (_log.isInfoEnabled()) { _log.info( "Subprocess exited. Waited " + (System.currentTimeMillis() - startTime) + " ms"); } return; } } } } @Test public void testAttach3() throws Exception { // Detach ServerSocketChannel serverSocketChannel = SocketUtil.createServerSocketChannel( InetAddressUtil.getLoopbackInetAddress(), 12342, _serverSocketConfigurator); try (ServerSocket serverSocket = serverSocketChannel.socket()) { int port = serverSocket.getLocalPort(); _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), new AttachParentProcessCallable( AttachChildProcessCallable3.class.getName(), port)); Socket parentSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(parentSocket)); Socket childSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(childSocket)); // Kill parent ServerThread.exit(parentSocket); if (_log.isInfoEnabled()) { _log.info("Waiting subprocess to exit..."); } long startTime = System.currentTimeMillis(); while (true) { Thread.sleep(10); if (!ServerThread.isAlive(childSocket)) { if (_log.isInfoEnabled()) { _log.info( "Subprocess exited. Waited " + (System.currentTimeMillis() - startTime) + " ms"); } return; } } } } @Test public void testAttach4() throws Exception { // Shutdown by interruption ServerSocketChannel serverSocketChannel = SocketUtil.createServerSocketChannel( InetAddressUtil.getLoopbackInetAddress(), 12342, _serverSocketConfigurator); try (ServerSocket serverSocket = serverSocketChannel.socket()) { int port = serverSocket.getLocalPort(); _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), new AttachParentProcessCallable( AttachChildProcessCallable4.class.getName(), port)); Socket parentSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(parentSocket)); Socket childSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(childSocket)); // Interrupt child process heartbeat thread ServerThread.interruptHeartbeatThread(childSocket); // Kill parent ServerThread.exit(parentSocket); } } @Test public void testAttach5() throws Exception { // Bad shutdown hook ServerSocketChannel serverSocketChannel = SocketUtil.createServerSocketChannel( InetAddressUtil.getLoopbackInetAddress(), 12342, _serverSocketConfigurator); try (ServerSocket serverSocket = serverSocketChannel.socket()) { int port = serverSocket.getLocalPort(); _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), new AttachParentProcessCallable( AttachChildProcessCallable5.class.getName(), port)); Socket parentSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(parentSocket)); Socket childSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(childSocket)); // Interrupt child process heartbeat thread ServerThread.interruptHeartbeatThread(childSocket); // Kill parent ServerThread.exit(parentSocket); } } @Test public void testAttach6() throws Exception { // NPE on heartbeat piping back ServerSocketChannel serverSocketChannel = SocketUtil.createServerSocketChannel( InetAddressUtil.getLoopbackInetAddress(), 12342, _serverSocketConfigurator); try (ServerSocket serverSocket = serverSocketChannel.socket()) { int port = serverSocket.getLocalPort(); _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), new AttachParentProcessCallable( AttachChildProcessCallable6.class.getName(), port)); Socket parentSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(parentSocket)); Socket childSocket = serverSocket.accept(); Assert.assertTrue(ServerThread.isAlive(childSocket)); // Null out child process' OOS to cause NPE in heartbeat thread ServerThread.nullOutOOS(childSocket); if (_log.isInfoEnabled()) { _log.info("Waiting subprocess to exit"); } long startTime = System.currentTimeMillis(); while (true) { Thread.sleep(10); if (!ServerThread.isAlive(childSocket)) { if (_log.isInfoEnabled()) { _log.info( "Subprocess exited. Waited " + (System.currentTimeMillis() - startTime) + " ms"); } break; } } // Kill parent ServerThread.exit(parentSocket); } } @Test public void testBrokenPiping() throws Exception { try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( LocalProcessExecutor.class.getName(), Level.SEVERE)) { List<LogRecord> logRecords = captureHandler.getLogRecords(); BrokenPipingProcessCallable brokenPipingProcessCallable = new BrokenPipingProcessCallable(); ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), brokenPipingProcessCallable); Future<Serializable> future = processChannel.getProcessNoticeableFuture(); try { future.get(); Assert.fail(); } catch (ExecutionException ee) { Throwable cause = ee.getCause(); Assert.assertTrue(cause instanceof ProcessException); Assert.assertEquals( "Corrupted object input stream", cause.getMessage()); } Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); Assert.assertEquals(logRecords.toString(), 1, logRecords.size()); String message = logRecords.get(0).getMessage(); int index = message.lastIndexOf(' '); Assert.assertTrue(index != -1); Assert.assertEquals( "Dumping content of corrupted object input stream to", message.substring(0, index)); File file = new File(message.substring(index + 1)); Assert.assertTrue(file.exists()); file.delete(); } } @Test public void testCancel() throws Exception { ReturnWithoutExitProcessCallable returnWithoutExitProcessCallable = new ReturnWithoutExitProcessCallable(""); ProcessChannel<String> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), returnWithoutExitProcessCallable); Future<String> future = processChannel.getProcessNoticeableFuture(); Assert.assertFalse(future.isCancelled()); Assert.assertFalse(future.isDone()); Assert.assertTrue(future.cancel(true)); try { future.get(); Assert.fail(); } catch (CancellationException ce) { } Assert.assertTrue(future.isCancelled()); Assert.assertTrue(future.isDone()); Assert.assertFalse(future.cancel(true)); } @Test public void testConcurrentCreateExecutorService() throws Exception { final AtomicReference<ExecutorService> atomicReference = new AtomicReference<>(); SyncThrowableThread<Void> syncThrowableThread = new SyncThrowableThread<>( new Callable<Void>() { @Override public Void call() throws Exception { ExecutorService executorService = _invokeGetThreadPoolExecutor(); atomicReference.set(executorService); return null; } }); ExecutorService executorService = null; synchronized (_localProcessExecutor) { syncThrowableThread.start(); while (syncThrowableThread.getState() != Thread.State.BLOCKED); executorService = _invokeGetThreadPoolExecutor(); } syncThrowableThread.sync(); Assert.assertSame(executorService, atomicReference.get()); } @Test public void testConstructor() { new LocalProcessLauncher(); } @Test public void testCrash() throws Exception { try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( LocalProcessExecutor.class.getName(), Level.OFF)) { // One crash KillJVMProcessCallable killJVMProcessCallable = new KillJVMProcessCallable(1); ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), killJVMProcessCallable); Future<Serializable> future = processChannel.getProcessNoticeableFuture(); try { future.get(); Assert.fail(); } catch (ExecutionException ee) { Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); Throwable throwable = ee.getCause(); Assert.assertSame( TerminationProcessException.class, throwable.getClass()); Assert.assertEquals( "Subprocess terminated with exit code 1", throwable.getMessage()); TerminationProcessException terminationProcessException = (TerminationProcessException)throwable; Assert.assertEquals( 1, terminationProcessException.getExitCode()); } // Zero crash killJVMProcessCallable = new KillJVMProcessCallable(0); processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), killJVMProcessCallable); future = processChannel.getProcessNoticeableFuture(); try { future.get(); Assert.fail(); } catch (ExecutionException ee) { Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); Throwable throwable = ee.getCause(); Assert.assertSame(ProcessException.class, throwable.getClass()); throwable = throwable.getCause(); Assert.assertSame(EOFException.class, throwable.getClass()); } } } @Test public void testCreateProcessContext() throws Exception { Constructor<ProcessContext> constructor = ProcessContext.class.getDeclaredConstructor(); constructor.setAccessible(true); constructor.newInstance(); Assert.assertNotNull(ProcessContext.getAttributes()); } @Test public void testDestroy() throws Exception { // Clean destroy _localProcessExecutor.destroy(); Assert.assertNull(_getThreadPoolExecutor()); // Idle destroy ExecutorService executorService = _invokeGetThreadPoolExecutor(); Assert.assertNotNull(executorService); Assert.assertNotNull(_getThreadPoolExecutor()); _localProcessExecutor.destroy(); Assert.assertNull(_getThreadPoolExecutor()); // Busy destroy executorService = _invokeGetThreadPoolExecutor(); Assert.assertNotNull(executorService); Assert.assertNotNull(_getThreadPoolExecutor()); DummyJob dummyJob = new DummyJob(); Future<Void> future = executorService.submit(dummyJob); dummyJob.waitUntilStarted(); _localProcessExecutor.destroy(); try { future.get(); Assert.fail(); } catch (ExecutionException ee) { Throwable throwable = ee.getCause(); Assert.assertTrue(throwable instanceof InterruptedException); } Assert.assertNull(_getThreadPoolExecutor()); // Concurrent destroy _invokeGetThreadPoolExecutor(); final LocalProcessExecutor referenceProcessExecutor = _localProcessExecutor; Thread thread = new Thread() { @Override public void run() { referenceProcessExecutor.destroy(); } }; synchronized (_localProcessExecutor) { thread.start(); while (thread.getState() != Thread.State.BLOCKED); _localProcessExecutor.destroy(); } thread.join(); _invokeGetThreadPoolExecutor(); _localProcessExecutor.destroy(); // Destroy after destroyed _localProcessExecutor.destroy(); Assert.assertNull(_getThreadPoolExecutor()); } @Test public void testException() throws Exception { DummyExceptionProcessCallable dummyExceptionProcessCallable = new DummyExceptionProcessCallable(); ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), dummyExceptionProcessCallable); Future<Serializable> future = processChannel.getProcessNoticeableFuture(); try { future.get(); Assert.fail(); } catch (ExecutionException ee) { Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); Throwable throwable = ee.getCause(); Assert.assertSame(ProcessException.class, throwable.getClass()); Assert.assertEquals( DummyExceptionProcessCallable.class.getName(), throwable.getMessage()); } RuntimeExceptionProcessCallable runtimeExceptionProcessCallable = new RuntimeExceptionProcessCallable(); processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), runtimeExceptionProcessCallable); future = processChannel.getProcessNoticeableFuture(); try { future.get(); Assert.fail(); } catch (ExecutionException ee) { Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); Throwable throwable = ee.getCause(); Assert.assertSame(ProcessException.class, throwable.getClass()); throwable = throwable.getCause(); Assert.assertSame(RuntimeException.class, throwable.getClass()); Assert.assertEquals( RuntimeExceptionProcessCallable.class.getName(), throwable.getMessage()); } } @Test public void testExceptionPipingBackProcessCallable() throws Exception { ExceptionPipingBackProcessCallable exceptionPipingBackProcessCallable = new ExceptionPipingBackProcessCallable(); try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( LocalProcessExecutor.class.getName(), Level.SEVERE)) { ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), exceptionPipingBackProcessCallable); NoticeableFuture<Serializable> noticeableFuture = processChannel.getProcessNoticeableFuture(); Assert.assertNull(noticeableFuture.get()); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertEquals(logRecords.toString(), 1, logRecords.size()); LogRecord logRecord = logRecords.get(0); Assert.assertEquals( "Unable to invoke generic process callable", logRecord.getMessage()); Throwable throwable = logRecord.getThrown(); Assert.assertSame(ProcessException.class, throwable.getClass()); Assert.assertEquals( DummyExceptionProcessCallable.class.getName(), throwable.getMessage()); } } @Test public void testExecuteOnDestroy() throws Exception { ExecutorService executorService = _invokeGetThreadPoolExecutor(); executorService.shutdownNow(); boolean result = executorService.awaitTermination(10, TimeUnit.SECONDS); Assert.assertTrue(result); DummyReturnProcessCallable dummyReturnProcessCallable = new DummyReturnProcessCallable(); try { _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), dummyReturnProcessCallable); Assert.fail(); } catch (ProcessException pe) { Throwable throwable = pe.getCause(); Assert.assertEquals( throwable.getClass(), RejectedExecutionException.class); } } @Test public void testGetWithTimeout() throws Exception { // Success return DummyReturnProcessCallable dummyReturnProcessCallable = new DummyReturnProcessCallable(); ProcessChannel<String> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), dummyReturnProcessCallable); Future<String> future = processChannel.getProcessNoticeableFuture(); String returnValue = future.get(100, TimeUnit.SECONDS); Assert.assertEquals( DummyReturnProcessCallable.class.getName(), returnValue); Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); // Timeout return ReturnWithoutExitProcessCallable returnWithoutExitProcessCallable = new ReturnWithoutExitProcessCallable(""); processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), returnWithoutExitProcessCallable); future = processChannel.getProcessNoticeableFuture(); try { future.get(1, TimeUnit.SECONDS); Assert.fail(); } catch (TimeoutException te) { } Assert.assertFalse(future.isCancelled()); Assert.assertFalse(future.isDone()); future.cancel(true); ExecutorService executorService = _getThreadPoolExecutor(); executorService.shutdownNow(); executorService.awaitTermination(10, TimeUnit.SECONDS); Assert.assertTrue(future.isCancelled()); Assert.assertTrue(future.isDone()); } @Test public void testLargeProcessCallable() throws Exception { byte[] largePayload = new byte[100 * 1024 * 1024]; Random random = new Random(); random.nextBytes(largePayload); EchoPayloadProcessCallable echoPayloadProcessCallable = new EchoPayloadProcessCallable(largePayload); ProcessChannel<byte[]> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), echoPayloadProcessCallable); Future<byte[]> future = processChannel.getProcessNoticeableFuture(); Assert.assertArrayEquals(largePayload, future.get()); Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); } @Test public void testLargeRuntimeClassPath() throws Exception { Builder builder = new Builder(); builder.setArguments(_createArguments(_JPDA_OPTIONS1)); char[] largeFileNameChars = new char[10 * 1024 * 1024]; largeFileNameChars[0] = CharPool.SLASH; for (int i = 1; i < largeFileNameChars.length; i++) { largeFileNameChars[i] = (char)('a' + (i % 26)); } String largeFileName = new String(largeFileNameChars); builder.setRuntimeClassPath(largeFileName); ProcessChannel<String> processChannel = _localProcessExecutor.execute( builder.build(), new EchoRuntimeClassPathProcessCallable()); Future<String> future = processChannel.getProcessNoticeableFuture(); Assert.assertEquals(largeFileName, future.get()); Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); } @Test public void testLeadingLog() throws Exception { try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( LocalProcessExecutor.class.getName(), Level.WARNING)) { // Warn level List<LogRecord> logRecords = captureHandler.getLogRecords(); String leadingLog = "Test leading log.\n"; String bodyLog = "Test body log.\n"; LeadingLogProcessCallable leadingLogProcessCallable = new LeadingLogProcessCallable(leadingLog, bodyLog); ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), leadingLogProcessCallable); Future<Serializable> future = processChannel.getProcessNoticeableFuture(); future.get(); Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); Assert.assertEquals(logRecords.toString(), 1, logRecords.size()); LogRecord logRecord = logRecords.get(0); Assert.assertEquals( "Found corrupt leading log " + leadingLog, logRecord.getMessage()); // Fine level logRecords = captureHandler.resetLogLevel(Level.FINE); leadingLogProcessCallable = new LeadingLogProcessCallable( leadingLog, bodyLog); processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), leadingLogProcessCallable); future = processChannel.getProcessNoticeableFuture(); future.get(); Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); Assert.assertEquals(logRecords.toString(), 2, logRecords.size()); LogRecord logRecord1 = logRecords.get(0); Assert.assertEquals( "Found corrupt leading log " + leadingLog, logRecord1.getMessage()); LogRecord logRecord2 = logRecords.get(1); String message = logRecord2.getMessage(); Assert.assertTrue( message.contains("Invoked generic process callable")); // Severe level logRecords = captureHandler.resetLogLevel(Level.SEVERE); leadingLogProcessCallable = new LeadingLogProcessCallable( leadingLog, bodyLog); processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), leadingLogProcessCallable); future = processChannel.getProcessNoticeableFuture(); future.get(); Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); Assert.assertEquals(logRecords.toString(), 0, logRecords.size()); } } @Test public void testLogging() throws Exception { PrintStream oldOutPrintStream = System.out; ByteArrayOutputStream outByteArrayOutputStream = new ByteArrayOutputStream(); PrintStream newOutPrintStream = new PrintStream( outByteArrayOutputStream, true); System.setOut(newOutPrintStream); PrintStream oldErrPrintStream = System.err; ByteArrayOutputStream errByteArrayOutputStream = new ByteArrayOutputStream(); PrintStream newErrPrintStream = new PrintStream( errByteArrayOutputStream, true); System.setErr(newErrPrintStream); File signalFile = new File("signal"); signalFile.delete(); try { String logMessage = "Log Message"; final LoggingProcessCallable loggingProcessCallable = new LoggingProcessCallable(logMessage, signalFile); final AtomicReference<Exception> exceptionAtomicReference = new AtomicReference<>(); Thread thread = new Thread() { @Override public void run() { try { ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), loggingProcessCallable); Future<Serializable> future = processChannel.getProcessNoticeableFuture(); future.get(); Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); } catch (Exception e) { exceptionAtomicReference.set(e); } } }; thread.start(); Assert.assertTrue(signalFile.createNewFile()); _waitForSignalFile(signalFile, false); Assert.assertTrue(signalFile.createNewFile()); thread.join(); Exception e = exceptionAtomicReference.get(); if (e != null) { throw e; } String outByteArrayOutputStreamString = outByteArrayOutputStream.toString(); Assert.assertTrue( outByteArrayOutputStreamString.contains(logMessage)); String errByteArrayOutputStreamString = errByteArrayOutputStream.toString(); Assert.assertTrue( errByteArrayOutputStreamString.contains(logMessage)); } finally { System.setOut(oldOutPrintStream); System.setErr(oldErrPrintStream); signalFile.delete(); } } @Test public void testNonprocessCallablePipingBackProcessCallable() throws Exception { NonprocessCallablePipingBackProcessCallable nonprocessCallablePipingBackProcessCallable = new NonprocessCallablePipingBackProcessCallable(); try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( LocalProcessExecutor.class.getName(), Level.INFO)) { ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), nonprocessCallablePipingBackProcessCallable); NoticeableFuture<Serializable> noticeableFuture = processChannel.getProcessNoticeableFuture(); noticeableFuture.get(); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertEquals(logRecords.toString(), 1, logRecords.size()); LogRecord logRecord = logRecords.get(0); Assert.assertEquals( "Received a nonprocess callable piping back string piping " + "back object", logRecord.getMessage()); } try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( LocalProcessExecutor.class.getName(), Level.OFF)) { ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), nonprocessCallablePipingBackProcessCallable); NoticeableFuture<Serializable> noticeableFuture = processChannel.getProcessNoticeableFuture(); noticeableFuture.get(); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertTrue(logRecords.isEmpty()); } } @Test public void testProcessChannelPiping() throws Exception { ReturnWithoutExitProcessCallable returnWithoutExitProcessCallable = new ReturnWithoutExitProcessCallable("Premature return value"); ProcessChannel<String> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), returnWithoutExitProcessCallable); Future<String> resultFuture = processChannel.write( new DummyReturnProcessCallable()); Assert.assertEquals( DummyReturnProcessCallable.class.getName(), resultFuture.get()); PrintStream oldErrPrintStream = System.err; ByteArrayOutputStream errByteArrayOutputStream = new ByteArrayOutputStream(); PrintStream newErrPrintStream = new PrintStream( errByteArrayOutputStream, true); System.setErr(newErrPrintStream); try { Future<Serializable> exceptionFuture = processChannel.write( new DummyExceptionProcessCallable()); try { exceptionFuture.get(); Assert.fail(); } catch (ExecutionException ee) { Throwable throwable = ee.getCause(); Assert.assertEquals( DummyExceptionProcessCallable.class.getName(), throwable.getMessage()); } Future<Serializable> interruptFuture = processChannel.write( new InterruptProcessCallable()); try { Assert.assertNull(interruptFuture.get()); } catch (CancellationException ce) { } } finally { System.setErr(oldErrPrintStream); String errLog = errByteArrayOutputStream.toString(); Assert.assertTrue( errLog.startsWith( "[" + returnWithoutExitProcessCallable.toString() + "]" + new ProcessException( DummyExceptionProcessCallable.class.getName()))); } Future<String> processFuture = processChannel.getProcessNoticeableFuture(); try { Assert.fail(processFuture.get()); } catch (ExecutionException ee) { Throwable throwable = ee.getCause(); throwable = throwable.getCause(); Assert.assertSame(InterruptedException.class, throwable.getClass()); } } @Test public void testPropertyPassing() throws Exception { Builder builder = new Builder(); List<String> arguments = _createArguments(_JPDA_OPTIONS1); String propertyKey = "test-key"; String propertyValue = "test-value"; arguments.add("-D" + propertyKey + "=" + propertyValue); builder.setArguments(arguments); ProcessChannel<String> processChannel = _localProcessExecutor.execute( builder.build(), new ReadPropertyProcessCallable(propertyKey)); Future<String> future = processChannel.getProcessNoticeableFuture(); Assert.assertEquals(propertyValue, future.get()); Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); } @Test public void testReturn() throws Exception { DummyReturnProcessCallable dummyReturnProcessCallable = new DummyReturnProcessCallable(); ProcessChannel<String> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), dummyReturnProcessCallable); Future<String> future = processChannel.getProcessNoticeableFuture(); Assert.assertEquals( DummyReturnProcessCallable.class.getName(), future.get()); Assert.assertFalse(future.isCancelled()); Assert.assertTrue(future.isDone()); Assert.assertFalse(future.cancel(true)); } @Test public void testReturnWithoutExit() throws Exception { ReturnWithoutExitProcessCallable returnWithoutExitProcessCallable = new ReturnWithoutExitProcessCallable("Premature return value"); ProcessChannel<String> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), returnWithoutExitProcessCallable); Future<String> future = processChannel.getProcessNoticeableFuture(); for (int i = 0; i < 10; i++) { try { future.get(1, TimeUnit.SECONDS); Assert.fail(); } catch (TimeoutException te) { } } try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( LocalProcessExecutor.class.getName(), Level.OFF)) { Set<Process> processes = _localProcessExecutor.destroy(); Assert.assertEquals(processes.toString(), 1, processes.size()); try { future.get(); Assert.fail(); } catch (CancellationException ce) { Assert.assertTrue(future.isCancelled()); Assert.assertTrue(future.isDone()); } Iterator<Process> iterator = processes.iterator(); Process process = iterator.next(); Assert.assertTrue(process.waitFor() > 0); } } @Test public void testUnserializablePipingBackProcessCallable() throws Exception { UnserializablePipingBackProcessCallable unserializablePipingBackProcessCallable = new UnserializablePipingBackProcessCallable(); try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( LocalProcessExecutor.class.getName(), Level.WARNING)) { ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), unserializablePipingBackProcessCallable); NoticeableFuture<Serializable> noticeableFuture = processChannel.getProcessNoticeableFuture(); try { noticeableFuture.get(); Assert.fail(); } catch (ExecutionException ee) { Throwable cause = ee.getCause(); Assert.assertSame(ProcessException.class, cause.getClass()); cause = cause.getCause(); Assert.assertSame( NotSerializableException.class, cause.getClass()); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertEquals( logRecords.toString(), 1, logRecords.size()); LogRecord logRecord = logRecords.get(0); Assert.assertEquals( "Caught a write aborted exception", logRecord.getMessage()); cause = logRecord.getThrown(); Assert.assertSame( WriteAbortedException.class, cause.getClass()); cause = cause.getCause(); Assert.assertSame( NotSerializableException.class, cause.getClass()); } } try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( LocalProcessExecutor.class.getName(), Level.OFF)) { ProcessChannel<Serializable> processChannel = _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), unserializablePipingBackProcessCallable); NoticeableFuture<Serializable> noticeableFuture = processChannel.getProcessNoticeableFuture(); try { noticeableFuture.get(); Assert.fail(); } catch (ExecutionException ee) { Throwable cause = ee.getCause(); Assert.assertSame(ProcessException.class, cause.getClass()); cause = cause.getCause(); Assert.assertSame( NotSerializableException.class, cause.getClass()); List<LogRecord> logRecords = captureHandler.getLogRecords(); Assert.assertTrue(logRecords.isEmpty()); } } } @Test public void testUnserializableProcessCallable() { UnserializableProcessCallable unserializableProcessCallable = new UnserializableProcessCallable(); try { _localProcessExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS1), unserializableProcessCallable); Assert.fail(); } catch (ProcessException pe) { Throwable throwable = pe.getCause(); Assert.assertTrue(throwable instanceof NotSerializableException); } } @Test public void testWrongJavaExecutable() { try { Builder builder = new Builder(); builder.setJavaExecutable("javax"); _localProcessExecutor.execute( builder.build(), new DummyReturnProcessCallable()); Assert.fail(); } catch (ProcessException pe) { Throwable throwable = pe.getCause(); Assert.assertTrue(throwable instanceof IOException); } } private static List<String> _createArguments(String jpdaOptions) { List<String> arguments = new ArrayList<>(); arguments.add( "-D" + SystemProperties.SYSTEM_PROPERTIES_QUIET + "=true"); if (Boolean.getBoolean("jvm.debug")) { arguments.add(jpdaOptions); arguments.add("-Djvm.debug=true"); } arguments.add("-Dliferay.mode=test"); arguments.add("-Dsun.zip.disableMemoryMapping=true"); String whipAgentLine = System.getProperty("whip.agent"); if (Validator.isNotNull(whipAgentLine)) { arguments.add(whipAgentLine); arguments.add("-Dwhip.agent=" + whipAgentLine); } String fileName = System.getProperty("whip.datafile"); if (fileName != null) { arguments.add("-Dwhip.datafile=" + fileName); } if (Boolean.getBoolean("whip.instrument.dump")) { arguments.add("-Dwhip.instrument.dump=true"); } arguments.add("-Dwhip.static.instrument=true"); arguments.add("-Dwhip.static.instrument.use.data.file=true"); return arguments; } private static ProcessConfig _createJPDAProcessConfig(String jpdaOption) { Builder builder = new Builder(); builder.setArguments(_createArguments(jpdaOption)); builder.setBootstrapClassPath(System.getProperty("java.class.path")); builder.setReactClassLoader( LocalProcessExecutorTest.class.getClassLoader()); return builder.build(); } private static Thread _getHeartbeatThread(boolean remove) { AtomicReference<? extends Thread> heartbeatThreadReference = ReflectionTestUtil.getFieldValue( ProcessContext.class, "_heartbeatThreadReference"); if (remove) { return heartbeatThreadReference.getAndSet(null); } else { return heartbeatThreadReference.get(); } } private static byte[] _toHeadlessSerializationData( Serializable serializable) throws IOException { UnsyncByteArrayOutputStream unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream(); try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(unsyncByteArrayOutputStream) { @Override protected void writeStreamHeader() { } }) { objectOutputStream.reset(); objectOutputStream.writeUnshared(serializable); } return unsyncByteArrayOutputStream.toByteArray(); } private static void _waitForSignalFile( File signalFile, boolean expectedExists) throws Exception { while (expectedExists != signalFile.exists()) { Thread.sleep(100); } } private ThreadPoolExecutor _getThreadPoolExecutor() throws Exception { Field field = LocalProcessExecutor.class.getDeclaredField( "_threadPoolExecutor"); field.setAccessible(true); return (ThreadPoolExecutor)field.get(_localProcessExecutor); } private ExecutorService _invokeGetThreadPoolExecutor() throws Exception { Method method = LocalProcessExecutor.class.getDeclaredMethod( "_getThreadPoolExecutor"); method.setAccessible(true); return (ExecutorService)method.invoke(_localProcessExecutor); } private void _nullOutThreadPoolExecutor() throws Exception { Field field = LocalProcessExecutor.class.getDeclaredField( "_threadPoolExecutor"); field.setAccessible(true); field.set(_localProcessExecutor, null); } private static final String _JPDA_OPTIONS1 = "-agentlib:jdwp=transport=dt_socket,address=8001,server=y,suspend=y"; private static final String _JPDA_OPTIONS2 = "-agentlib:jdwp=transport=dt_socket,address=8002,server=y,suspend=y"; private static final Log _log = LogFactoryUtil.getLog( LocalProcessExecutorTest.class); private static final ServerSocketConfigurator _serverSocketConfigurator = new ServerSocketConfigurator() { @Override public void configure(ServerSocket serverSocket) throws SocketException { serverSocket.setReuseAddress(true); } }; private final LocalProcessExecutor _localProcessExecutor = new LocalProcessExecutor(); private static class AttachChildProcessCallable1 implements ProcessCallable<Serializable> { public AttachChildProcessCallable1(int serverPort) { _serverPort = serverPort; } @Override public Serializable call() throws ProcessException { try { ServerThread serverThread = new ServerThread( Thread.currentThread(), "Child Server Thread", _serverPort); serverThread.start(); } catch (Exception e) { throw new ProcessException(e); } try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException ie) { } return null; } @Override public String toString() { Class<?> clazz = getClass(); return clazz.getSimpleName(); } private static final long serialVersionUID = 1L; private int _serverPort; } private static class AttachChildProcessCallable2 extends AttachChildProcessCallable1 { public AttachChildProcessCallable2(int serverPort) { super(serverPort); } @Override public Serializable call() throws ProcessException { try { try { ProcessContext.attach("Child Process", 100, null); throw new ProcessException("Shutdown hook is null"); } catch (IllegalArgumentException iae) { } boolean result = ProcessContext.attach( "Child Process", 100, new TestShutdownHook()); if (!result || !ProcessContext.isAttached()) { throw new ProcessException("Unable to attach"); } Thread.sleep(1000); result = ProcessContext.attach( "Child Process", 100, new TestShutdownHook()); if (result) { throw new ProcessException("Duplicate attach"); } super.call(); } catch (Exception e) { throw new ProcessException(e); } return null; } private static final long serialVersionUID = 1L; } private static class AttachChildProcessCallable3 extends AttachChildProcessCallable1 { public AttachChildProcessCallable3(int serverPort) { super(serverPort); } @Override public Serializable call() throws ProcessException { try { ProcessContext.detach(); boolean result = ProcessContext.attach( "Child Process", Long.MAX_VALUE, new TestShutdownHook()); if (!result || !ProcessContext.isAttached()) { throw new ProcessException("Unable to attach"); } Thread heartbeatThread = _getHeartbeatThread(false); while (heartbeatThread.getState() != Thread.State.TIMED_WAITING); ProcessContext.detach(); if (ProcessContext.isAttached()) { throw new ProcessException("Unable to detach"); } result = ProcessContext.attach( "Child Process", 100, new TestShutdownHook()); if (!result || !ProcessContext.isAttached()) { throw new ProcessException("Unable to attach"); } heartbeatThread = _getHeartbeatThread(true); ReflectionTestUtil.setFieldValue( heartbeatThread, "_detach", true); heartbeatThread.join(); if (ProcessContext.isAttached()) { throw new ProcessException("Unable to detach"); } result = ProcessContext.attach( "Child Process", 100, new TestShutdownHook()); if (!result || !ProcessContext.isAttached()) { throw new ProcessException("Unable to attach"); } super.call(); } catch (Exception e) { throw new ProcessException(e); } return null; } private static final long serialVersionUID = 1L; } private static class AttachChildProcessCallable4 extends AttachChildProcessCallable1 { public AttachChildProcessCallable4(int serverPort) { super(serverPort); } @Override public Serializable call() throws ProcessException { try { boolean result = ProcessContext.attach( "Child Process", Long.MAX_VALUE, new TestShutdownHook()); if (!result || !ProcessContext.isAttached()) { throw new ProcessException("Unable to attach"); } super.call(); } catch (Exception e) { throw new ProcessException(e); } return null; } private static final long serialVersionUID = 1L; } private static class AttachChildProcessCallable5 extends AttachChildProcessCallable1 { public AttachChildProcessCallable5(int serverPort) { super(serverPort); } @Override public Serializable call() throws ProcessException { try { boolean result = ProcessContext.attach( "Child Process", Long.MAX_VALUE, new TestShutdownHook(true)); Thread heartbeatThread = _getHeartbeatThread(false); heartbeatThread.setUncaughtExceptionHandler( new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { // Swallow unconcerned uncaught exception } }); if (!result || !ProcessContext.isAttached()) { throw new ProcessException("Unable to attach"); } super.call(); } catch (Exception e) { throw new ProcessException(e); } return null; } private static final long serialVersionUID = 1L; } private static class AttachChildProcessCallable6 extends AttachChildProcessCallable1 { public AttachChildProcessCallable6(int serverPort) { super(serverPort); } @Override public Serializable call() throws ProcessException { try { boolean result = ProcessContext.attach( "Child Process", 100, new NPEOOSShutdownHook()); if (!result || !ProcessContext.isAttached()) { throw new ProcessException("Unable to attach"); } super.call(); } catch (Exception e) { throw new ProcessException(e); } return null; } private static final long serialVersionUID = 1L; } private static class AttachParentProcessCallable implements ProcessCallable<Serializable> { public AttachParentProcessCallable(String className, int serverPort) throws Exception { _serverPort = serverPort; _processCallableClass = (Class<ProcessCallable<?>>)Class.forName( className); } @Override public Serializable call() throws ProcessException { Logger logger = Logger.getLogger(""); logger.setLevel(Level.FINE); try { ServerThread serverThread = new ServerThread( Thread.currentThread(), "Parent Server Thread", _serverPort); serverThread.start(); Constructor<ProcessCallable<?>> constructor = _processCallableClass.getConstructor(int.class); ProcessExecutor processExecutor = new LocalProcessExecutor(); processExecutor.execute( _createJPDAProcessConfig(_JPDA_OPTIONS2), constructor.newInstance(_serverPort)); } catch (Exception e) { throw new ProcessException(e); } try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException ie) { } return null; } @Override public String toString() { StringBundler sb = new StringBundler(7); Class<?> clazz = getClass(); sb.append(clazz.getSimpleName()); sb.append(StringPool.OPEN_PARENTHESIS); sb.append("className="); sb.append(_processCallableClass.getSimpleName()); sb.append(", serverPort="); sb.append(_serverPort); sb.append(StringPool.CLOSE_PARENTHESIS); return sb.toString(); } private static final long serialVersionUID = 1L; private final Class<ProcessCallable<?>> _processCallableClass; private int _serverPort; } private static class BrokenPipingProcessCallable implements ProcessCallable<Serializable> { public BrokenPipingProcessCallable() throws IOException { DummyReturnProcessCallable dummyReturnProcessCallable = new DummyReturnProcessCallable(); UnsyncByteArrayOutputStream unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream(); try (ObjectOutputStream objectOutputStream = new ObjectOutputStream( unsyncByteArrayOutputStream)) { objectOutputStream.writeObject(dummyReturnProcessCallable); } byte[] serializedData = unsyncByteArrayOutputStream.toByteArray(); serializedData[5] = (byte)(serializedData[5] + 1); _brokenPipingData = serializedData; } @Override public Serializable call() throws ProcessException { try { FileOutputStream fileOutputStream = new FileOutputStream( FileDescriptor.out); fileOutputStream.write(_brokenPipingData); fileOutputStream.flush(); } catch (Exception e) { throw new ProcessException(e); } return null; } @Override public String toString() { Class<?> clazz = getClass(); return clazz.getSimpleName(); } private static final long serialVersionUID = 1L; private final byte[] _brokenPipingData; } private static class DummyExceptionProcessCallable implements ProcessCallable<Serializable> { @Override public Serializable call() throws ProcessException { throw new ProcessException( DummyExceptionProcessCallable.class.getName()); } @Override public String toString() { Class<?> clazz = getClass(); return clazz.getSimpleName(); } private static final long serialVersionUID = 1L; } private static class DummyJob implements Callable<Void> { public DummyJob() { _countDownLatch = new CountDownLatch(1); } @Override public Void call() throws Exception { _countDownLatch.countDown(); Thread.sleep(Long.MAX_VALUE); return null; } public void waitUntilStarted() throws InterruptedException { _countDownLatch.await(); } private final CountDownLatch _countDownLatch; } private static class DummyReturnProcessCallable implements ProcessCallable<String> { @Override public String call() { return DummyReturnProcessCallable.class.getName(); } @Override public String toString() { Class<?> clazz = getClass(); return clazz.getSimpleName(); } private static final long serialVersionUID = 1L; } private static class EchoPayloadProcessCallable implements ProcessCallable<byte[]> { public EchoPayloadProcessCallable(byte[] payload) { _payload = payload; } @Override public byte[] call() { return _payload; } private final byte[] _payload; } private static class EchoRuntimeClassPathProcessCallable implements ProcessCallable<String> { @Override public String call() { Thread currentThread = Thread.currentThread(); URLClassLoader urlClassLoader = (URLClassLoader)currentThread.getContextClassLoader(); URL[] urls = urlClassLoader.getURLs(); StringBundler sb = new StringBundler(urls.length * 2); for (URL url : urls) { String path = url.getPath(); int index = path.indexOf(":/"); if (index != -1) { path = path.substring(index + 1); } if (path.endsWith(StringPool.SLASH)) { path = path.substring(0, path.length() - 1); } sb.append(path); sb.append(File.pathSeparator); } if (sb.index() > 0) { sb.setIndex(sb.index() - 1); } return sb.toString(); } } private static class ExceptionPipingBackProcessCallable implements ProcessCallable<Serializable> { @Override public Serializable call() throws ProcessException { ProcessOutputStream processOutputStream = ProcessContext.getProcessOutputStream(); try { processOutputStream.writeProcessCallable( new DummyExceptionProcessCallable()); } catch (IOException ioe) { throw new ProcessException(ioe); } return null; } private static final long serialVersionUID = 1L; } private static class InterruptProcessCallable implements ProcessCallable<Serializable> { @Override public Serializable call() throws ProcessException { BlockingQueue<Thread> threadBlockingQueue = ReturnWithoutExitProcessCallable._threadBlockingQueue; try { Thread thread = threadBlockingQueue.take(); thread.interrupt(); } catch (InterruptedException ie) { throw new ProcessException(ie); } return null; } } private static class KillJVMProcessCallable implements ProcessCallable<Serializable> { public KillJVMProcessCallable(int exitCode) { _exitCode = exitCode; } @Override public Serializable call() { System.exit(_exitCode); return null; } @Override public String toString() { StringBundler sb = new StringBundler(5); Class<?> clazz = getClass(); sb.append(clazz.getSimpleName()); sb.append(StringPool.OPEN_PARENTHESIS); sb.append("exitCode="); sb.append(_exitCode); sb.append(StringPool.CLOSE_PARENTHESIS); return sb.toString(); } private static final long serialVersionUID = 1L; private final int _exitCode; } private static class LeadingLogProcessCallable implements ProcessCallable<Serializable> { public LeadingLogProcessCallable(String leadingLog, String bodyLog) { _leadingLog = leadingLog; _bodyLog = bodyLog; } @Override public Serializable call() throws ProcessException { try { FileOutputStream fileOutputStream = new FileOutputStream( FileDescriptor.out); fileOutputStream.write(_leadingLog.getBytes(StringPool.UTF8)); fileOutputStream.flush(); System.out.print(_bodyLog); System.out.flush(); // Forcibly restore System.out. This is a necessary protection // for code coverage. Cobertura's collector thread will output // to System.out after the subprocess's main thread has exited. // That information will be captured by the parent unit test // process which will cause an assert Assert.failure. System.setOut(new PrintStream(fileOutputStream)); } catch (Exception e) { throw new ProcessException(e); } return null; } @Override public String toString() { StringBundler sb = new StringBundler(7); Class<?> clazz = getClass(); sb.append(clazz.getSimpleName()); sb.append(StringPool.OPEN_PARENTHESIS); sb.append("leadingLog="); sb.append(_leadingLog); sb.append(", bodyLog="); sb.append(_bodyLog); sb.append(StringPool.CLOSE_PARENTHESIS); return sb.toString(); } private static final long serialVersionUID = 1L; private final String _bodyLog; private final String _leadingLog; } private static class LoggingProcessCallable implements ProcessCallable<Serializable> { public LoggingProcessCallable(String logMessage, File signalFile) { _logMessage = logMessage; _signalFile = signalFile; } @Override public Serializable call() throws ProcessException { try { _waitForSignalFile(_signalFile, true); System.out.print(_logMessage); System.err.print(_logMessage); boolean result = _signalFile.delete(); if (!result) { throw new ProcessException( "Unable to remove file " + _signalFile.getAbsolutePath()); } _waitForSignalFile(_signalFile, true); } catch (Exception e) { throw new ProcessException(e); } return null; } @Override public String toString() { StringBundler sb = new StringBundler(5); Class<?> clazz = getClass(); sb.append(clazz.getSimpleName()); sb.append(StringPool.OPEN_PARENTHESIS); sb.append("logMessage="); sb.append(_logMessage); sb.append(StringPool.CLOSE_PARENTHESIS); return sb.toString(); } private static final long serialVersionUID = 1L; private final String _logMessage; private final File _signalFile; } private static class NonprocessCallablePipingBackProcessCallable implements ProcessCallable<Serializable> { @Override public Serializable call() throws ProcessException { try { synchronized (System.out) { System.out.flush(); OutputStream outputStream = new FileOutputStream( FileDescriptor.out); outputStream.write( _toHeadlessSerializationData( "string piping back object")); } } catch (IOException ioe) { throw new ProcessException(ioe); } return null; } private static final long serialVersionUID = 1L; } private static class NPEOOSShutdownHook implements ShutdownHook { public NPEOOSShutdownHook() { ProcessOutputStream processOutputStream = ProcessContext.getProcessOutputStream(); _oldObjectOutputStream = ReflectionTestUtil.getFieldValue( processOutputStream, "_objectOutputStream"); _thread = Thread.currentThread(); } @Override public boolean shutdown(int shutdownCode, Throwable shutdownError) { try { ProcessOutputStream processOutputStream = ProcessContext.getProcessOutputStream(); ReflectionTestUtil.setFieldValue( processOutputStream, "_objectOutputStream", _oldObjectOutputStream); } catch (Exception e) { throw new RuntimeException(e); } _thread.interrupt(); return true; } private final ObjectOutputStream _oldObjectOutputStream; private Thread _thread; } private static class ReadPropertyProcessCallable implements ProcessCallable<String> { public ReadPropertyProcessCallable(String propertyKey) { _propertyKey = propertyKey; } @Override public String call() { return System.getProperty(_propertyKey); } @Override public String toString() { StringBundler sb = new StringBundler(5); Class<?> clazz = getClass(); sb.append(clazz.getSimpleName()); sb.append(StringPool.OPEN_PARENTHESIS); sb.append("propertyKey="); sb.append(_propertyKey); sb.append(StringPool.CLOSE_PARENTHESIS); return sb.toString(); } private static final long serialVersionUID = 1L; private final String _propertyKey; } private static class ReturnWithoutExitProcessCallable implements ProcessCallable<String> { public ReturnWithoutExitProcessCallable(String returnValue) { _returnValue = returnValue; } @Override public String call() throws ProcessException { try { ProcessOutputStream processOutputStream = ProcessContext.getProcessOutputStream(); // Forcibly write a premature ReturnProcessCallable processOutputStream.writeProcessCallable( new ReturnProcessCallable<String>(_returnValue)); _threadBlockingQueue.put(Thread.currentThread()); Thread.sleep(Long.MAX_VALUE); } catch (Exception e) { throw new ProcessException(e); } return null; } @Override public String toString() { StringBundler sb = new StringBundler(5); Class<?> clazz = getClass(); sb.append(clazz.getSimpleName()); sb.append(StringPool.OPEN_PARENTHESIS); sb.append("returnValue="); sb.append(_returnValue); sb.append(StringPool.CLOSE_PARENTHESIS); return sb.toString(); } private static final BlockingQueue<Thread> _threadBlockingQueue = new SynchronousQueue<>(); private static final long serialVersionUID = 1L; private final String _returnValue; } private static class RuntimeExceptionProcessCallable implements ProcessCallable<Serializable> { @Override public Serializable call() { throw new RuntimeException( RuntimeExceptionProcessCallable.class.getName()); } @Override public String toString() { Class<?> clazz = getClass(); return clazz.getSimpleName(); } private static final long serialVersionUID = 1L; } private static class ServerThread extends Thread { public static void exit(Socket socket) throws Exception { InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); outputStream.write(_CODE_EXIT); socket.shutdownOutput(); int code = inputStream.read(); Assert.assertEquals(-1, code); socket.close(); } public static void interruptHeartbeatThread(Socket socket) throws Exception { InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); outputStream.write(_CODE_INTERRUPT); socket.shutdownOutput(); int code = inputStream.read(); Assert.assertEquals(-1, code); socket.close(); } public static boolean isAlive(Socket socket) { try { InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); outputStream.write(_CODE_ECHO); outputStream.flush(); if (inputStream.read() == _CODE_ECHO) { return true; } else { return false; } } catch (Exception e) { return false; } } public static void nullOutOOS(Socket socket) throws Exception { InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); outputStream.write(_CODE_NULL_OUT_OOS); outputStream.flush(); int code = inputStream.read(); Assert.assertEquals( "Unable to null out OOS because of code " + code, _CODE_NULL_OUT_OOS, code); } public ServerThread(Thread mainThread, String name, int serverPort) throws Exception { _mainThread = mainThread; _socket = new Socket( InetAddressUtil.getLoopbackInetAddress(), serverPort); setName(name); } @Override public void run() { try { InputStream inputStream = _socket.getInputStream(); OutputStream outputStream = _socket.getOutputStream(); int command = 0; while (((command = inputStream.read()) != -1) && _mainThread.isAlive()) { switch (command) { case _CODE_ECHO : outputStream.write(_CODE_ECHO); outputStream.flush(); break; case _CODE_EXIT : break; case _CODE_INTERRUPT : Thread heartbeatThread = _getHeartbeatThread(false); heartbeatThread.interrupt(); heartbeatThread.join(); break; case _CODE_NULL_OUT_OOS : ReflectionTestUtil.setFieldValue( ProcessContext.getProcessOutputStream(), "_objectOutputStream", null); outputStream.write(_CODE_NULL_OUT_OOS); outputStream.flush(); break; } } } catch (Exception e) { } finally { try { _socket.close(); _mainThread.interrupt(); _mainThread.join(); } catch (Exception e) { } } } private static final int _CODE_ECHO = 1; private static final int _CODE_EXIT = 2; private static final int _CODE_INTERRUPT = 3; private static final int _CODE_NULL_OUT_OOS = 4; private final Thread _mainThread; private final Socket _socket; } private static class TestShutdownHook implements ShutdownHook { public TestShutdownHook() { this(false); } public TestShutdownHook(boolean failToShutdown) { _failToShutdown = failToShutdown; _thread = Thread.currentThread(); } @Override public boolean shutdown(int shutdownCode, Throwable shutdownThrowable) { _thread.interrupt(); if (_failToShutdown) { throw new RuntimeException(); } return true; } private final boolean _failToShutdown; private Thread _thread; } private static class UnserializablePipingBackProcessCallable implements ProcessCallable<Serializable> { @Override public Serializable call() throws ProcessException { ProcessOutputStream processOutputStream = ProcessContext.getProcessOutputStream(); try { processOutputStream.writeProcessCallable( new UnserializableProcessCallable()); } catch (IOException ioe) { throw new ProcessException(ioe); } return null; } private static final long serialVersionUID = 1L; } private static class UnserializableProcessCallable implements ProcessCallable<Serializable> { @Override public Serializable call() { return UnserializableProcessCallable.class.getName(); } @Override public String toString() { Class<?> clazz = getClass(); return clazz.getSimpleName(); } private static final long serialVersionUID = 1L; @SuppressWarnings("unused") private Object _unserializableObject = new Object(); } }