/** (C) Copyright 2011-2014 Chiral Behaviors, All Rights Reserved * * Licensed 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 com.hellblazer.process; import java.io.*; import java.net.ConnectException; import java.net.URL; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import org.apache.commons.io.input.Tailer; import org.apache.commons.io.input.TailerListener; import org.apache.commons.io.input.TailerListenerAdapter; import com.hellblazer.process.impl.JavaProcessImpl; import com.hellblazer.process.impl.ManagedProcessFactoryImpl; import com.hellblazer.utils.Condition; import com.hellblazer.utils.Utils; /** * @author Hal Hildebrand * */ public class JavaProcessTest extends ProcessTest { protected static final String TEST_DIR = "test-dirs/java-process-test"; protected static final String TEST_JAR = "test.jar"; protected File testDir; MBeanServerConnection connection; ManagedProcessFactoryImpl processFactory = new ManagedProcessFactoryImpl(); public void testClassExecution() throws Exception { copyTestClassFile(); JavaProcess process = new JavaProcessImpl(processFactory.create()); process.setArguments(new String[] { "-echo", "foo", "bar", "baz" }); process.setJavaClass(HelloWorld.class.getCanonicalName()); assertNull("No jar file set", process.getJarFile()); try { launchProcess(process); assertEquals("Process exited normally", 0, process.waitFor()); assertTrue("Process not active", !process.isActive()); try (BufferedReader reader = new BufferedReader( new InputStreamReader( process.getStdErr()))) { validateExpectedEchoLines(reader); } try (BufferedReader reader = new BufferedReader( new InputStreamReader( process.getStdOut()))) { assertEquals(HelloWorld.STARTUP_MSG, reader.readLine()); validateExpectedEchoLines(reader); } } finally { if (process != null) { process.destroy(); } } } public void testExitValue() throws Exception { copyTestClassFile(); JavaProcess process = new JavaProcessImpl(processFactory.create()); process.setArguments(new String[] { "-errno", "66" }); process.setJavaClass(HelloWorld.class.getCanonicalName()); process.setDirectory(testDir); process.setJavaExecutable(javaBin); try { launchProcess(process); assertEquals("Process exited abnormally", 66, process.waitFor()); assertTrue("Process not active", !process.isActive()); } finally { if (process != null) { process.destroy(); } } } public void testJarExecution() throws Exception { copyTestJarFile(); JavaProcess process = new JavaProcessImpl(processFactory.create()); process.setArguments(new String[] { "-echo", "hello" }); process.setJarFile(new File(testDir, TEST_JAR)); assertNull("No java class set", process.getJavaClass()); try { launchProcess(process); assertEquals("Process exited normally", 0, process.waitFor()); assertTrue("Process not active", !process.isActive()); } finally { if (process != null) { process.destroy(); } } } public void testLocalMBeanServerConnection() throws Exception { copyTestClassFile(); final JavaProcess process = new JavaProcessImpl(processFactory.create()); int sleepTime = 60000; process.setArguments(new String[] { "-jmx", Integer.toString(sleepTime) }); process.setJavaClass(HelloWorld.class.getCanonicalName()); process.setDirectory(testDir); process.setJavaExecutable(javaBin); try { launchProcess(process); Condition condition = new Condition() { @Override public boolean isTrue() { try { connection = process.getLocalMBeanServerConnection(HelloWorld.JMX_CONNECTION_NAME); return true; } catch (ConnectException e) { return false; } catch (NoLocalJmxConnectionException e) { return false; } catch (IOException e) { e.printStackTrace(); System.out.flush(); System.err.flush(); System.out.println("error"); fail("error retrieving JMX connection: " + e); return false; } } }; assertTrue("JMX connection established", Utils.waitForCondition(60 * 1000, condition)); Set<ObjectName> names = connection.queryNames(null, null); assertTrue(names.size() > 1); } finally { if (process != null) { process.destroy(); } } assertTrue("Process not active", !process.isActive()); } public void testOnDemandInputOutputStreams() throws Exception { copyTestClassFile(); JavaProcess process = new JavaProcessImpl(processFactory.create()); process.setArguments(new String[] { "-loglines" }); process.setJavaClass(HelloWorld.class.getCanonicalName()); process.setDirectory(testDir); process.setJavaExecutable(javaBin); // Try it before launching process try { process.getStdOutTail(200); fail("Expected an illegal thread state exception because the process hasn't started yet"); } catch (IllegalThreadStateException e) { System.out.println("Caught expected exception : " + e.getMessage()); } try { process.getStdErrTail(200); fail("Expected an illegal thread state exception because the process hasn't started yet"); } catch (IllegalThreadStateException e) { System.out.println("Caught expected exception : " + e.getMessage()); } try { launchProcess(process); String stdout = process.getStdOutTail(200).trim(); assertTrue(stdout.startsWith("Line #4800")); assertTrue(stdout.endsWith("Line #4999")); String stderr = process.getStdErrTail(200).trim(); assertTrue(stderr.startsWith("Line #4800")); assertTrue(stderr.endsWith("Line #4999")); } finally { if (process != null) { process.destroy(); } } } public void testTailStdInputOutputStreams() throws Exception { final List<String> lines = new CopyOnWriteArrayList<>(); TailerListener listener = new TailerListenerAdapter() { @Override public void handle(String line) { lines.add(line); } }; copyTestClassFile(); JavaProcess process = new JavaProcessImpl(processFactory.create()); String testLine = "hello"; process.setArguments(new String[] { "-readln", testLine }); process.setJavaClass(HelloWorld.class.getCanonicalName()); process.setDirectory(testDir); process.setJavaExecutable(javaBin); Tailer tailer = null; try { launchProcess(process); tailer = process.tailStdOut(listener); try (PrintWriter writer = new PrintWriter( new OutputStreamWriter( process.getStdIn()))) { writer.println(testLine); writer.flush(); } assertEquals("Process exited normally", 0, process.waitFor()); assertTrue("Process not active", !process.isActive()); Utils.waitForCondition(1000, new Condition() { @Override public boolean isTrue() { return lines.size() > 1; } }); assertEquals(2, lines.size()); assertEquals(testLine, lines.get(1)); tailer.stop(); } finally { if (tailer != null) { tailer.stop(); } process.destroy(); } } private void launchProcess(JavaProcess process) throws IOException { process.setDirectory(testDir); process.setJavaExecutable(javaBin); setupJavaClasspath(process); process.start(); assertTrue("Expected successful process start", new ProcessStartWatcher(process).waitForSuccessfulStartup()); // Give the process a chance to do its thing before launching into // evaluating output try { System.out.println("Waiting for process before evaluating output..."); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } private void validateExpectedEchoLines(BufferedReader reader) throws IOException { String line; line = reader.readLine(); assertEquals("foo", line); line = reader.readLine(); assertEquals("bar", line); line = reader.readLine(); assertEquals("baz", line); line = reader.readLine(); assertNull(line); } protected void copyTestClassFile() throws Exception { String classFileName = HelloWorld.class.getCanonicalName().replace('.', '/') + ".class"; URL classFile = getClass().getResource("/" + classFileName); assertNotNull(classFile); File copiedFile = new File(testDir, classFileName); assertTrue(copiedFile.getParentFile().mkdirs()); FileOutputStream out = new FileOutputStream(copiedFile); InputStream in = classFile.openStream(); byte[] buffer = new byte[1024]; for (int read = in.read(buffer); read != -1; read = in.read(buffer)) { out.write(buffer, 0, read); } in.close(); out.close(); } protected void copyTestJarFile() throws Exception { String classFileName = HelloWorld.class.getCanonicalName().replace('.', '/') + ".class"; URL classFile = getClass().getResource("/" + classFileName); assertNotNull(classFile); Manifest manifest = new Manifest(); Attributes attributes = manifest.getMainAttributes(); attributes.putValue("Manifest-Version", "1.0"); attributes.putValue("Main-Class", HelloWorld.class.getCanonicalName()); FileOutputStream fos = new FileOutputStream(new File(testDir, TEST_JAR)); JarOutputStream jar = new JarOutputStream(fos, manifest); JarEntry entry = new JarEntry(classFileName); jar.putNextEntry(entry); InputStream in = classFile.openStream(); byte[] buffer = new byte[1024]; for (int read = in.read(buffer); read != -1; read = in.read(buffer)) { jar.write(buffer, 0, read); } in.close(); jar.closeEntry(); jar.close(); } @Override protected void setUp() { System.setProperty("sun.net.client.defaultConnectTimeout", "10000"); System.setProperty("sun.net.client.defaultReadTimeout", "10000"); System.setProperty("javax.net.debug", "all"); Utils.initializeDirectory(TEST_DIR); testDir = new File(TEST_DIR); } }