From a289659523c85c842fddcd8b3c9114aa8a61871a Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Thu, 16 Aug 2007 09:04:53 +0000
Subject: [PATCH] SqlUnitTestRunner: slightly improved, Javadoc added, tests
 added

SVN: 1383
---
 .../cisd/common/db/SqlUnitTestRunner.java     | 114 +++++++--
 .../cisd/common/db/SqlUnitTestRunnerTest.java | 239 ++++++++++++++++++
 2 files changed, 333 insertions(+), 20 deletions(-)
 create mode 100644 common/sourceTest/java/ch/systemsx/cisd/common/db/SqlUnitTestRunnerTest.java

diff --git a/common/source/java/ch/systemsx/cisd/common/db/SqlUnitTestRunner.java b/common/source/java/ch/systemsx/cisd/common/db/SqlUnitTestRunner.java
index cccd691d2a6..f92743817eb 100644
--- a/common/source/java/ch/systemsx/cisd/common/db/SqlUnitTestRunner.java
+++ b/common/source/java/ch/systemsx/cisd/common/db/SqlUnitTestRunner.java
@@ -17,18 +17,48 @@
 package ch.systemsx.cisd.common.db;
 
 import java.io.File;
+import java.io.FileFilter;
 import java.io.FilenameFilter;
 import java.io.PrintWriter;
+import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
 
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 import ch.systemsx.cisd.common.utilities.OSUtilities;
 
 /**
+ * Runner of SQL Unit tests. Needs an implementation of {@link ISqlScriptExecutor} to do the actual tests.
+ * The runner executes all test scripts found in the specified test scripts folder. The folder should have the
+ * following structure
+ * <pre>
+ *   &lt;<i>test script folder</i>&gt;
+ *      &lt;<i>1. test case</i>&gt;
+ *         buildup.sql
+ *         1=&lt;<i>first test</i>&gt;.sql
+ *         2=&lt;<i>second test</i>&gt;.sql
+ *         ...
+ *         teardown.sql
+ *      &lt;<i>2. test case</i>&gt;
+ *         ...
+ *      ...
+ * </pre>
+ * The test cases are executed in lexicographical order of their name. For each test case <code>buildup.sql</code>
+ * will be executed first. The test scripts follow the naming schema
+ * <pre>
+ *   &lt;<i>decimal number</i>&gt;=&lt;<i>test name</i>&gt;.sql
+ * </pre>
+ * They are executed in ascending order of their numbers. Finally <code>teardown.sql</code> is executed. 
+ * If execution of <code>buildup.sql</code> failed all test scripts and the tear down script are skipped.
+ * Note that <code>buildup.sql</code> and <code>teardown.sql</code> are optional.
+ * <p>
+ * A script fails if its execution throws an exception. Its innermost cause (usually a {@link SQLException}) will
+ * be recorded together with the name of the test case and the script. All failed scripts will be recorded.
+ * <p> 
+ * The runner throws an {@link AssertionError} if at least one script failed.
  * 
- *
  * @author Franz-Josef Elmer
  */
 public class SqlUnitTestRunner
@@ -75,23 +105,37 @@ public class SqlUnitTestRunner
     private final ISqlScriptExecutor executor;
     private final PrintWriter writer;
     
+    /**
+     * Creates an instance for the specified SQL script executor and writer.
+     *
+     * @param executor SQL script executor.
+     * @param writer Writer used to monitor running progress by printing test and test case names.
+     */
     public SqlUnitTestRunner(ISqlScriptExecutor executor, PrintWriter writer)
     {
+        assert executor != null : "Undefined SQL script executor.";
+        assert writer != null : "Undefined writer.";
+        
         this.executor = executor;
         this.writer = writer;
     }
 
