db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d..@apache.org
Subject svn commit: r1618088 - in /db/derby/code/trunk/java/testing/org/apache/derbyTesting: functionTests/tests/jdbcapi/AutoloadTest.java functionTests/util/derby_tests.policy junit/BaseTestCase.java junit/SpawnedProcess.java
Date Fri, 15 Aug 2014 02:12:11 GMT
Author: dag
Date: Fri Aug 15 02:12:11 2014
New Revision: 1618088

URL: http://svn.apache.org/r1618088
Log:
DERBY-6704 Hang in AutoloadTest.spawnProcess:JDBCDriversEmbeddedTest on Mac/jdk7,8

Patch derby-6704b: it collects std out and err for the spawned
subprocesses, and also forward the debug options "derby.tests.debug"
and "derby.tests.trace" to the subprocesses. If any of those are set,
we collect the std err and std out and print them interleaved with
that of the top process.

The patch will also try to get a jstack status of a hanging subprocess
before killing it (only works on Unixen) - hopefully this would work
also on MacOs; it uses a little dirty reflection to access a private
PID field in the underlying Process class object before trying a
jstack. For this to work, the test must also run using an Oracle JDK,
a JRE doesn't have a jstack.

Modified:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AutoloadTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/derby_tests.policy
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AutoloadTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AutoloadTest.java?rev=1618088&r1=1618087&r2=1618088&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AutoloadTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AutoloadTest.java
Fri Aug 15 02:12:11 2014
@@ -55,7 +55,7 @@ import org.apache.derbyTesting.junit.Tes
  */
 public class AutoloadTest extends BaseJDBCTestCase
 {
-    private Class spawnedTestClass;
+    private Class<?> spawnedTestClass;
 
 	public	AutoloadTest( String name ) { super( name ); }
 
@@ -65,14 +65,16 @@ public class AutoloadTest extends BaseJD
      * @param wrapper a test class that decorates {@code AutoloadTest} with the
      * desired configuration
      */
-    private AutoloadTest(Class wrapper) {
+    private AutoloadTest(Class<?> wrapper) {
         this("spawnProcess");
         spawnedTestClass = wrapper;
     }
 
     /**
      * Get the name of the test case.
+     * @return
      */
+    @Override
     public String getName() {
         String name = super.getName();
         if (spawnedTestClass != null) {
@@ -86,6 +88,7 @@ public class AutoloadTest extends BaseJD
     /**
      * Only run a test if the driver will be auto-loaded.
      * See class desciption for details.
+     * @return the test
      */
     public static Test suite() {
         if (!JDBC.vmSupportsJDBC3())
@@ -118,10 +121,10 @@ public class AutoloadTest extends BaseJD
                 jdbcDrivers = "";
 
             embeddedAutoLoad = jdbcDrivers
-                    .indexOf("org.apache.derby.jdbc.EmbeddedDriver") != -1;
+                    .contains("org.apache.derby.jdbc.EmbeddedDriver");
 
             clientAutoLoad = jdbcDrivers
-                    .indexOf("org.apache.derby.jdbc.ClientDriver") != -1;
+                    .contains("org.apache.derby.jdbc.ClientDriver");
 
         } catch (SecurityException se) {
             // assume there is no autoloading if
@@ -176,6 +179,9 @@ public class AutoloadTest extends BaseJD
     
     /**
      * Return the ordered set of tests when autoloading is enabled.
+     *
+     * @param which embedded or client
+     * @return the constructed test suite
      */
     private static Test baseAutoLoadSuite(String which)
     {
@@ -225,6 +231,7 @@ public class AutoloadTest extends BaseJD
      * <li>jdbc.drivers property specifying client driver</li>
      * <li>jdbc.drivers property specifying both drivers</li>
      * </ul>
+     * @return the test constructed
      */
     static Test fullAutoloadSuite() {
         BaseTestSuite suite = new BaseTestSuite("AutoloadTest:All");
@@ -238,6 +245,7 @@ public class AutoloadTest extends BaseJD
         // stop the engine in the main test process to prevent attempts to
         // double-boot the database.
         return new TestSetup(suite) {
+            @Override
             protected void setUp() {
                 TestConfiguration.getCurrent().shutdownEngine();
             }
@@ -246,56 +254,82 @@ public class AutoloadTest extends BaseJD
 
     /**
      * Run {@code AutoloadTest} in a separate JVM.
+     *
+     * @throws java.lang.Exception something went wrong
      */
     public void spawnProcess() throws Exception {
-        if (TestConfiguration.isDefaultBasePort()) {
-            final List<String> args = new ArrayList<String>();
-            args.add("-Dderby.system.durability=" +
-                     getSystemProperty("derby.system.durability"));
-            args.add("-Dderby.tests.trace=" +
-                     getSystemProperty("derby.tests.trace"));
-            args.add("-Dderby.system.debug=" +
-                     getSystemProperty("derby.tests.debug"));
-            args.add("junit.textui.TestRunner");
-            args.add(spawnedTestClass.getName());
-            final String[] cmd = args.toArray(new String[0]);
-            
-            SpawnedProcess proc = new SpawnedProcess
-                    (execJavaCmd(cmd), spawnedTestClass.getName());
-            proc.suppressOutputOnComplete(); // we want to read it ourselves
-            final int exitCode = proc.complete(180000L); // 3 minutes
+        final List<String> args = new ArrayList<String>();
+        args.add("-Dderby.system.durability=" +
+                getSystemProperty("derby.system.durability"));
+        args.add("-Dderby.tests.trace=" +
+                getSystemProperty("derby.tests.trace"));
+        args.add("-Dderby.system.debug=" +
+                getSystemProperty("derby.tests.debug"));
+
+        if (!TestConfiguration.isDefaultBasePort()) {
+            args.add("-Dderby.tests.basePort=" + TestConfiguration.getBasePort());
+        }
+
+        args.add("junit.textui.TestRunner");
+        args.add(spawnedTestClass.getName());
+        final String[] cmd = args.toArray(new String[0]);
+
+        final SpawnedProcess proc = new SpawnedProcess
+                            (execJavaCmd(cmd), spawnedTestClass.getName());
+        // Close stdin of the process so that it stops
+        // any waiting for it and exits (shoudln't matter for this test)
+        proc.suppressOutputOnComplete(); // we want to read it ourselves
+
+        final boolean completed = proc.waitForExit(120000L /* 2m */, 1000L);
+
+        final StringBuilder jstackReport = new StringBuilder();
+        if (!completed) {
+            jstackReport.append("\n\n\n[Subprocess ");
+            jstackReport.append(proc.getPid());
+            jstackReport.append(" hanging, jstack result:");
+            jstackReport.append(proc.jstack());
+            jstackReport.append("End of jstack output]\n\n");
+        }
+
+        final int exitCode = proc.complete(0); // kill right away if not done
+
+        final String output = proc.getFullServerOutput();
+        final String err    = proc.getFullServerError();
+
+        final String headerOut = "\n[ (stdout subprocess) ";
+        final String headerErr = headerOut.replace("out", "err");
+        final String contLineOut = headerOut.replace('[', ' ');
+        final String contLineErr = contLineOut.replace("out", "err");
 
-            assertTrue(proc.getFailMessage("subprocess run failed: "),
-                    exitCode == 0);
+        if (exitCode != 0) {
+            final StringBuilder errMsg = new StringBuilder();
             
-            final String output = proc.getFullServerOutput(); // ignore
-            final String err    = proc.getFullServerError();
+            errMsg.append("subprocess run failed: exit code==");
+            errMsg.append(exitCode);
+            errMsg.append("\n");
+            errMsg.append(headerOut);
+            errMsg.append(output.replaceAll("\n", contLineOut));
+            errMsg.append("]\n");
+            errMsg.append(headerErr);
+            errMsg.append(err.replaceAll("\n", contLineErr));
+            errMsg.append("]\n");
 
-            // Print sub process' outputs if this test specifies any such
-            if (Boolean.parseBoolean(
-                        getSystemProperty("derby.tests.trace")) ||
-                Boolean.parseBoolean(
-                    getSystemProperty("derby.tests.debug"))) {
+            errMsg.append(jstackReport);
 
-                System.out.println("\n[ (stdout subprocess) " +
-                        output.replace("\n", "\n  (stdout subprocess) ") + "]\n");
-                System.out.println("\n[ (stderr subprocess) " +
-                        err.replace("\n", "\n  (stderr subprocess) ") + "]\n");
-            }
+            fail(errMsg.toString());
         }
-        else 
-        {
-            // if we're not using the default port of 1527, ensure we're
-            // passing on the baseport value to the spawned process.
-            String[] cmd = {
-                    "-Dderby.tests.basePort=" + TestConfiguration.getBasePort(),
-                    "junit.textui.TestRunner", spawnedTestClass.getName()
-            };            
-            SpawnedProcess proc = new SpawnedProcess
-                    (execJavaCmd(cmd), spawnedTestClass.getName());
-            if (proc.complete() != 0) {
-                fail(proc.getFailMessage("Test process failed"));
-            }
+
+
+        // Print sub process' outputs if this test specifies any such
+        if (Boolean.parseBoolean(
+                getSystemProperty("derby.tests.trace")) ||
+                Boolean.parseBoolean(
+                        getSystemProperty("derby.tests.debug"))) {
+
+            System.out.println(
+                    headerOut + output.replace("\n", contLineOut) + "]\n");
+            System.out.println(
+                    headerErr + err.replace("\n", contLineErr) + "]\n");
         }
     }
 
@@ -401,10 +435,10 @@ public class AutoloadTest extends BaseJD
     {
         String  clientDriverName = getClientDriverName();
         
-        for (Enumeration e = DriverManager.getDrivers();
+        for (Enumeration<Driver> e = DriverManager.getDrivers();
                 e.hasMoreElements(); )
         {
-            Driver d = (Driver) e.nextElement();
+            Driver d = e.nextElement();
             String driverClass = d.getClass().getName();
             if (!driverClass.startsWith("org.apache.derby."))
                 continue;
@@ -420,8 +454,9 @@ public class AutoloadTest extends BaseJD
     }
 
 	/**
-     	 * Test we can connect successfully to a database.
-	 */
+     * Test we can connect successfully to a database.
+     * @throws SQLException test error
+     */
 	public void testSuccessfulConnect()
        throws SQLException
 	{
@@ -437,9 +472,9 @@ public class AutoloadTest extends BaseJD
     /**
      * Test the error code on an unsuccessful connect
      * to ensure it is not one returned by DriverManager.
+     * @throws SQLException test error
      */
-    public void testUnsuccessfulConnect()
-       throws SQLException
+    public void testUnsuccessfulConnect() throws SQLException
     {     
         // Test we can connect successfully to a database!
         String url = getTestConfiguration().getJDBCUrl("nonexistentDatabase");
@@ -456,7 +491,7 @@ public class AutoloadTest extends BaseJD
     }
     
     /**
-     * Test an explict load of the driver works as well
+     * Test an explicit load of the driver works as well
      * even though the drivers were loaded automatically.
      * @throws Exception 
      *
@@ -564,17 +599,17 @@ public class AutoloadTest extends BaseJD
     }
     
     /**
-     * Return true if a full auto-boot of the engine is expected
+     * @return {@code true} if a full auto-boot of the engine is expected
      * due to jdbc.drivers containing the name of the embedded driver.
      */
     private boolean fullEngineAutoBoot()
     {
         String jdbcDrivers = getSystemProperty("jdbc.drivers");
-        return jdbcDrivers.indexOf("org.apache.derby.jdbc.EmbeddedDriver") != -1;
+        return jdbcDrivers.contains("org.apache.derby.jdbc.EmbeddedDriver");
     }
     
     /**
-     * Test indirect artifiacts through public apis that
+     * Test indirect artifacts through public apis that
      * the embedded engine has not been started.
      */
     
@@ -596,11 +631,11 @@ public class AutoloadTest extends BaseJD
 
     private boolean getRegisteredDrivers(String driver) {
 
-	Enumeration e = DriverManager.getDrivers();
+    Enumeration<Driver> e = DriverManager.getDrivers();
 
         while(e.hasMoreElements())
         {
-                Driver drv = (Driver)e.nextElement();
+                Driver drv = e.nextElement();
                 if(drv.getClass().getName().equals(driver))	
 			return true;
         }
@@ -676,6 +711,7 @@ public class AutoloadTest extends BaseJD
      * thread groups. Since this not testing Derby functionality
      * there's harm to not having a security manager, since
      * no code is executed against Derby.
+     * @return see above
      */
     private boolean hasDerbyThreadGroup() {
         ThreadGroup tg = Thread.currentThread().getThreadGroup();

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/derby_tests.policy
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/derby_tests.policy?rev=1618088&r1=1618087&r2=1618088&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/derby_tests.policy
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/derby_tests.policy
Fri Aug 15 02:12:11 2014
@@ -297,6 +297,12 @@ grant codeBase "${derbyTesting.testjar}d
 
   // Needed by ClasspathSetup for freeing resources.
   permission java.lang.RuntimePermission "closeClassLoader";
+
+  // Needed by AutoloadTest to get at spawned process pid (Unixen) and call jstack:
+  permission java.lang.RuntimePermission "accessDeclaredMembers";
+  permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
+  // Presumes we have a JDK: First "..": back up past "jre"
+  permission java.io.FilePermission "${java.home}${/}..${/}bin${/}-", "execute, read";
 };
 
 //

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java?rev=1618088&r1=1618087&r2=1618088&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java Fri Aug
15 02:12:11 2014
@@ -36,6 +36,7 @@ import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.InterruptedIOException;
 import java.io.RandomAccessFile;
+import java.lang.reflect.Field;
 import java.net.URL;
 import java.sql.SQLException;
 import java.security.AccessController;
@@ -611,6 +612,16 @@ public abstract class BaseTestCase
 	    }
 	}
 
+    /**
+     * Same as {@link #execJavaCmd(
+     * String jvm, String cp, String[] cmd, final File dir, boolean addClassPath)}
+     * but with {@code addClassPath == true}.
+     */
+    public static Process execJavaCmd(
+        String jvm, String cp, String[] cmd, final File dir)
+            throws IOException {
+        return execJavaCmd(jvm, cp, cmd, dir, true);
+    }
 
 	/**
 	 * Execute a java command and return the process.
@@ -628,11 +639,12 @@ public abstract class BaseTestCase
 	 * @param cmd array of java arguments for command
      * @param dir working directory for the sub-process, or {@code null} to
      *            run in the same directory as the main test process
+     * @param addClassPath if {@code true},add classpath
 	 * @return the process that was started
 	 * @throws IOException
 	 */
     public static Process execJavaCmd(
-        String jvm, String cp, String[] cmd, final File dir)
+        String jvm, String cp, String[] cmd, final File dir, boolean addClassPath)
             throws IOException {
 
         // Is this an invocation of a jar file with java -jar ...?
@@ -706,7 +718,7 @@ public abstract class BaseTestCase
             // been set in addition to -jar, as that's probably a mistake in
             // the calling code.
             assertNull("Both -jar and classpath specified", cp);
-        } else {
+        } else if (addClassPath) {
             cmdlist.add("-classpath");
             cmdlist.add(cp == null ? getSystemProperty("java.class.path") : cp);
         }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java?rev=1618088&r1=1618087&r2=1618088&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java Fri
Aug 15 02:12:11 2014
@@ -20,13 +20,23 @@
 package org.apache.derbyTesting.junit;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintStream;
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
 
 import java.util.Timer;
 import java.util.TimerTask;
+import static junit.framework.Assert.assertTrue;
+import static org.apache.derbyTesting.junit.BaseTestCase.execJavaCmd;
+import static org.apache.derbyTesting.junit.BaseTestCase.getJavaExecutableName;
+import static org.apache.derbyTesting.junit.BaseTestCase.isIBMJVM;
+import static org.apache.derbyTesting.junit.BaseTestCase.isWindowsPlatform;
 
 /**
  * Utility code that wraps a spawned process (Java Process object).
@@ -504,4 +514,110 @@ public final class SpawnedProcess {
             process = null;
         }
     }
+
+    /**
+     * Return {@code true} if the subprocess {@code p} has exited within {@code
+     * patience} milliseconds. Sleep {@code sleepInterval} between each check}.
+     * Note: you still need to call one of the {@link #complete} overloads even
+     * if using this method (which is optional). It can be used before trying
+     * a {@link #jstack} call.
+     *
+     * @param patience the maximum milliseconds we want to wait for
+     * @param sleepInterval sleep for this amount of milliseconds before trying
+     *                      testing again if not already exited the first time
+     *                      we check. If patience &lt;= sleepInterval we only
+     *                      check once.
+     * @return true if the process exited before our patience is up.
+     * @throws java.lang.InterruptedException
+     */
+    @SuppressWarnings("SleepWhileInLoop")
+    public boolean waitForExit(long patience, long sleepInterval)
+            throws InterruptedException {
+        boolean completed = false;
+        while (!completed && patience > 0) {
+            try {
+                try {
+                    javaProcess.exitValue();
+                    completed = true;
+                } catch (IllegalThreadStateException e) {
+                    // try again after sleeping
+                    Thread.sleep(sleepInterval);
+                    patience = patience - sleepInterval;
+                }
+            } catch (InterruptedException e) {
+                throw e;
+            }
+        }
+        return completed;
+    }
+
+
+    /**
+     * Return the jstack(1) dump of the process if possible.
+     * It will only work if we are running with a full JDK, not a simple JRE.
+     * It will not work on Windows, and just return an empty string.
+     * @return jstack dump if possible
+     * @throws PrivilegedActionException
+     * @throws InterruptedException
+     */
+    public String jstack()
+            throws PrivilegedActionException, InterruptedException{
+
+        String output = "";
+
+        if (!isWindowsPlatform() && !isIBMJVM()) {
+            // Get the pid of the subprocess using reflection. Dirty,
+            // for Unix there is a private field pid in the implementing
+            // class.
+            final int pid = getPid();
+            final String execName = getJavaExecutableName().replace(
+                    "jre" + File.separator + "bin" + File.separator + "java",
+                    "bin" + File.separator + "jstack");
+            final String[] arguments =
+                    new String[]{Integer.toString(pid)};
+            try {
+                final Process p2 =
+                        execJavaCmd(execName, null, arguments, null, false);
+                final SpawnedProcess spawn2 = new SpawnedProcess(p2, "jstack");
+                spawn2.suppressOutputOnComplete();
+                // Close stdin of the process so that it stops
+                // any waiting for it and exits (shouldn't matter for this test)
+                p2.getOutputStream().close();
+                final int exitCode2 = spawn2.complete(30000); // 30 seconds
+                assertTrue(spawn2.getFailMessage("jstack failed: "),
+                        exitCode2 == 0);
+                output = spawn2.getFullServerOutput();
+            } catch (IOException e) {
+                output = "Tried to catch jstack of hanging subprocess but it "
+                        + "failed (using JDK or JRE?): " + e;
+            }
+        }
+
+        return output;
+    }
+
+    /**
+     * Return the pid if on Unixen, or -1 on Windows (can't be obtained).
+     * @return pid
+     * @throws PrivilegedActionException
+     */
+    public int getPid() throws PrivilegedActionException {
+        if (!isWindowsPlatform() && !isIBMJVM()) {
+            return AccessController.doPrivileged(
+                new PrivilegedExceptionAction<Integer>() {
+                    @Override
+                    public Integer run() throws IllegalAccessException,
+                            NoSuchFieldException {
+                        final Field f = javaProcess.getClass().
+                                getDeclaredField("pid");
+                        f.setAccessible(true);
+
+                        return f.getInt(javaProcess);
+                    }
+                });
+        } else {
+            return -1;
+        }
+    }
+
 }



Mime
View raw message