ace-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject svn commit: r1327960 [3/3] - in /ace/trunk: ./ ace-processlauncher/ ace-processlauncher/src/ ace-processlauncher/src/main/ ace-processlauncher/src/main/java/ ace-processlauncher/src/main/java/org/ ace-processlauncher/src/main/java/org/apache/ ace-proce...
Date Thu, 19 Apr 2012 14:24:38 GMT
Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessManagerImplTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessManagerImplTest.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessManagerImplTest.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessManagerImplTest.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,317 @@
+/*
+ * 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.ace.processlauncher.test.impl;
+
+import static org.apache.ace.processlauncher.test.impl.TestUtil.getOSName;
+import static org.apache.ace.processlauncher.test.impl.TestUtil.sleep;
+
+import java.io.File;
+
+import junit.framework.TestCase;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.impl.LaunchConfigurationImpl;
+import org.apache.ace.processlauncher.impl.ProcessManager;
+import org.apache.ace.processlauncher.impl.ProcessManagerImpl;
+import org.apache.ace.test.utils.TestUtils;
+import org.osgi.service.log.LogService;
+
+/**
+ * Test cases for {@link ProcessManager}.
+ */
+public class ProcessManagerImplTest extends TestCase {
+
+    private ProcessManagerImpl m_processManager;
+
+    /**
+     * Tests that launching a simple process works for a UNIX-derived operating system.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testLaunchProcessOnUnixDerivativeOk() throws Exception {
+        // This test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        LaunchConfiguration launchConfig = createLaunchConfiguration("/bin/sh", "-c", "sleep 1 && exit 1");
+
+        int processCountBefore = m_processManager.getRunningProcessesCount();
+
+        m_processManager.launch("myPid", launchConfig);
+
+        // make sure we sleep a little to ensure the process is started
+        sleep(100);
+
+        int processCountAfter = m_processManager.getRunningProcessesCount();
+
+        assertTrue(processCountAfter > processCountBefore);
+
+        assertEquals(processCountAfter, m_processManager.getRunningProcessesCount());
+
+        // make sure we sleep a little longer to allow the process to finish...
+        sleep(1000);
+
+        assertEquals(processCountBefore, m_processManager.getRunningProcessesCount());
+    }
+
+    /**
+     * Tests that launching a simple process works for a Windows operating system.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testLaunchProcessOnWindowsOk() throws Exception {
+        // This test will only work on Windows!
+        if (!getOSName().contains("windows")) {
+            return;
+        }
+
+        LaunchConfiguration launchConfig = createLaunchConfiguration("PING", "-n", "1", "127.0.0.1");
+
+        int processCountBefore = m_processManager.getRunningProcessesCount();
+
+        m_processManager.launch("myPid", launchConfig);
+
+        // make sure we sleep a little to ensure the process is started
+        sleep(100);
+
+        int processCountAfter = m_processManager.getRunningProcessesCount();
+
+        assertTrue(processCountAfter > processCountBefore);
+
+        assertEquals(processCountAfter, m_processManager.getRunningProcessesCount());
+
+        // make sure we sleep a little longer to allow the process to finish...
+        sleep(1000);
+
+        assertEquals(processCountBefore, m_processManager.getRunningProcessesCount());
+    }
+
+    /**
+     * Tests that respawning a simple process works for a UNIX-derived operating system.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testRespawnProcessOnUnixDerivativeOk() throws Exception {
+        // This test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        int count = 2;
+        File tmpFile = File.createTempFile("ace", null);
+        String tmpFilename = tmpFile.getAbsolutePath();
+
+        // Seems daunting, but what this command does is simply count the number
+        // of lines in a given file (= tmpFile), and append this to that same
+        // file; it uses this number to create an exit value, which counts from
+        // <count> back to 0. This way, we can test whether the respawn
+        // functionality works, as this currently checks for certain exit
+        // values...
+        String script =
+            String.format("L=$(cat %1$s | wc -l)" + "&& echo $L >> %1$s && exit $((%2$d-$L))", tmpFilename, count);
+
+        LaunchConfiguration launchConfig =
+            createLaunchConfiguration(true /* respawnAutomatically */, "/bin/bash", "-c", script);
+
+        int processCountBefore = m_processManager.getRunningProcessesCount();
+
+        m_processManager.launch("myPid", launchConfig);
+
+        // sleep a little to ensure the process is started...
+        sleep(10);
+
+        assertEquals(processCountBefore + 1, m_processManager.getRunningProcessesCount());
+
+        // make sure we sleep a little longer to allow the process to finish...
+        sleep(500);
+
+        assertEquals(processCountBefore, m_processManager.getRunningProcessesCount());
+
+        String testResult = TestUtil.slurpFile(tmpFile);
+        assertTrue(testResult, testResult.matches("(?s)^0\n1\n2\n$"));
+    }
+
+    /**
+     * Tests that terminating all running processes works.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testShutdownOnUnixDerivativeOk() throws Exception {
+        // This test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        LaunchConfiguration launchConfig = createLaunchConfiguration("/bin/sh", "-c", "sleep 10");
+
+        int processCountBefore = m_processManager.getRunningProcessesCount();
+
+        for (int i = 0; i < 5; i++) {
+            m_processManager.launch(String.format("myPid%d", i), launchConfig);
+        }
+
+        // make sure we sleep a little to ensure the processes are started
+        sleep(100);
+
+        int processCountAfter = m_processManager.getRunningProcessesCount();
+
+        assertTrue(processCountAfter > processCountBefore);
+
+        // Shut down the process manager; should terminate all running
+        // processes...
+        m_processManager.shutdown();
+
+        assertEquals(processCountBefore, m_processManager.getRunningProcessesCount());
+    }
+
+    /**
+     * Tests that terminating all running processes works.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testShutdownOnWindowsOk() throws Exception {
+        // This test will only work on Windows!
+        if (!getOSName().contains("windows")) {
+            return;
+        }
+
+        LaunchConfiguration launchConfig = createLaunchConfiguration("PING", "-n", "11", "127.0.0.1");
+
+        int processCountBefore = m_processManager.getRunningProcessesCount();
+
+        for (int i = 0; i < 5; i++) {
+            m_processManager.launch(String.format("myPid%d", i), launchConfig);
+        }
+
+        // make sure we sleep a little to ensure the processes are started
+        sleep(100);
+
+        int processCountAfter = m_processManager.getRunningProcessesCount();
+
+        assertTrue(processCountAfter > processCountBefore);
+
+        // Shut down the process manager; should terminate all running
+        // processes...
+        m_processManager.shutdown();
+
+        assertEquals(processCountBefore, m_processManager.getRunningProcessesCount());
+    }
+
+    /**
+     * Tests that terminating a single process works.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testTerminateProcessOnUnixDerivativeOk() throws Exception {
+        // This test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        LaunchConfiguration launchConfig = createLaunchConfiguration("/bin/sh", "-c", "sleep 10");
+
+        int processCountBefore = m_processManager.getRunningProcessesCount();
+
+        final String pid = "myPid";
+        m_processManager.launch(pid, launchConfig);
+
+        // make sure we sleep a little to ensure the process is started
+        sleep(100);
+
+        int processCountAfterLaunch = m_processManager.getRunningProcessesCount();
+
+        assertTrue(processCountAfterLaunch > processCountBefore);
+
+        m_processManager.terminate(pid);
+
+        int processCountAfterTerminate = m_processManager.getRunningProcessesCount();
+
+        assertTrue(processCountAfterTerminate == processCountBefore);
+    }
+
+    /**
+     * Tests that terminating a single process works.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testTerminateProcessOnWindowsOk() throws Exception {
+        // This test will not work on Windows!
+        if (!getOSName().contains("windows")) {
+            return;
+        }
+
+        LaunchConfiguration launchConfig = createLaunchConfiguration("PING", "-n", "11", "127.0.0.1");
+
+        int processCountBefore = m_processManager.getRunningProcessesCount();
+
+        final String pid = "myPid";
+        m_processManager.launch(pid, launchConfig);
+
+        // make sure we sleep a little to ensure the process is started
+        sleep(100);
+
+        int processCountAfterLaunch = m_processManager.getRunningProcessesCount();
+
+        assertTrue(processCountAfterLaunch > processCountBefore);
+
+        m_processManager.terminate(pid);
+
+        int processCountAfterTerminate = m_processManager.getRunningProcessesCount();
+
+        assertTrue(processCountAfterTerminate == processCountBefore);
+    }
+
+    /**
+     * Set up for this test case.
+     */
+    @Override
+    protected void setUp() {
+        m_processManager = new ProcessManagerImpl();
+        TestUtils.configureObject(m_processManager, LogService.class);
+    }
+
+    /**
+     * Creates a new launch configuration for the given executable and arguments.
+     * 
+     * @param respawnAutomatically <code>true</code> if the process should be respawned
+     *        automatically upon a non-zero exit value;
+     * @param execName the name of the executable;
+     * @param execArgs the (optional) arguments.
+     * 
+     * @return a {@link LaunchConfigurationImpl} instance, never <code>null</code>.
+     */
+    private LaunchConfiguration createLaunchConfiguration(boolean respawnAutomatically, String execName,
+        String... execArgs) {
+        return new LaunchConfigurationImpl(1, "/tmp", execName, execArgs, 0, null /* processStreamListenerFilter */,
+            null /* processLifecycleListenerFilter */, respawnAutomatically);
+    }
+
+    /**
+     * Creates a new launch configuration for the given executable and arguments.
+     * 
+     * @param execName the name of the executable;
+     * @param execArgs the (optional) arguments.
+     * 
+     * @return a {@link LaunchConfigurationImpl} instance, never <code>null</code>.
+     */
+    private LaunchConfiguration createLaunchConfiguration(String execName, String... execArgs) {
+        return createLaunchConfiguration(false /* respawnAutomatically */, execName, execArgs);
+    }
+}

Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/StringSplitterTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/StringSplitterTest.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/StringSplitterTest.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/StringSplitterTest.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,258 @@
+/*
+ * 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.ace.processlauncher.test.impl;
+
+import static org.apache.ace.processlauncher.impl.StringSplitter.split;
+
+import org.apache.ace.processlauncher.impl.StringSplitter;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for {@link StringSplitter}.
+ */
+public class StringSplitterTest extends TestCase {
+
+    /**
+     * Test double quoted command line argument.
+     */
+    public void testDoubleQuotedCommandLineArgumentOk() {
+        String[] result =
+            split("\\\"fwOption=org.osgi.framework.system.packages.extra=org.w3c.dom.tral,org.w3c.dom.html,org.w3c.dom.ranges,sun.reflect,org.osgi.service.deploymentadmin;version=\"1.0\",org.osgi.service.deploymentadmin.spi;version=\"1.0\",org.osgi.service.cm;version=\"1.3\",org.osgi.service.event;version=\"1.2\",org.osgi.service.log;version=\"1.3\",org.osgi.service.metatype;version=\"1.1\",org.apache.ace.log;version=\"0.8.0\"\\\"");
+        assertArrayEquals(
+            new String[] { "\"fwOption=org.osgi.framework.system.packages.extra=org.w3c.dom.tral,org.w3c.dom.html,org.w3c.dom.ranges,sun.reflect,org.osgi.service.deploymentadmin;version=\"1.0\",org.osgi.service.deploymentadmin.spi;version=\"1.0\",org.osgi.service.cm;version=\"1.3\",org.osgi.service.event;version=\"1.2\",org.osgi.service.log;version=\"1.3\",org.osgi.service.metatype;version=\"1.1\",org.apache.ace.log;version=\"0.8.0\"\"" },
+            result);
+    }
+
+    /**
+     * Test double quoted string.
+     */
+    public void testDoubleQuotedStringOk() {
+        String[] result = split("\"hello world\"");
+        assertArrayEquals(new String[] { "\"hello world\"" }, result);
+    }
+
+    /**
+     * Test double quoted string with trailing text.
+     */
+    public void testDoubleQuotedStringWithTrailingTextOk() {
+        String[] result = split("\"hello world\" foo-bar");
+        assertArrayEquals(new String[] { "\"hello world\"", "foo-bar" }, result);
+    }
+
+    /**
+     * Test double quoted words.
+     */
+    public void testDoubleQuotedWordsOk() {
+        String[] result = split("\"hello\" \"world\"");
+        assertArrayEquals(new String[] { "\"hello\"", "\"world\"" }, result);
+    }
+
+    /**
+     * Test double quoted words omit quotes.
+     */
+    public void testDoubleQuotedWordsOmitQuotesOk() {
+        String[] result = split("\"hello\" \"world\"", false /* includeQuotes */);
+        assertArrayEquals(new String[] { "hello", "world" }, result);
+    }
+
+    /**
+     * Test escaped backslash in string.
+     */
+    public void testEscapedBackslashInStringOk() {
+        String[] result = split("hello\\\\ world");
+        assertArrayEquals(new String[] { "hello\\", "world" }, result);
+    }
+
+    /**
+     * Test escaped backslash string in double quotes.
+     */
+    public void testEscapedBackslashStringInDoubleQuotesOk() {
+        String[] result = split("\"hello\\\\ world\"");
+        assertArrayEquals(new String[] { "\"hello\\ world\"" }, result);
+    }
+
+    /**
+     * Test escaped double quoted in single quoted string.
+     */
+    public void testEscapedDoubleQuotedInSingleQuotedStringOk() {
+        String[] result = split("'\"hello world\"'");
+        assertArrayEquals(new String[] { "'\"hello world\"'" }, result);
+    }
+
+    /**
+     * Test escaped double quoted string.
+     */
+    public void testEscapedDoubleQuotedStringOk() {
+        String[] result = split("\\\"hello world\\\"");
+        assertArrayEquals(new String[] { "\"hello", "world\"" }, result);
+    }
+
+    /**
+     * Test escaped key value pair.
+     */
+    public void testEscapedKeyValuePairOk() {
+        String[] result = split("key=\\'qux qoo\\'");
+        assertArrayEquals(new String[] { "key='qux", "qoo'" }, result);
+    }
+
+    /**
+     * Test escaped single quoted in double quoted string.
+     */
+    public void testEscapedSingleQuotedInDoubleQuotedStringOk() {
+        String[] result = split("\"\\'hello world\\'\"");
+        assertArrayEquals(new String[] { "\"'hello world'\"" }, result);
+    }
+
+    /**
+     * Test escaped single quoted string.
+     */
+    public void testEscapedSingleQuotedStringOk() {
+        String[] result = split("\\'hello world\\'");
+        assertArrayEquals(new String[] { "\'hello", "world\'" }, result);
+    }
+
+    /**
+     * Test escaped space string in double quotes.
+     */
+    public void testEscapedSpaceStringInDoubleQuotesOk() {
+        String[] result = split("\"hello\\ world\"");
+        assertArrayEquals(new String[] { "\"hello world\"" }, result);
+    }
+
+    /**
+     * Test escaped space string.
+     */
+    public void testEscapedSpaceStringOk() {
+        String[] result = split("hello\\ world");
+        assertArrayEquals(new String[] { "hello world" }, result);
+    }
+
+    /**
+     * Test key value pair in double quotes.
+     */
+    public void testKeyValuePairInDoubleQuotesOk() {
+        String[] result = split("\"key=\\\"qux qoo\\\"\"");
+        assertArrayEquals(new String[] { "\"key=\"qux qoo\"\"" }, result);
+    }
+
+    /**
+     * Test key value pair.
+     */
+    public void testKeyValuePairOk() {
+        String[] result = split("key='qux qoo'");
+        assertArrayEquals(new String[] { "key='qux qoo'" }, result);
+    }
+
+    /**
+     * Test os gi import package value.
+     */
+    public void testOSGiImportPackageValueOk() {
+        String[] result = split("\"org.foo.bar;version=\"1\",org.qux.quu;version=\"2\"\"");
+        assertArrayEquals(new String[] { "\"org.foo.bar;version=\"1\",org.qux.quu;version=\"2\"\"" }, result);
+    }
+
+    /**
+     * Test single quoted string.
+     */
+    public void testSingleQuotedStringOk() {
+        String[] result = split("'hello world'");
+        assertArrayEquals(new String[] { "'hello world'" }, result);
+    }
+
+    /**
+     * Test single quoted words.
+     */
+    public void testSingleQuotedWordsOk() {
+        String[] result = split("'hello' 'world'");
+        assertArrayEquals(new String[] { "'hello'", "'world'" }, result);
+    }
+
+    /**
+     * Test single quoted words omit quotes.
+     */
+    public void testSingleQuotedWordsOmitQuotesOk() {
+        String[] result = split("'hello' 'world'", false /* includeQuotes */);
+        assertArrayEquals(new String[] { "hello", "world" }, result);
+    }
+
+    /**
+     * Test split empty string.
+     */
+    public void testSplitEmptyStringOk() {
+        String[] result = split("");
+        assertArrayEquals(new String[0], result);
+    }
+
+    /**
+     * Test split null value.
+     */
+    public void testSplitNullValueOk() {
+        String[] result = split(null);
+        assertArrayEquals(new String[0], result);
+    }
+
+    /**
+     * Test split on tab.
+     */
+    public void testSplitOnTabOk() {
+        String[] result = split("hello\tworld");
+        assertArrayEquals(new String[] { "hello", "world" }, result);
+    }
+
+    /**
+     * Test split whitespaces only.
+     */
+    public void testSplitWhitespacesOnlyOk() {
+        String[] result = split(" \t  ");
+        assertArrayEquals(new String[0], result);
+    }
+
+    /**
+     * Test unquoted command line argument.
+     */
+    public void testUnquotedCommandLineArgumentOk() {
+        String[] result =
+            split("fwOption=org.osgi.framework.system.packages.extra=org.w3c.dom.tral,org.w3c.dom.html,org.w3c.dom.ranges,sun.reflect,org.osgi.service.deploymentadmin;version=\"1.0\",org.osgi.service.deploymentadmin.spi;version=\"1.0\",org.osgi.service.cm;version=\"1.3\",org.osgi.service.event;version=\"1.2\",org.osgi.service.log;version=\"1.3\",org.osgi.service.metatype;version=\"1.1\",org.apache.ace.log;version=\"0.8.0\"");
+        assertArrayEquals(
+            new String[] { "fwOption=org.osgi.framework.system.packages.extra=org.w3c.dom.tral,org.w3c.dom.html,org.w3c.dom.ranges,sun.reflect,org.osgi.service.deploymentadmin;version=\"1.0\",org.osgi.service.deploymentadmin.spi;version=\"1.0\",org.osgi.service.cm;version=\"1.3\",org.osgi.service.event;version=\"1.2\",org.osgi.service.log;version=\"1.3\",org.osgi.service.metatype;version=\"1.1\",org.apache.ace.log;version=\"0.8.0\"" },
+            result);
+    }
+
+    /**
+     * Test unquoted string.
+     */
+    public void testUnquotedStringOk() {
+        String[] result = split("hello world");
+        assertArrayEquals(new String[] { "hello", "world" }, result);
+    }
+
+    /**
+     * Assert array equals.
+     * 
+     * @param expected the expected
+     * @param actual the actual
+     */
+    private void assertArrayEquals(Object[] expected, Object[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+}

Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/TestUtil.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/TestUtil.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/TestUtil.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/TestUtil.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,106 @@
+/*
+ * 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.ace.processlauncher.test.impl;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * Provides some convenience methods commonly used in the unit tests of ace-launcher.
+ */
+public final class TestUtil {
+
+    /**
+     * Creates a new {@link TestUtil} instance, not used.
+     */
+    private TestUtil() {
+        // No-op
+    }
+
+    /**
+     * Returns the name of the running operating system.
+     * 
+     * @return the OS-name, in lower case.
+     */
+    public static String getOSName() {
+        return System.getProperty("os.name", "").toLowerCase();
+    }
+
+    /**
+     * Obtains the file denoted by the given path as resource, and treats it as properties file.
+     * 
+     * @param path the path to the resource to load, cannot be <code>null</code>.
+     * @return a properties file, never <code>null</code>.
+     * @throws IOException in case of I/O problems reading the properties file;
+     * @throws RuntimeException in case the given path is not a valid resource file.
+     */
+    public static Properties getProperties(String path) throws IOException {
+        InputStream is = TestUtil.class.getResourceAsStream(path);
+        if (is == null) {
+            throw new RuntimeException("File not found: " + path);
+        }
+        try {
+            Properties props = new Properties();
+            props.load(is);
+            return props;
+        }
+        finally {
+            is.close();
+        }
+    }
+
+    /**
+     * Sleeps for a given amount of milliseconds.
+     * 
+     * @param delayInMillis the delay to sleep, in milliseconds.
+     */
+    public static void sleep(int delayInMillis) {
+        try {
+            Thread.sleep(delayInMillis);
+        }
+        catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * Reads an entire file denoted by the given argument and returns its content as string.
+     * 
+     * @param file the file to read, cannot be <code>null</code>.
+     * @return the file contents, never <code>null</code>.
+     * @throws IOException in case of I/O problems reading the file.
+     */
+    public static String slurpFile(File file) throws IOException {
+        StringBuilder sb = new StringBuilder();
+        FileReader fr = new FileReader(file);
+        int ch;
+        try {
+            while ((ch = fr.read()) >= 0) {
+                sb.append((char) ch);
+            }
+        }
+        finally {
+            fr.close();
+        }
+        return sb.toString();
+    }
+}

Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherRespawnIntegrationTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherRespawnIntegrationTest.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherRespawnIntegrationTest.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherRespawnIntegrationTest.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,433 @@
+/*
+ * 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.ace.processlauncher.test.osgi;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+import static org.apache.ace.processlauncher.test.impl.TestUtil.getOSName;
+import static org.apache.ace.processlauncher.test.impl.TestUtil.sleep;
+import static org.ops4j.pax.exam.CoreOptions.cleanCaches;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import javax.inject.Inject;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.ProcessLauncherService;
+import org.apache.ace.processlauncher.ProcessLifecycleListener;
+import org.apache.ace.processlauncher.ProcessStreamListener;
+import org.apache.ace.processlauncher.impl.ProcessLauncherServiceImpl;
+import org.apache.ace.processlauncher.test.impl.TestUtil;
+import org.apache.felix.dm.DependencyManager;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Integration test for {@link ProcessLauncherService}.
+ */
+@RunWith(JUnit4TestRunner.class)
+public class ProcessLauncherRespawnIntegrationTest {
+
+    @Inject
+    private BundleContext m_context;
+    private DependencyManager m_dependencyManager;
+
+    /**
+     * @return the PAX-exam configuration, never <code>null</code>.
+     */
+    @Configuration
+    public Option[] config() {
+        // Craft the correct options for PAX-URL wrap: to use Bnd and make a correct bundle...
+        String bndOptions =
+            String.format("Bundle-Activator=%1$s.osgi.Activator&" + "Export-Package=%1$s,%1$s.util&"
+                + "Private-Package=%1$s.impl,%1$s.osgi", ProcessLauncherService.class.getPackage().getName());
+
+        return options(cleanCaches(),
+            junitBundles(),
+            provision(mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.log").version("1.0.1")), //
+            provision(mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.dependencymanager")
+                .version("3.0.0")), //
+            provision(mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.configadmin")
+                .version("1.2.8")), //
+            provision("wrap:assembly:./target/classes$" + bndOptions) //
+        );
+    }
+
+    /**
+     * Common set up for each test case.
+     */
+    @Before
+    public void setUp() {
+        m_dependencyManager = new DependencyManager(m_context);
+    }
+
+    /**
+     * Tests that a new process will be respawned if its exit value is non-equal to two.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testRespawnProcessWithExitValueTwoOnUnixBasedHostsOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        File tmpFile = createEmptyTempFile();
+
+        doTestRespawnProcess(tmpFile, "2", null /* psFilter */, null /* lcFilter */);
+
+        String contents = TestUtil.slurpFile(tmpFile);
+        assertTrue(contents, contents.matches("(?s)0.+1.+2.+"));
+    }
+
+    /**
+     * Tests that a new process will be respawned if its exit value is non-zero.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testRespawnProcessWithExitValueZeroOnUnixBasedHostsOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        File tmpFile = createEmptyTempFile();
+
+        doTestRespawnProcess(tmpFile, "0", null /* psFilter */, null /* lcFilter */);
+
+        String contents = TestUtil.slurpFile(tmpFile);
+        assertTrue(contents, contents.matches("(?s)0.+1.+2.+3.+4.+"));
+    }
+
+    /**
+     * Tests that a new process will be respawned, and its process stream listener is called for
+     * each respawn.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testRespawnProcessWithProcessStreamListenersOnUnixBasedHostsOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        File tmpFile = createEmptyTempFile();
+
+        TestProcessStreamListener psl = new TestProcessStreamListener();
+        String filter = registerProcessStreamListener(psl, "baz", "bam");
+
+        doTestRespawnProcess(tmpFile, "0", filter, null /* lcFilter */);
+
+        // Check whether our PSL is obtained and called...
+        assertEquals(5, psl.m_setStdoutCallCount);
+        assertEquals(5, psl.m_setStdinCallCount);
+
+        String contents = TestUtil.slurpFile(tmpFile);
+        assertTrue(contents, contents.matches("(?s)0.+1.+2.+3.+4.+"));
+    }
+
+    /**
+     * Tests that a new process will be respawned, and its process lifecycle listener is called for
+     * each respawn.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testRespawnProcessWithProcessLifecycleListenersOnUnixBasedHostsOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        File tmpFile = createEmptyTempFile();
+
+        TestProcessLifecycleListener pll = new TestProcessLifecycleListener();
+        String filter = registerProcessLifecycleListener(pll, "bar", "foo");
+
+        doTestRespawnProcess(tmpFile, "0", null /* psFilter */, filter);
+
+        // Check whether our PSL is obtained and called...
+        assertEquals(5, pll.m_afterCallCount);
+        assertEquals(5, pll.m_beforeCallCount);
+
+        String contents = TestUtil.slurpFile(tmpFile);
+        assertTrue(contents, contents.matches("(?s)0.+1.+2.+3.+4.+"));
+    }
+
+    /**
+     * Creates an empty temporary file, that is deleted on exit of the JVM.
+     * 
+     * @return a file instance pointing to a new temporary file.
+     * @throws IOException in case of I/O problems.
+     */
+    private File createEmptyTempFile() throws IOException {
+        File tmpFile = File.createTempFile("pls", null);
+        tmpFile.deleteOnExit();
+        return tmpFile;
+    }
+
+    /**
+     * Actual test implementation for <tt>#testRespawnProcessWithExitValue*<tt>.
+     * 
+     * @param tmpFile the temporary file to write the script results to;
+     * @param normalExitValue the exit value that should be considered as normal.
+     * @throws IOException in case of I/O problems.
+     */
+    private void doTestRespawnProcess(File tmpFile, String normalExitValue, String psFilter, String lcFilter)
+        throws IOException {
+
+        int count = 4;
+        String tmpFilename = tmpFile.getAbsolutePath();
+
+        // Seems daunting, but what this command does is simply count the number
+        // of lines in a given file (= tmpFile), and append this to that same
+        // file; it uses this number to create an exit value, which counts from
+        // <count> back to 0. This way, we can test whether the respawn
+        // functionality works, as this currently checks for certain exit
+        // values...
+        String args =
+            String.format("-c L=$(cat\\ %1$s\\ |\\ wc\\ -l)\\ &&\\ echo\\ $L\\ >>\\ %1$s\\ &&\\ exit\\ $((%2$d-$L))",
+                tmpFilename, count);
+
+        Properties launchConfig = new Properties();
+        launchConfig.put("instance.count", "1");
+        launchConfig.put("executable.name", "/bin/bash");
+        launchConfig.put("executable.args", args);
+        launchConfig.put("executable.workingDir", "/tmp");
+        launchConfig.put("executable.respawnAutomatically", "true");
+        launchConfig.put("executable.normalExitValue", normalExitValue);
+        if (psFilter != null) {
+            launchConfig.put("executable.processStreamListener", psFilter);
+        }
+        if (lcFilter != null) {
+            launchConfig.put("executable.processLifecycleListener", lcFilter);
+        }
+
+        configureFactory(ProcessLauncherServiceImpl.PID, launchConfig);
+
+        // Wait until the processes are done...
+        sleep(1000);
+    }
+
+    /**
+     * Registers a given process stream listener and returns the filter clause to obtain that same
+     * instance through OSGi.
+     * 
+     * @param processStreamListener the process stream listener to register, cannot be
+     *        <code>null</code>.
+     * @return the filter clause to obtain the exact same process stream listener through OSGi,
+     *         never <code>null</code>.
+     */
+    private String registerProcessStreamListener(TestProcessStreamListener processStreamListener, String... properties) {
+        assertEquals("Number of properties not a multiple of two!", 0, properties.length % 2);
+
+        String className = ProcessStreamListener.class.getName();
+        String extraFilter = "";
+
+        Properties props = new Properties();
+        for (int i = 0; i < properties.length; i += 2) {
+            String key = properties[i];
+            String value = properties[i + 1];
+
+            extraFilter = String.format("%s(%s=%s)", extraFilter, key, value);
+            props.setProperty(key, value);
+        }
+
+        m_dependencyManager.add(m_dependencyManager.createComponent().setInterface(className, props)
+            .setImplementation(processStreamListener));
+
+        if (extraFilter.trim().isEmpty()) {
+            return String.format("(%s=%s)", Constants.OBJECTCLASS, className);
+        }
+        return String.format("(&(%s=%s)%s)", Constants.OBJECTCLASS, className, extraFilter);
+    }
+
+    /**
+     * Registers a given process lifecycle listener and returns the filter clause to obtain that
+     * same instance through OSGi.
+     * 
+     * @param processLifecycleListener the process lifecycle listener to register, cannot be
+     *        <code>null</code>.
+     * @return the filter clause to obtain the exact same process stream listener through OSGi,
+     *         never <code>null</code>.
+     */
+    private String registerProcessLifecycleListener(TestProcessLifecycleListener processLifecycleListener,
+        String... properties) {
+        assertEquals("Number of properties not a multiple of two!", 0, properties.length % 2);
+
+        String className = ProcessLifecycleListener.class.getName();
+        String extraFilter = "";
+
+        Properties props = new Properties();
+        for (int i = 0; i < properties.length; i += 2) {
+            String key = properties[i];
+            String value = properties[i + 1];
+
+            extraFilter = String.format("%s(%s=%s)", extraFilter, key, value);
+            props.setProperty(key, value);
+        }
+
+        m_dependencyManager.add(m_dependencyManager.createComponent().setInterface(className, props)
+            .setImplementation(processLifecycleListener));
+
+        if (extraFilter.trim().isEmpty()) {
+            return String.format("(%s=%s)", Constants.OBJECTCLASS, className);
+        }
+        return String.format("(&(%s=%s)%s)", Constants.OBJECTCLASS, className, extraFilter);
+    }
+
+    /**
+     * Lazily initializes the configuration admin service and returns it.
+     * 
+     * @return the {@link ConfigurationAdmin} instance, never <code>null</code>.
+     * @throws AssertionFailedError in case the {@link ConfigurationAdmin} service couldn't be
+     *         obtained.
+     */
+    private ConfigurationAdmin getConfigAdmin() {
+        ServiceTracker serviceTracker = new ServiceTracker(m_context, ConfigurationAdmin.class.getName(), null);
+
+        ConfigurationAdmin instance = null;
+
+        serviceTracker.open();
+        try {
+            instance = (ConfigurationAdmin) serviceTracker.waitForService(2 * 1000);
+
+            if (instance == null) {
+                fail("ConfigurationAdmin service not found!");
+            }
+            else {
+                return instance;
+            }
+        }
+        catch (InterruptedException e) {
+            // Make sure the thread administration remains correct!
+            Thread.currentThread().interrupt();
+
+            e.printStackTrace();
+            fail("ConfigurationAdmin service not available: " + e.toString());
+        }
+
+        return instance;
+    }
+
+    /**
+     * Creates a factory configuration with the given properties, just like {@link #configure}.
+     * 
+     * @param factoryPid the PID of the factory that should be used to create a configuration;
+     * @param properties the new configuration properties to configure, can be <code>null</code>.
+     * @return The PID of newly created configuration.
+     * @throws IOException when the configuration couldn't be set/updated.
+     * @throws AssertionFailedError in case the {@link ConfigurationAdmin} service couldn't be
+     *         obtained.
+     */
+    private String configureFactory(String factoryPid, Properties properties) throws IOException {
+        assertNotNull("Parameter factoryPid cannot be null!", factoryPid);
+
+        org.osgi.service.cm.Configuration config = getConfigAdmin().createFactoryConfiguration(factoryPid, null);
+        config.update(properties);
+
+        // Delay a bit to allow configuration to be propagated...
+        sleep(500);
+
+        return config.getPid();
+    }
+
+    /**
+     * Testing implementation of {@link ProcessLifecycleListener}.
+     */
+    static final class TestProcessLifecycleListener implements ProcessLifecycleListener {
+        private volatile int m_beforeCallCount = 0;
+        private volatile int m_afterCallCount = 0;
+
+        /**
+         * {@inheritDoc}
+         */
+        public void afterProcessEnd(LaunchConfiguration configuration) {
+            m_afterCallCount++;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Properties beforeProcessStart(LaunchConfiguration configuration) {
+            m_beforeCallCount++;
+            return null;
+        }
+    }
+
+    /**
+     * Testing implementation of {@link ProcessStreamListener}.
+     */
+    static class TestProcessStreamListener implements ProcessStreamListener {
+
+        private volatile int m_setStdinCallCount = 0;
+        private volatile int m_setStdoutCallCount = 0;
+
+        /**
+         * {@inheritDoc}
+         */
+        public void setStdin(LaunchConfiguration launchConfiguration, OutputStream outputStream) {
+            m_setStdinCallCount++;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void setStdout(LaunchConfiguration launchConfiguration, InputStream inputStream) {
+            m_setStdoutCallCount++;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean wantsStdin() {
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean wantsStdout() {
+            return true;
+        }
+    }
+}

Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherServiceIntegrationTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherServiceIntegrationTest.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherServiceIntegrationTest.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherServiceIntegrationTest.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,452 @@
+/*
+ * 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.ace.processlauncher.test.osgi;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.fail;
+import static org.apache.ace.processlauncher.test.impl.TestUtil.getOSName;
+import static org.apache.ace.processlauncher.test.impl.TestUtil.sleep;
+import static org.ops4j.pax.exam.CoreOptions.cleanCaches;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import javax.inject.Inject;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.ProcessLauncherService;
+import org.apache.ace.processlauncher.ProcessStreamListener;
+import org.apache.ace.processlauncher.impl.ProcessLauncherServiceImpl;
+import org.apache.felix.dm.DependencyManager;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Integration test for {@link ProcessLauncherService}.
+ */
+@RunWith(JUnit4TestRunner.class)
+public class ProcessLauncherServiceIntegrationTest {
+
+    @Inject
+    private BundleContext m_context;
+    @Inject
+    private ProcessLauncherService m_instance;
+    private DependencyManager m_dependencyManager;
+
+    /**
+     * @return the PAX-exam configuration, never <code>null</code>.
+     */
+    @Configuration
+    public Option[] config() {
+        // Craft the correct options for PAX-URL wrap: to use Bnd and make a correct bundle...
+        String bndOptions =
+            String.format("Bundle-Activator=%1$s.osgi.Activator&" + "Export-Package=%1$s,%1$s.util&"
+                + "Private-Package=%1$s.impl,%1$s.osgi", ProcessLauncherService.class.getPackage().getName());
+
+        return options(cleanCaches(),
+            junitBundles(),
+            provision(mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.log").version("1.0.1")), //
+            provision(mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.dependencymanager")
+                .version("3.0.0")), //
+            provision(mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.configadmin")
+                .version("1.2.8")), //
+            provision("wrap:assembly:./target/classes$" + bndOptions) //
+        );
+    }
+
+    /**
+     * Common set up for each test case.
+     */
+    @Before
+    public void setUp() {
+        m_dependencyManager = new DependencyManager(m_context);
+    }
+
+    /**
+     * Tests that manually providing a launch configuration to a {@link ProcessLauncherService} will
+     * cause a new process to be started and terminated.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testLaunchProcessWithExitValueOneOnUnixBasedHostsOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        Properties launchConfig = new Properties();
+        launchConfig.put("instance.count", "2");
+        launchConfig.put("executable.name", "/bin/sh");
+        launchConfig.put("executable.args", "-c sleep\\ 1\\ &&\\ exit\\ 1");
+        launchConfig.put("executable.workingDir", "/tmp");
+        launchConfig.put("executable.respawnAutomatically", "false");
+        launchConfig.put("executable.normalExitValue", 1);
+
+        int launchConfigCount = m_instance.getLaunchConfigurationCount();
+        int runningProcessCount = m_instance.getRunningProcessCount();
+
+        configureFactory(ProcessLauncherService.PID, launchConfig);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Two instances...
+        assertEquals(runningProcessCount + 2, m_instance.getRunningProcessCount());
+
+        // Wait until the processes are done...
+        sleep(1100);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Zero instances...
+        assertEquals(runningProcessCount, m_instance.getRunningProcessCount());
+    }
+
+    /**
+     * Tests that manually providing a launch configuration to a {@link ProcessLauncherServiceImpl}
+     * will cause a new process to be started and terminated.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testLaunchProcessWithExitValueZeroOnUnixBasedHostsOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        Properties launchConfig = new Properties();
+        launchConfig.put("instance.count", "2");
+        launchConfig.put("executable.name", "/bin/sh");
+        launchConfig.put("executable.args", "-c sleep\\ 1\\ &&\\ exit\\ 0");
+        launchConfig.put("executable.workingDir", "/tmp");
+        launchConfig.put("executable.respawnAutomatically", "false");
+        launchConfig.put("executable.normalExitValue", 0);
+
+        int launchConfigCount = m_instance.getLaunchConfigurationCount();
+        int runningProcessCount = m_instance.getRunningProcessCount();
+
+        configureFactory(ProcessLauncherService.PID, launchConfig);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Two instances...
+        assertEquals(runningProcessCount + 2, m_instance.getRunningProcessCount());
+
+        // Wait until the processes are done...
+        sleep(1100);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Zero instances...
+        assertEquals(runningProcessCount, m_instance.getRunningProcessCount());
+    }
+
+    /**
+     * Tests that registering multiple process stream listeners will cause the registered listener
+     * with the highest service-ID to be called when a process is executed.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testLaunchProcessWithMultipleRegisteredProcessStreamListenerOnUnixBasedHostsOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        TestProcessStreamListener psl1 = new TestProcessStreamListener();
+        registerProcessStreamListener(psl1, "qux", "quu");
+        TestProcessStreamListener psl2 = new TestProcessStreamListener();
+        String filter = registerProcessStreamListener(psl2, "qux", "quu");
+
+        Properties launchConfig = new Properties();
+        launchConfig.put("instance.count", "1");
+        launchConfig.put("executable.name", "/bin/sh");
+        launchConfig.put("executable.args", "-c sleep\\ 1\\ &&\\ exit\\ 0");
+        launchConfig.put("executable.processStreamListener", filter);
+        launchConfig.put("executable.workingDir", "/tmp");
+        launchConfig.put("executable.respawnAutomatically", "false");
+        launchConfig.put("executable.normalExitValue", 0);
+
+        int launchConfigCount = m_instance.getLaunchConfigurationCount();
+        int runningProcessCount = m_instance.getRunningProcessCount();
+
+        configureFactory(ProcessLauncherService.PID, launchConfig);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Two instances...
+        assertEquals(runningProcessCount + 1, m_instance.getRunningProcessCount());
+
+        // Wait until the processes are done...
+        sleep(1100);
+
+        // Check whether our PSL is obtained and called...
+        assertEquals(1, psl1.m_setStdoutCallCount);
+        assertEquals(1, psl1.m_setStdinCallCount);
+        assertEquals(0, psl2.m_setStdoutCallCount);
+        assertEquals(0, psl2.m_setStdinCallCount);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Zero instances...
+        assertEquals(runningProcessCount, m_instance.getRunningProcessCount());
+    }
+
+    /**
+     * Tests that an unregistered process stream listener will cause an exception to be thrown when
+     * a process is to be started.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testLaunchProcessWithoutRegisteredProcessStreamListenerOnUnixBasedHostsFail() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        TestProcessStreamListener psl = new TestProcessStreamListener();
+        String filter =
+            String.format("(&(%s=%s)(qux=quu))", Constants.OBJECTCLASS, ProcessStreamListener.class.getName());
+
+        Properties launchConfig = new Properties();
+        launchConfig.put("instance.count", "1");
+        launchConfig.put("executable.name", "/bin/sh");
+        launchConfig.put("executable.args", "-c sleep\\ 1\\ &&\\ exit\\ 0");
+        launchConfig.put("executable.processStreamListener", filter);
+        launchConfig.put("executable.workingDir", "/tmp");
+        launchConfig.put("executable.respawnAutomatically", "false");
+        launchConfig.put("executable.normalExitValue", 0);
+
+        int launchConfigCount = m_instance.getLaunchConfigurationCount();
+        int runningProcessCount = m_instance.getRunningProcessCount();
+
+        configureFactory(ProcessLauncherService.PID, launchConfig);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Two instances...
+        assertEquals(runningProcessCount + 1, m_instance.getRunningProcessCount());
+
+        // Wait until the processes are done...
+        sleep(1100);
+
+        // Check whether our PSL is obtained and called...
+        assertEquals(0, psl.m_setStdoutCallCount);
+        assertEquals(0, psl.m_setStdinCallCount);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Zero instances...
+        assertEquals(runningProcessCount, m_instance.getRunningProcessCount());
+    }
+
+    /**
+     * Tests that registering a process stream listener will cause the registered listener to be
+     * called when a process is executed.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testLaunchProcessWithRegisteredProcessStreamListenerOnUnixBasedHostsOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        TestProcessStreamListener psl = new TestProcessStreamListener();
+        String filter = registerProcessStreamListener(psl, "foo", "bar");
+
+        Properties launchConfig = new Properties();
+        launchConfig.put("instance.count", "1");
+        launchConfig.put("executable.name", "/bin/sh");
+        launchConfig.put("executable.args", "-c sleep\\ 1\\ &&\\ exit\\ 0");
+        launchConfig.put("executable.processStreamListener", filter);
+        launchConfig.put("executable.workingDir", "/tmp");
+        launchConfig.put("executable.respawnAutomatically", "false");
+        launchConfig.put("executable.normalExitValue", 0);
+
+        int launchConfigCount = m_instance.getLaunchConfigurationCount();
+        int runningProcessCount = m_instance.getRunningProcessCount();
+
+        configureFactory(ProcessLauncherService.PID, launchConfig);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Two instances...
+        assertEquals(runningProcessCount + 1, m_instance.getRunningProcessCount());
+
+        // Wait until the processes are done...
+        sleep(1100);
+
+        // Check whether our PSL is obtained and called...
+        assertEquals(1, psl.m_setStdoutCallCount);
+        assertEquals(1, psl.m_setStdinCallCount);
+
+        // One process...
+        assertEquals(launchConfigCount + 1, m_instance.getLaunchConfigurationCount());
+        // Zero instances...
+        assertEquals(runningProcessCount, m_instance.getRunningProcessCount());
+    }
+
+    /**
+     * Registers a given process stream listener and returns the filter clause to obtain that same
+     * instance through OSGi.
+     * 
+     * @param processStreamListener the process stream listener to register, cannot be
+     *        <code>null</code>.
+     * @return the filter clause to obtain the exact same process stream listener through OSGi,
+     *         never <code>null</code>.
+     */
+    private String registerProcessStreamListener(TestProcessStreamListener processStreamListener, String... properties) {
+        assertEquals("Number of properties not a multiple of two!", 0, properties.length % 2);
+
+        String className = ProcessStreamListener.class.getName();
+        String extraFilter = "";
+
+        Properties props = new Properties();
+        for (int i = 0; i < properties.length; i += 2) {
+            String key = properties[i];
+            String value = properties[i + 1];
+
+            extraFilter = String.format("%s(%s=%s)", extraFilter, key, value);
+            props.setProperty(key, value);
+        }
+
+        m_dependencyManager.add(m_dependencyManager.createComponent().setInterface(className, props)
+            .setImplementation(processStreamListener));
+
+        if (extraFilter.trim().isEmpty()) {
+            return String.format("(%s=%s)", Constants.OBJECTCLASS, className);
+        }
+        return String.format("(&(%s=%s)%s)", Constants.OBJECTCLASS, className, extraFilter);
+    }
+
+    /**
+     * Lazily initializes the configuration admin service and returns it.
+     * 
+     * @return the {@link ConfigurationAdmin} instance, never <code>null</code>.
+     * @throws AssertionFailedError in case the {@link ConfigurationAdmin} service couldn't be
+     *         obtained.
+     */
+    private ConfigurationAdmin getConfigAdmin() {
+        ServiceTracker serviceTracker = new ServiceTracker(m_context, ConfigurationAdmin.class.getName(), null);
+
+        ConfigurationAdmin instance = null;
+
+        serviceTracker.open();
+        try {
+            instance = (ConfigurationAdmin) serviceTracker.waitForService(2 * 1000);
+
+            if (instance == null) {
+                fail("ConfigurationAdmin service not found!");
+            }
+            else {
+                return instance;
+            }
+        }
+        catch (InterruptedException e) {
+            // Make sure the thread administration remains correct!
+            Thread.currentThread().interrupt();
+
+            e.printStackTrace();
+            fail("ConfigurationAdmin service not available: " + e.toString());
+        }
+
+        return instance;
+    }
+
+    /**
+     * Creates a factory configuration with the given properties, just like {@link #configure}.
+     * 
+     * @param factoryPid the PID of the factory that should be used to create a configuration;
+     * @param properties the new configuration properties to configure, can be <code>null</code>.
+     * @return The PID of newly created configuration.
+     * @throws IOException when the configuration couldn't be set/updated.
+     * @throws AssertionFailedError in case the {@link ConfigurationAdmin} service couldn't be
+     *         obtained.
+     */
+    private String configureFactory(String factoryPid, Properties properties) throws IOException {
+        assertNotNull("Parameter factoryPid cannot be null!", factoryPid);
+
+        org.osgi.service.cm.Configuration config = getConfigAdmin().createFactoryConfiguration(factoryPid, null);
+        config.update(properties);
+
+        // Delay a bit to allow configuration to be propagated...
+        sleep(500);
+
+        return config.getPid();
+    }
+
+    /**
+     * {@link ProcessStreamListener} implementation for the test cases in this test.
+     */
+    static class TestProcessStreamListener implements ProcessStreamListener {
+
+        private volatile int m_setStdinCallCount = 0;
+        private volatile int m_setStdoutCallCount = 0;
+
+        /**
+         * {@inheritDoc}
+         */
+        public void setStdin(LaunchConfiguration launchConfiguration, OutputStream outputStream) {
+            m_setStdinCallCount++;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void setStdout(LaunchConfiguration launchConfiguration, InputStream inputStream) {
+            m_setStdoutCallCount++;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean wantsStdin() {
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean wantsStdout() {
+            return true;
+        }
+    }
+}

Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/util/InputStreamRedirectorTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/util/InputStreamRedirectorTest.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/util/InputStreamRedirectorTest.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/util/InputStreamRedirectorTest.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,184 @@
+/*
+ * 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.ace.processlauncher.test.util;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static org.apache.ace.processlauncher.test.impl.TestUtil.sleep;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.ace.processlauncher.util.InputStreamRedirector;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Test cases for {@link InputStreamRedirector}.
+ */
+public class InputStreamRedirectorTest {
+
+    /**
+     * Tests that when an EOF is read on the input stream, the output stream is closed as well.
+     * 
+     * @throws IOException not part of this test case.
+     */
+    @Test
+    public void testInputStreamEOFCausesOutputStreamToBeClosedOk() throws IOException {
+        InputStream myIS = new ByteArrayInputStream("hello world!".getBytes());
+        OutputStream mockOS = mock(OutputStream.class);
+
+        InputStreamRedirector redirector = new InputStreamRedirector(myIS, mockOS);
+        redirector.run();
+
+        verify(mockOS).close();
+    }
+
+    /**
+     * Tests that the input stream is 1:1 copied to the given output stream.
+     * 
+     * @throws IOException not part of this test case.
+     */
+    @Test
+    public void testInputStreamIsVerbatimelyCopiedToOutputStreamOk() throws IOException {
+        String input = "hello world!";
+
+        InputStream myIS = new ByteArrayInputStream(input.getBytes());
+        ByteArrayOutputStream myOS = new ByteArrayOutputStream();
+
+        InputStreamRedirector redirector = new InputStreamRedirector(myIS, myOS);
+        redirector.run();
+
+        assertEquals(input, myOS.toString());
+    }
+
+    /**
+     * Tests that we can interrupt a redirector and that it ceases its work.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testInterruptRedirectorOk() throws Exception {
+        InputStream myIS = createBlockingInputStream();
+        OutputStream myOS = mock(OutputStream.class);
+
+        InputStreamRedirector redirector = new InputStreamRedirector(myIS, myOS);
+
+        Thread redirectorThread = new Thread(redirector);
+        redirectorThread.start();
+
+        // Sleep for a little while to ensure everything is up and running...
+        sleep(100);
+
+        redirectorThread.interrupt();
+
+        // Wait until the thread is really finished...
+        redirectorThread.join(1000);
+
+        verify(myIS, atLeast(1)).read(Mockito.<byte[]>any(), anyInt(), anyInt());
+        verify(myOS).close();
+    }
+
+    /**
+     * Tests that we can recover when the input stream throws an I/O exception.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testRecoverFromExceptionInInputStreamWithoutOutputStreamOk() throws Exception {
+        InputStream myIS = createExceptionThrowingInputStream();
+        ByteArrayOutputStream myOS = new ByteArrayOutputStream();
+
+        InputStreamRedirector redirector = new InputStreamRedirector(myIS, myOS);
+
+        redirector.run();
+
+        verify(myIS, atLeast(1)).read(Mockito.<byte[]>any(), anyInt(), anyInt());
+
+        // Verify that the exception is indeed logged...
+        String stdout = myOS.toString();
+
+        assertTrue(stdout.contains("IGNORE ME! TEST EXCEPTION!"));
+    }
+
+    /**
+     * Tests that we can recover when the input stream throws an I/O exception.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testRecoverFromExceptionInInputStreamWithOutputStreamOk() throws Exception {
+        InputStream myIS = createExceptionThrowingInputStream();
+        OutputStream myOS = mock(OutputStream.class);
+
+        InputStreamRedirector redirector = new InputStreamRedirector(myIS, myOS);
+
+        redirector.run();
+
+        verify(myIS, atLeast(1)).read(Mockito.<byte[]>any(), anyInt(), anyInt());
+        verify(myOS).close();
+    }
+
+    /**
+     * Tests that we can recover when the input stream throws an I/O exception.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Test
+    public void testWithoutOutputStreamOk() throws Exception {
+        InputStream myIS = new ByteArrayInputStream("hello world!".getBytes());
+
+        InputStreamRedirector redirector = new InputStreamRedirector(myIS);
+
+        redirector.run();
+    }
+
+    /**
+     * Creates an input stream that keeps pretending its returning data when its
+     * {@link InputStream#read(byte[])} method is called.
+     * 
+     * @return a mocked {@link InputStream} instance, never <code>null</code>.
+     */
+    private InputStream createBlockingInputStream() throws IOException {
+        InputStream is = mock(InputStream.class);
+        when(is.read(Mockito.<byte[]>any(), anyInt(), anyInt())).thenReturn(Integer.valueOf(10));
+        return is;
+    }
+
+    /**
+     * Creates an input stream that keeps pretending its returning data when its
+     * {@link InputStream#read(byte[])} method is called.
+     * 
+     * @return a mocked {@link InputStream} instance, never <code>null</code>.
+     */
+    private InputStream createExceptionThrowingInputStream() throws IOException {
+        InputStream is = mock(InputStream.class);
+        when(is.read(Mockito.<byte[]>any(), anyInt(), anyInt())).thenThrow(
+            new IOException("IGNORE ME! TEST EXCEPTION!"));
+        return is;
+    }
+}

Added: ace/trunk/ace-processlauncher/src/test/resources/org/apache/ace/processlauncher/test/impl/launch.properties
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/resources/org/apache/ace/processlauncher/test/impl/launch.properties?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/resources/org/apache/ace/processlauncher/test/impl/launch.properties (added)
+++ ace/trunk/ace-processlauncher/src/test/resources/org/apache/ace/processlauncher/test/impl/launch.properties Thu Apr 19 14:24:36 2012
@@ -0,0 +1,6 @@
+instance.count = 2
+executable.name = /bin/sh
+executable.args = -c 'sleep 1 && exit' -c 'echo "foo bar!\n"'
+executable.executable.processStreamListener = (foo=bar)
+executable.workingDir = /tmp
+executable.respawnAutomatically = false
\ No newline at end of file

Modified: ace/trunk/pom.xml
URL: http://svn.apache.org/viewvc/ace/trunk/pom.xml?rev=1327960&r1=1327959&r2=1327960&view=diff
==============================================================================
--- ace/trunk/pom.xml (original)
+++ ace/trunk/pom.xml Thu Apr 19 14:24:36 2012
@@ -131,6 +131,7 @@
         <module>ace-managementagent</module>
         <module>ace-launcher</module>
         <module>ace-glassfish-launcher</module>
+        <module>ace-processlauncher</module>
 
         <module>ace-karaf-features</module>
 



Mime
View raw message