-    public void run(File testScriptsFolder)
+    /**
+     * Executes all scripts in the specified folder. Does nothing if it does not exists or if it is empty.
+     * 
+     * @throws AssertionError if at least one script failed.
+     */
+    public void run(File testScriptsFolder) throws AssertionError
     {
-        if (testScriptsFolder.exists() == false)
+        if (testScriptsFolder == null || testScriptsFolder.exists() == false)
         {
             return; // no tests
         }
-        File[] testCases = testScriptsFolder.listFiles(new FilenameFilter()
+        File[] testCases = testScriptsFolder.listFiles(new FileFilter()
             {
-                public boolean accept(File dir, String name)
+                public boolean accept(File pathname)
                 {
-                    return name.startsWith(".") == false;
+                    return pathname.isDirectory() && pathname.getName().startsWith(".") == false;
                 }
             });
         Arrays.sort(testCases);
@@ -105,7 +149,9 @@ public class SqlUnitTestRunner
         {
             if (result.isOK() == false)
             {
-                builder.append("Test script ").append(getName(result.getTestScript())).append(" failed because of ");
+                File testScript = result.getTestScript();
+                builder.append("Script '").append(testScript.getName()).append("' of test case '");
+                builder.append(testScript.getParentFile().getName()).append("' failed because of ");
                 builder.append(result.getThrowable()).append(OSUtilities.LINE_SEPARATOR);
             }
         }
@@ -118,20 +164,19 @@ public class SqlUnitTestRunner
 
     private void runTestCase(File testCaseFolder, List<TestResult> results)
     {
-        writer.println("====== Test case " + testCaseFolder.getName() + " ======");
+        writer.println("====== Test case: " + testCaseFolder.getName() + " ======");
         File buildupFile = new File(testCaseFolder, "buildup.sql");
         if (buildupFile.exists())
         {
-            results.add(runScript(buildupFile));
-        }
-        File[] testScripts = testCaseFolder.listFiles(new FilenameFilter()
+            TestResult result = runScript(buildupFile);
+            results.add(result);
+            if (result.isOK() == false)
             {
-                public boolean accept(File dir, String name)
-                {
-                    return name.length() > 1 && name.charAt(1) == '=';
-                }
-            });
-        Arrays.sort(testScripts);
+                writer.println("       script failed: skip test scripts and teardown script.");
+                return;
+            }
+        }
+        File[] testScripts = getTestScripts(testCaseFolder);
         for (File testScript : testScripts)
         {
             results.add(runScript(testScript));
@@ -142,6 +187,25 @@ public class SqlUnitTestRunner
             results.add(runScript(teardownFile));
         }
     }
+
+    private File[] getTestScripts(File testCaseFolder)
+    {
+        File[] testScripts = testCaseFolder.listFiles(new FilenameFilter()
+            {
+                public boolean accept(File dir, String name)
+                {
+                    return getNumber(name) >= 0;
+                }
+            });
+        Arrays.sort(testScripts, new Comparator<File>()
+            {
+                public int compare(File f1, File f2)
+                {
+                    return getNumber(f1.getName()) - getNumber(f2.getName());
+                }
+            });
+        return testScripts;
+    }
     
     private TestResult runScript(File scriptFile)
     {
@@ -160,9 +224,19 @@ public class SqlUnitTestRunner
         }
     }
 
-    private String getName(File testScript)
+    private int getNumber(String name)
     {
-        return testScript.getParentFile().getName() + File.separatorChar + testScript.getName();
+        int index = name.indexOf('=');
+        if (index < 0)
+        {
+            return -1;
+        }
+        try
+        {
+            return Integer.parseInt(name.substring(0, index));
+        } catch (NumberFormatException ex)
+        {
+            return -1;
+        }
     }
-    
 }
\ No newline at end of file
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/db/SqlUnitTestRunnerTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/db/SqlUnitTestRunnerTest.java
new file mode 100644
index 00000000000..8599c316576
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/db/SqlUnitTestRunnerTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.db;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.apache.commons.io.FileUtils;
+import org.jmock.Mockery;
+import org.jmock.api.Action;
+import org.jmock.internal.Cardinality;
+import org.jmock.internal.InvocationExpectationBuilder;
+import org.jmock.lib.action.ThrowAction;
+import org.jmock.lib.action.VoidAction;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.common.utilities.FileUtilities;
+import ch.systemsx.cisd.common.utilities.OSUtilities;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class SqlUnitTestRunnerTest
+{
+    private static final File TEST_SCRIPTS_FOLDER = new File("temporary_test_scripts_folder");
+    
+    private Mockery context;
+    private ISqlScriptExecutor executor;
+    private StringWriter monitor;
+    private SqlUnitTestRunner testRunner;
+    
+    @BeforeMethod
+    public void setup()
+    {
+        TEST_SCRIPTS_FOLDER.mkdir();
+        context = new Mockery();
+        executor = context.mock(ISqlScriptExecutor.class);
+        monitor = new StringWriter();
+        testRunner = new SqlUnitTestRunner(executor, new PrintWriter(monitor));
+    }
+    
+    @AfterMethod
+    public void teardown()
+    {
+        assert FileUtilities.deleteRecursively(TEST_SCRIPTS_FOLDER);
+        // To following line of code should also be called at the end of each test method.
+        // Otherwise one do not known which test failed.
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testNullFolder()
+    {
+        testRunner.run(null);
+        assertEquals("", monitor.toString());
+        
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testNotExistingFolder()
+    {
+        testRunner.run(new File("blabla"));
+        assertEquals("", monitor.toString());
+        
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testEmptyFolder()
+    {
+        testRunner.run(TEST_SCRIPTS_FOLDER);
+        assertEquals("", monitor.toString());
+        
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testNonEmptyFolderButNoTestCases() throws IOException
+    {
+        assert new File(TEST_SCRIPTS_FOLDER, "some file").createNewFile();
+        assert new File(TEST_SCRIPTS_FOLDER, ".folder").mkdir();
+        
+        testRunner.run(TEST_SCRIPTS_FOLDER);
+        assertEquals("", monitor.toString());
+        
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testEmptyTestCase()
+    {
+        assert new File(TEST_SCRIPTS_FOLDER, "my test case").mkdir();
+        
+        testRunner.run(TEST_SCRIPTS_FOLDER);
+        assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR, monitor.toString());
+        
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testNonEmptyTestCaseButNoScripts() throws IOException
+    {
+        File testCaseFolder = new File(TEST_SCRIPTS_FOLDER, "my test case");
+        assert testCaseFolder.mkdir();
+        assert new File(testCaseFolder, "blabla.sql").createNewFile();
+        assert new File(testCaseFolder, "folder").mkdir();
+        
+        testRunner.run(TEST_SCRIPTS_FOLDER);
+        assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR, monitor.toString());
+        
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testTestCaseWithNoTestsButBuildupScript() throws IOException 
+    {
+        File testCaseFolder = new File(TEST_SCRIPTS_FOLDER, "my test case");
+        assert testCaseFolder.mkdir();
+        createScriptPrepareExecutor(new File(testCaseFolder, "buildup.sql"), "-- build up\n", null);
+        
+        testRunner.run(TEST_SCRIPTS_FOLDER);
+        assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR
+                     + "     execute script buildup.sql" + OSUtilities.LINE_SEPARATOR, monitor.toString());
+        
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testTestCaseWithFailingBuildupScript() throws IOException 
+    {
+        File testCaseFolder = new File(TEST_SCRIPTS_FOLDER, "my test case");
+        assert testCaseFolder.mkdir();
+        RuntimeException runtimeException = new RuntimeException("42");
+        createScriptPrepareExecutor(new File(testCaseFolder, "buildup.sql"), "-- build up\n", runtimeException);
+        
+        try
+        {
+            testRunner.run(TEST_SCRIPTS_FOLDER);
+            fail("AssertionError expected");
+        } catch (AssertionError e)
+        {
+            assertEquals("Script 'buildup.sql' of test case 'my test case' failed because of "
+                         + runtimeException + OSUtilities.LINE_SEPARATOR, e.getMessage());
+        }
+        assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR
+                     + "     execute script buildup.sql" + OSUtilities.LINE_SEPARATOR
+                     + "       script failed: skip test scripts and teardown script." + OSUtilities.LINE_SEPARATOR,
+                     monitor.toString());
+        
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testOrderOfExecutingTestScripts() throws IOException 
+    {
+        File testCaseFolder = new File(TEST_SCRIPTS_FOLDER, "my test case");
+        assert testCaseFolder.mkdir();
+        RuntimeException runtimeException = new RuntimeException("42");
+        createScriptPrepareExecutor(new File(testCaseFolder, "9=b.sql"), "Select 9\n", runtimeException);
+        FileUtils.writeStringToFile(new File(testCaseFolder, "abc=abc.sql"), "Select abc\n");
+        createScriptPrepareExecutor(new File(testCaseFolder, "10=c.sql"), "Select 10\n", null);
+        createScriptPrepareExecutor(new File(testCaseFolder, "1=a.sql"), "Select 1\n", null);
+        
+        try
+        {
+            testRunner.run(TEST_SCRIPTS_FOLDER);
+            fail("AssertionError expected");
+        } catch (AssertionError e)
+        {
+            assertEquals("Script '9=b.sql' of test case 'my test case' failed because of "
+                         + runtimeException + OSUtilities.LINE_SEPARATOR, e.getMessage());
+        }
+        assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR
+                     + "     execute script 1=a.sql" + OSUtilities.LINE_SEPARATOR
+                     + "     execute script 9=b.sql" + OSUtilities.LINE_SEPARATOR
+                     + "     execute script 10=c.sql" + OSUtilities.LINE_SEPARATOR,
+                     monitor.toString());
+        
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testOrderOfExecutingTestCases() throws IOException 
+    {
+        assert new File(TEST_SCRIPTS_FOLDER, "TC002").mkdir();
+        File testCaseFolder1 = new File(TEST_SCRIPTS_FOLDER, "TC001");
+        assert testCaseFolder1.mkdir();
+        createScriptPrepareExecutor(new File(testCaseFolder1, "buildup.sql"), "create table\n", null);
+        createScriptPrepareExecutor(new File(testCaseFolder1, "1=a.sql"), "Select 1\n", null);
+        createScriptPrepareExecutor(new File(testCaseFolder1, "2=b.sql"), "Select 2\n", null);
+        createScriptPrepareExecutor(new File(testCaseFolder1, "teardown.sql"), "drop table\n", null);
+        
+        testRunner.run(TEST_SCRIPTS_FOLDER);
+        assertEquals("====== Test case: TC001 ======" + OSUtilities.LINE_SEPARATOR
+                     + "     execute script buildup.sql" + OSUtilities.LINE_SEPARATOR
+                     + "     execute script 1=a.sql" + OSUtilities.LINE_SEPARATOR
+                     + "     execute script 2=b.sql" + OSUtilities.LINE_SEPARATOR
+                     + "     execute script teardown.sql" + OSUtilities.LINE_SEPARATOR
+                     + "====== Test case: TC002 ======" + OSUtilities.LINE_SEPARATOR,
+                     monitor.toString());
+   
+   context.assertIsSatisfied();
+    }
+    
+    private void createScriptPrepareExecutor(File scriptFile, String script, Throwable throwable) throws IOException
+    {
+        FileUtils.writeStringToFile(scriptFile, script);
+        InvocationExpectationBuilder builder = new InvocationExpectationBuilder();
+        builder.setCardinality(new Cardinality(1, 1));
+        builder.of(executor).execute(script);
+        Action action = throwable == null ? new VoidAction() : new ThrowAction(throwable);
+        context.addExpectation(builder.toExpectation(action));
+    }
+    
+}
-- 
GitLab