From 34046b58e2d13a65ff47bb03435734302c2c1ad5 Mon Sep 17 00:00:00 2001
From: ribeaudc <ribeaudc>
Date: Sun, 19 Aug 2007 00:27:29 +0000
Subject: [PATCH] add: - TestAppender: for catching logging output in tests
 change: - Update some unit tests to use upper defined class

SVN: 1416
---
 .../crowd/CrowdAuthenticationServiceTest.java | 259 ++++++++----------
 .../common/utilities/FileWatcherTest.java     |  80 ++----
 .../cisd/common/utilities/TestAppender.java   | 101 +++++++
 .../dbmigration/DBMigrationEngineTest.java    | 107 +++-----
 4 files changed, 272 insertions(+), 275 deletions(-)
 create mode 100644 common/sourceTest/java/ch/systemsx/cisd/common/utilities/TestAppender.java

diff --git a/authentication/sourceTest/java/ch/systemsx/cisd/authentication/crowd/CrowdAuthenticationServiceTest.java b/authentication/sourceTest/java/ch/systemsx/cisd/authentication/crowd/CrowdAuthenticationServiceTest.java
index 7aced698974..ad2a8abaffa 100644
--- a/authentication/sourceTest/java/ch/systemsx/cisd/authentication/crowd/CrowdAuthenticationServiceTest.java
+++ b/authentication/sourceTest/java/ch/systemsx/cisd/authentication/crowd/CrowdAuthenticationServiceTest.java
@@ -19,14 +19,9 @@ package ch.systemsx.cisd.authentication.crowd;
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.fail;
 
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
 import java.util.Date;
-import java.util.Properties;
 
-import org.apache.log4j.ConsoleAppender;
-import org.apache.log4j.PatternLayout;
-import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.Level;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.testng.annotations.AfterMethod;
@@ -37,193 +32,180 @@ import ch.systemsx.cisd.authentication.IAuthenticationService;
 import ch.systemsx.cisd.authentication.Principal;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.utilities.OSUtilities;
+import ch.systemsx.cisd.common.utilities.TestAppender;
 
 /**
  * Tests for {@link CrowdAuthenticationService}.
- *
+ * 
  * @author Franz-Josef Elmer
  */
 public class CrowdAuthenticationServiceTest
 {
 
     private static final String URL = "url";
-    
+
     private static final String APPLICATION = "appli & cation";
+
     private static final String APPLICATION_ESCAPED = "appli &amp; cation";
-    
+
     private static final String APPLICATION_PASSWORD = "<password>";
+
     private static final String APPLICATION_PASSWORD_ESCAPED = "&lt;password&gt;";
-    
+
     private static final String APPLICATION_TOKEN = "application<&>token";
+
     private static final String APPLICATION_TOKEN_ESACPED = "application&lt;&amp;&gt;token";
-    
+
     private static final String USER = "<user>";
+
     private static final String USER_ESCAPED = "&lt;user&gt;";
-    
+
     private static final String USER_PASSWORD = "pass\"word\"";
+
     private static final String USER_PASSWORD_ESCAPED = "pass&quot;word&quot;";
 
     private Mockery context;
+
     private IRequestExecutor executor;
+
     private IAuthenticationService authenticationService;
-    private PrintStream systemOut;
-    private PrintStream systemErr;
-    private ByteArrayOutputStream logRecorder;
-    
+
+    private TestAppender logRecorder;
+
     @BeforeMethod
     public void setup()
     {
         context = new Mockery();
         executor = context.mock(IRequestExecutor.class);
         authenticationService = new CrowdAuthenticationService(URL, APPLICATION, APPLICATION_PASSWORD, executor);
-        logRecorder = new ByteArrayOutputStream();
-        systemOut = System.out;
-        systemErr = System.err;
-        System.setErr(new PrintStream(logRecorder));
-        System.setOut(new PrintStream(logRecorder));
-        Properties properties = new Properties();
-        properties.setProperty("log4j.rootLogger", "DEBUG, TestAppender");
-        properties.setProperty("log4j.appender.TestAppender", ConsoleAppender.class.getName());
-        properties.setProperty("log4j.appender.TestAppender.layout", PatternLayout.class.getName());
-        properties.setProperty("log4j.appender.TestAppender.layout.ConversionPattern", "%-5p %c - %m%n");
-        PropertyConfigurator.configure(properties);
+        logRecorder = new TestAppender("%-5p %c - %m%n", Level.DEBUG);
     }
-    
+
     @AfterMethod
     public void tearDown()
     {
-        if (systemOut != null)
-        {
-            System.setOut(systemOut);
-        }
-        if (systemErr != null)
-        {
-            System.setErr(systemErr);
-        }
+        logRecorder.reset();
         // 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 testSuccessfullApplicationAuthentication()
     {
         context.checking(new Expectations()
-        {
             {
-                Object[] parameters = new Object[] {APPLICATION_ESCAPED, APPLICATION_PASSWORD_ESCAPED};
-                String message = CrowdAuthenticationService.AUTHENTICATE_APPL.format(parameters);
-                one(executor).execute(URL, message);
-                will(returnValue(createXMLElement(CrowdSoapElements.TOKEN, APPLICATION_TOKEN_ESACPED)));
-            }
-
-        });
-        
-        logRecorder.reset();
+                {
+                    Object[] parameters = new Object[]
+                        { APPLICATION_ESCAPED, APPLICATION_PASSWORD_ESCAPED };
+                    String message = CrowdAuthenticationService.AUTHENTICATE_APPL.format(parameters);
+                    one(executor).execute(URL, message);
+                    will(returnValue(createXMLElement(CrowdSoapElements.TOKEN, APPLICATION_TOKEN_ESACPED)));
+                }
+
+            });
         String result = authenticationService.authenticateApplication();
         assertEquals(APPLICATION_TOKEN, result);
-        assertEquals(createDebugLogEntry("CROWD: application '" + APPLICATION + "' successfully authenticated."), 
-                     getLogContent());
-        
+        assertEquals(createDebugLogEntry("CROWD: application '" + APPLICATION + "' successfully authenticated."),
+                logRecorder.getLogContent());
+
         context.assertIsSatisfied();
     }
-    
+
     @Test
     public void testFailedApplicationAuthentication()
     {
         context.checking(new Expectations()
-        {
             {
-                Object[] parameters = new Object[] {APPLICATION_ESCAPED, APPLICATION_PASSWORD_ESCAPED};
-                String message = CrowdAuthenticationService.AUTHENTICATE_APPL.format(parameters);
-                one(executor).execute(URL, message);
-                will(returnValue("error"));
-            }
-        });
-        
-        logRecorder.reset();
+                {
+                    Object[] parameters = new Object[]
+                        { APPLICATION_ESCAPED, APPLICATION_PASSWORD_ESCAPED };
+                    String message = CrowdAuthenticationService.AUTHENTICATE_APPL.format(parameters);
+                    one(executor).execute(URL, message);
+                    will(returnValue("error"));
+                }
+            });
         String result = authenticationService.authenticateApplication();
         assertEquals(null, result);
-        assertEquals(createDebugLogEntry("Element '" + CrowdSoapElements.TOKEN + "' could not be found in 'error'.") 
-                     + OSUtilities.LINE_SEPARATOR + createErrorLogEntry("CROWD: application '" 
-                     + APPLICATION + "' failed to authenticate."), getLogContent());
-        
+        assertEquals(createDebugLogEntry("Element '" + CrowdSoapElements.TOKEN + "' could not be found in 'error'.")
+                + OSUtilities.LINE_SEPARATOR
+                + createErrorLogEntry("CROWD: application '" + APPLICATION + "' failed to authenticate."), logRecorder
+                .getLogContent());
+
         context.assertIsSatisfied();
     }
-    
+
     @Test
     public void testSuccessfullUserAuthentication()
     {
         context.checking(new Expectations()
-        {
             {
-                Object[] parameters = new Object[] {APPLICATION_ESCAPED, APPLICATION_TOKEN_ESACPED, USER_ESCAPED, 
-                                                    USER_PASSWORD_ESCAPED};
-                String message = CrowdAuthenticationService.AUTHENTICATE_USER.format(parameters);
-                one(executor).execute(URL, message);
-                will(returnValue(createXMLElement(CrowdSoapElements.OUT, APPLICATION_TOKEN_ESACPED)));
-            }
-        });
-        
-        logRecorder.reset();
+                {
+                    Object[] parameters = new Object[]
+                        { APPLICATION_ESCAPED, APPLICATION_TOKEN_ESACPED, USER_ESCAPED, USER_PASSWORD_ESCAPED };
+                    String message = CrowdAuthenticationService.AUTHENTICATE_USER.format(parameters);
+                    one(executor).execute(URL, message);
+                    will(returnValue(createXMLElement(CrowdSoapElements.OUT, APPLICATION_TOKEN_ESACPED)));
+                }
+            });
         boolean result = authenticationService.authenticateUser(APPLICATION_TOKEN, USER, USER_PASSWORD);
         assertEquals(true, result);
-        assertEquals(createInfoLogEntry("CROWD: authentication of user '" + USER + "', application '" + APPLICATION 
-                                        + "': SUCCESS."), getLogContent());
-        
+        assertEquals(createInfoLogEntry("CROWD: authentication of user '" + USER + "', application '" + APPLICATION
+                + "': SUCCESS."), logRecorder.getLogContent());
+
         context.assertIsSatisfied();
     }
-    
+
     @Test
     public void testFailedUserAuthentication()
     {
         context.checking(new Expectations()
-        {
             {
-                Object[] parameters = new Object[] {APPLICATION_ESCAPED, APPLICATION_TOKEN_ESACPED, USER_ESCAPED, 
-                        USER_PASSWORD_ESCAPED};
-                String message = CrowdAuthenticationService.AUTHENTICATE_USER.format(parameters);
-                one(executor).execute(URL, message);
-                will(returnValue("error"));
-            }
-        });
-        
-        logRecorder.reset();
+                {
+                    Object[] parameters = new Object[]
+                        { APPLICATION_ESCAPED, APPLICATION_TOKEN_ESACPED, USER_ESCAPED, USER_PASSWORD_ESCAPED };
+                    String message = CrowdAuthenticationService.AUTHENTICATE_USER.format(parameters);
+                    one(executor).execute(URL, message);
+                    will(returnValue("error"));
+                }
+            });
         boolean result = authenticationService.authenticateUser(APPLICATION_TOKEN, USER, USER_PASSWORD);
         assertEquals(false, result);
-        assertEquals(createDebugLogEntry("Element '" + CrowdSoapElements.OUT + "' could not be found in 'error'.") 
-                + OSUtilities.LINE_SEPARATOR + createInfoLogEntry("CROWD: authentication of user '" + USER 
-                + "', application '" + APPLICATION + "': FAILED."), getLogContent());
-        
+        assertEquals(createDebugLogEntry("Element '" + CrowdSoapElements.OUT + "' could not be found in 'error'.")
+                + OSUtilities.LINE_SEPARATOR
+                + createInfoLogEntry("CROWD: authentication of user '" + USER + "', application '" + APPLICATION
+                        + "': FAILED."), logRecorder.getLogContent());
+
         context.assertIsSatisfied();
     }
-    
+
     @Test
     public void testSuccessfullPrincipalRetrieval()
     {
         context.checking(new Expectations()
-        {
-            {
-                Object[] parameters = new Object[] {APPLICATION_ESCAPED, APPLICATION_TOKEN_ESACPED, USER_ESCAPED};
-                String message = CrowdAuthenticationService.FIND_PRINCIPAL_BY_NAME.format(parameters);
-                one(executor).execute(URL, message);
-                String element = createSOAPAttribute("sn", "Stepka");
-                element += createSOAPAttribute("invalidPasswordAttempts", "0");
-                element += createSOAPAttribute("requiresPasswordChange", "false");
-                element += createSOAPAttribute("mail", "justen.stepka@atlassian.com");
-                element += createSOAPAttribute("lastAuthenticated", "1169440408520");
-                element += createSOAPAttribute("givenName", "Justen");
-                element += createSOAPAttribute("passwordLastChanged", "1168995491407");
-                will(returnValue("<a>" + element + "</a>"));
-            }
-
-            private String createSOAPAttribute(String name, String value)
             {
-                return "<SOAPAttribute><name>" + name + "</name><values><ns1:string xmlns:ns1=\"urn:SecurityServer\">" + value + "</ns1:string></values></SOAPAttribute>";
-            }
-        });
-        
-        logRecorder.reset();
+                {
+                    Object[] parameters = new Object[]
+                        { APPLICATION_ESCAPED, APPLICATION_TOKEN_ESACPED, USER_ESCAPED };
+                    String message = CrowdAuthenticationService.FIND_PRINCIPAL_BY_NAME.format(parameters);
+                    one(executor).execute(URL, message);
+                    String element = createSOAPAttribute("sn", "Stepka");
+                    element += createSOAPAttribute("invalidPasswordAttempts", "0");
+                    element += createSOAPAttribute("requiresPasswordChange", "false");
+                    element += createSOAPAttribute("mail", "justen.stepka@atlassian.com");
+                    element += createSOAPAttribute("lastAuthenticated", "1169440408520");
+                    element += createSOAPAttribute("givenName", "Justen");
+                    element += createSOAPAttribute("passwordLastChanged", "1168995491407");
+                    will(returnValue("<a>" + element + "</a>"));
+                }
+
+                private String createSOAPAttribute(String name, String value)
+                {
+                    return "<SOAPAttribute><name>" + name
+                            + "</name><values><ns1:string xmlns:ns1=\"urn:SecurityServer\">" + value
+                            + "</ns1:string></values></SOAPAttribute>";
+                }
+            });
         Principal result = authenticationService.getPrincipal(APPLICATION_TOKEN, USER);
         assertEquals("Justen", result.getFirstName());
         assertEquals("Stepka", result.getLastName());
@@ -232,25 +214,23 @@ public class CrowdAuthenticationServiceTest
         assertEquals(Boolean.FALSE, result.getProperty("requiresPasswordChange"));
         assertEquals(new Date(1169440408520L), result.getProperty("lastAuthenticated"));
         assertEquals(new Date(1168995491407L), result.getProperty("passwordLastChanged"));
-        assertEquals("", getLogContent());
-        
+        assertEquals("", logRecorder.getLogContent());
         context.assertIsSatisfied();
     }
-    
+
     @Test
     public void testFailedPrincipalRetrieval()
     {
         context.checking(new Expectations()
-        {
             {
-                Object[] parameters = new Object[] {APPLICATION_ESCAPED, APPLICATION_TOKEN_ESACPED, USER_ESCAPED};
-                String message = CrowdAuthenticationService.FIND_PRINCIPAL_BY_NAME.format(parameters);
-                one(executor).execute(URL, message);
-                will(returnValue("<a></a>"));
-            }
-        });
-        
-        logRecorder.reset();
+                {
+                    Object[] parameters = new Object[]
+                        { APPLICATION_ESCAPED, APPLICATION_TOKEN_ESACPED, USER_ESCAPED };
+                    String message = CrowdAuthenticationService.FIND_PRINCIPAL_BY_NAME.format(parameters);
+                    one(executor).execute(URL, message);
+                    will(returnValue("<a></a>"));
+                }
+            });
         try
         {
             authenticationService.getPrincipal(APPLICATION_TOKEN, USER);
@@ -259,38 +239,33 @@ public class CrowdAuthenticationServiceTest
         {
             assertEquals("CROWD: Principal information for user '" + USER + "' could not be obtained.", e.getMessage());
         }
-        
-        assertEquals(createDebugLogEntry("No SOAPAttribute element could be found in the SOAP XML response."), 
-                     getLogContent());
-        
+
+        assertEquals(createDebugLogEntry("No SOAPAttribute element could be found in the SOAP XML response."),
+                logRecorder.getLogContent());
+
         context.assertIsSatisfied();
     }
-    
+
     private String createDebugLogEntry(String message)
     {
         return createLogEntry("DEBUG", message);
     }
-    
+
     private String createInfoLogEntry(String message)
     {
         return createLogEntry("INFO ", message);
     }
-    
+
     private String createErrorLogEntry(String message)
     {
         return createLogEntry("ERROR", message);
     }
-    
+
     private String createLogEntry(String level, String message)
     {
-        return level + " OPERATION." + authenticationService.getClass().getName() + " - " + message; 
+        return level + " OPERATION." + authenticationService.getClass().getName() + " - " + message;
     }
-    
-    private String getLogContent()
-    {
-        return new String(logRecorder.toByteArray()).trim();
-    }
-    
+
     private String createXMLElement(String element, String content)
     {
         return "<" + element + ">" + content + "</" + element + ">";
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileWatcherTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileWatcherTest.java
index 1afa191c89b..7995a71d05b 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileWatcherTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/FileWatcherTest.java
@@ -17,20 +17,15 @@
 package ch.systemsx.cisd.common.utilities;
 
 import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.fail;
 
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
-import java.io.PrintStream;
-import java.util.Properties;
 import java.util.Timer;
 
 import org.apache.commons.io.FileUtils;
-import org.apache.log4j.ConsoleAppender;
-import org.apache.log4j.PatternLayout;
-import org.apache.log4j.PropertyConfigurator;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
+import org.apache.log4j.Level;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -52,11 +47,7 @@ public class FileWatcherTest
 
     private static final File tmpFile2 = new File(workingDirectory, "tmpFile2");
 
-    private PrintStream systemOut;
-
-    private PrintStream systemErr;
-
-    private ByteArrayOutputStream logRecorder;
+    private TestAppender testAppender;
 
     private volatile boolean onChangeCalled;
 
@@ -68,81 +59,48 @@ public class FileWatcherTest
         file.deleteOnExit();
     }
 
-    private final static Properties createLogProperties()
-    {
-        Properties properties = new Properties();
-        properties.setProperty("log4j.rootLogger", "TRACE, TestAppender");
-        properties.setProperty("log4j.appender.TestAppender", ConsoleAppender.class.getName());
-        properties.setProperty("log4j.appender.TestAppender.layout", PatternLayout.class.getName());
-        properties.setProperty("log4j.appender.TestAppender.layout.ConversionPattern", "%m%n");
-        return properties;
-    }
-
-    private final String getLogContent()
-    {
-        return new String(logRecorder.toByteArray()).trim();
-    }
-
-    @BeforeClass
+    @BeforeMethod(alwaysRun = true)
     public final void setUp() throws IOException
     {
-        logRecorder = new ByteArrayOutputStream();
-        systemOut = System.out;
-        systemErr = System.err;
-        System.setErr(new PrintStream(logRecorder));
-        System.setOut(new PrintStream(logRecorder));
-        PropertyConfigurator.configure(createLogProperties());
         workingDirectory.mkdirs();
         assert workingDirectory.isDirectory();
         createNewFile(tmpFile1);
         createNewFile(tmpFile2);
         workingDirectory.deleteOnExit();
+        testAppender = new TestAppender(Level.TRACE);
     }
 
-    @AfterClass
+    @AfterMethod(alwaysRun = true)
     public final void tearDown()
     {
+        testAppender.reset();
         FileUtilities.deleteRecursively(workingDirectory);
-        if (systemOut != null)
-        {
-            System.setOut(systemOut);
-        }
-        if (systemErr != null)
-        {
-            System.setErr(systemErr);
-        }
     }
 
-    @BeforeMethod
-    public final void beforeMethod()
-    {
-        logRecorder.reset();
-    }
-
-    // @Test
+    @Test
     public final void testWithNonExistingFile()
     {
         File file = new File(workingDirectory, "doesNotExist");
         assert file.exists() == false;
         new TestFileWatcher(file).run();
-        assertEquals(String.format(FileWatcher.DOES_NOT_EXIST_FORMAT, file), getLogContent());
+        assertEquals(String.format(FileWatcher.DOES_NOT_EXIST_FORMAT, file), testAppender.getLogContent());
     }
 
-    // @Test
+    @Test
     public final void testNonChangingFile()
     {
         new TestFileWatcher(tmpFile1).run();
-        assertEquals(String.format(FileWatcher.HAS_NOT_CHANGED_FORMAT, tmpFile1), getLogContent());
+        assertEquals(String.format(FileWatcher.HAS_NOT_CHANGED_FORMAT, tmpFile1), testAppender.getLogContent());
     }
 
-    // @Test
+    @Test
     public final void testFileHasChanged() throws IOException
     {
         onChangeCalled = false;
         FileWatcher fileWatcher = new TestFileWatcher(tmpFile1);
         FileUtils.touch(tmpFile1);
         fileWatcher.run();
-        assertEquals(String.format(FileWatcher.HAS_CHANGED_FORMAT, tmpFile1), getLogContent());
+        assertEquals(String.format(FileWatcher.HAS_CHANGED_FORMAT, tmpFile1), testAppender.getLogContent());
         assertEquals(true, onChangeCalled);
     }
 
@@ -157,11 +115,17 @@ public class FileWatcherTest
     }
 
     @Test(dependsOnMethods = "testWithTimer", timeOut = 5000, groups = "slow")
-    public final void testOnChangeCalled() throws InterruptedException
+    public final void testOnChangeCalled()
     {
         while (onChangeCalled == false)
         {
-            Thread.sleep(200);
+            try
+            {
+                Thread.sleep(200);
+            } catch (InterruptedException ex)
+            {
+                fail(ex.getMessage());
+            }
         }
     }
 
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/TestAppender.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/TestAppender.java
new file mode 100644
index 00000000000..27b969af470
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/TestAppender.java
@@ -0,0 +1,101 @@
+/*
+ * 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.utilities;
+
+import java.io.ByteArrayOutputStream;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.WriterAppender;
+
+/**
+ * A <code>WriterAppender</code> extension for unit tests that are interested in log output and want to test it.
+ * <p>
+ * It internally uses a <code>ByteArrayOutputStream</code> which collect the log output and can return it using
+ * {@link #getLogContent()}. It is a good idea to reset the log recorder by calling {@link #resetLogContent()} before
+ * calling a unit test method.
+ * </p>
+ * 
+ * @author Christian Ribeaud
+ */
+public final class TestAppender extends WriterAppender
+{
+    private final ByteArrayOutputStream logRecorder;
+
+    /**
+     * Constructor with default pattern layout (which is {@link PatternLayout#DEFAULT_CONVERSION_PATTERN}) and
+     * {@link Level#DEBUG} as log level.
+     */
+    public TestAppender()
+    {
+        this(Level.DEBUG);
+    }
+
+    /**
+     * Constructor with default pattern layout (which is {@link PatternLayout#DEFAULT_CONVERSION_PATTERN}).
+     * 
+     * @param logLevel
+     */
+    public TestAppender(Level logLevel)
+    {
+        this(null, logLevel);
+    }
+
+    public TestAppender(String pattern, Level logLevel)
+    {
+        super();
+        logRecorder = new ByteArrayOutputStream();
+        setWriter(createWriter(logRecorder));
+        setLayout(createLayout(pattern));
+        configureRootLogger(logLevel);
+    }
+
+    private void configureRootLogger(Level logLevel)
+    {
+        Logger root = Logger.getRootLogger();
+        root.addAppender(this);
+        root.setLevel(logLevel);
+    }
+
+    protected Layout createLayout(String pattern)
+    {
+        return new PatternLayout(pattern);
+    }
+
+    public final String getLogContent()
+    {
+        return new String(logRecorder.toByteArray()).trim();
+    }
+
+    public final void resetLogContent()
+    {
+        logRecorder.reset();
+    }
+
+    //
+    // WriterAppender
+    //
+
+    @Override
+    public final void reset()
+    {
+        Logger.getRootLogger().removeAppender(this);
+        super.reset();
+    }
+}
\ No newline at end of file
diff --git a/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/DBMigrationEngineTest.java b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/DBMigrationEngineTest.java
index 1d095db7386..70e2c1a6127 100644
--- a/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/DBMigrationEngineTest.java
+++ b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/DBMigrationEngineTest.java
@@ -20,18 +20,13 @@ import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.fail;
 
-import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.sql.SQLException;
 import java.util.Date;
-import java.util.Properties;
 
-import org.apache.log4j.ConsoleAppender;
-import org.apache.log4j.PatternLayout;
-import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.Level;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.springframework.jdbc.BadSqlGrammarException;
@@ -42,6 +37,7 @@ import org.testng.annotations.Test;
 import ch.systemsx.cisd.common.db.ISqlScriptExecutor;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.utilities.OSUtilities;
+import ch.systemsx.cisd.common.utilities.TestAppender;
 
 /**
  * Tests of {@link DBMigrationEngine} using mocks for database and {@link SqlScriptProvider}.
@@ -80,14 +76,10 @@ public class DBMigrationEngineTest
     private IDatabaseVersionLogDAO logDAO;
 
     private ISqlScriptExecutor scriptExecutor;
-    
-    private IMassUploader massUploader;
-
-    private PrintStream systemOut;
 
-    private PrintStream systemErr;
+    private IMassUploader massUploader;
 
-    private ByteArrayOutputStream logRecorder;
+    private TestAppender logRecorder;
 
     @BeforeMethod
     public void setUp()
@@ -99,30 +91,13 @@ public class DBMigrationEngineTest
         logDAO = context.mock(IDatabaseVersionLogDAO.class);
         scriptExecutor = context.mock(ISqlScriptExecutor.class);
         massUploader = context.mock(IMassUploader.class);
-        logRecorder = new ByteArrayOutputStream();
-        systemOut = System.out;
-        systemErr = System.err;
-        System.setErr(new PrintStream(logRecorder));
-        System.setOut(new PrintStream(logRecorder));
-        Properties properties = new Properties();
-        properties.setProperty("log4j.rootLogger", "DEBUG, TestAppender");
-        properties.setProperty("log4j.appender.TestAppender", ConsoleAppender.class.getName());
-        properties.setProperty("log4j.appender.TestAppender.layout", PatternLayout.class.getName());
-        properties.setProperty("log4j.appender.TestAppender.layout.ConversionPattern", "%-5p %c - %m%n");
-        PropertyConfigurator.configure(properties);
+        logRecorder = new TestAppender("%-5p %c - %m%n", Level.DEBUG);
     }
 
     @AfterMethod
     public void tearDown()
     {
-        if (systemOut != null)
-        {
-            System.setOut(systemOut);
-        }
-        if (systemErr != null)
-        {
-            System.setErr(systemErr);
-        }
+        logRecorder.reset();
         // 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();
@@ -166,7 +141,8 @@ public class DBMigrationEngineTest
                     expectSuccessfulExecution(new Script("schema", "schema code"), version);
                     one(scriptProvider).getMassUploadFiles(version);
                     final File massUploadFile = new File("1=materials.csv");
-                    will(returnValue(new File[] { massUploadFile }));
+                    will(returnValue(new File[]
+                        { massUploadFile }));
                     one(scriptProvider).getDataScript(version);
                     expectSuccessfulExecution(new Script("data", "data code"), version);
                     one(massUploader).performMassUpload(massUploadFile);
@@ -175,13 +151,11 @@ public class DBMigrationEngineTest
                 }
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, true);
-
-        logRecorder.reset();
         migrationEngine.migrateTo(version);
         assertEquals("INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                 + "Database 'my 1. database' does not exist." + OSUtilities.LINE_SEPARATOR
                 + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
-                + "Database 'my 2. database' version 042 has been successfully created.", getLogContent());
+                + "Database 'my 2. database' version 042 has been successfully created.", logRecorder.getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -213,8 +187,6 @@ public class DBMigrationEngineTest
                 }
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, true);
-
-        logRecorder.reset();
         try
         {
             migrationEngine.migrateTo(version);
@@ -224,7 +196,7 @@ public class DBMigrationEngineTest
             assertSame(exception, e);
         }
         assertEquals("INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
-                + "Database 'my 1. database' does not exist.", getLogContent());
+                + "Database 'my 1. database' does not exist.", logRecorder.getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -263,13 +235,11 @@ public class DBMigrationEngineTest
                 }
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
-
-        logRecorder.reset();
         migrationEngine.migrateTo(version);
         assertEquals("INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                 + "Database 'my 1. database' does not exist." + OSUtilities.LINE_SEPARATOR
                 + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
-                + "Database 'my 2. database' version 042 has been successfully created.", getLogContent());
+                + "Database 'my 2. database' version 042 has been successfully created.", logRecorder.getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -307,13 +277,11 @@ public class DBMigrationEngineTest
                 }
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
-
-        logRecorder.reset();
         migrationEngine.migrateTo(version);
         assertEquals("INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                 + "Database 'my 1. database' does not exist." + OSUtilities.LINE_SEPARATOR
                 + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
-                + "Database 'my 2. database' version 042 has been successfully created.", getLogContent());
+                + "Database 'my 2. database' version 042 has been successfully created.", logRecorder.getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -345,8 +313,6 @@ public class DBMigrationEngineTest
                 }
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
-
-        logRecorder.reset();
         String message = "No schema script found for version 042";
         try
         {
@@ -358,7 +324,8 @@ public class DBMigrationEngineTest
         }
         assertEquals("INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                 + "Database 'my 1. database' does not exist." + OSUtilities.LINE_SEPARATOR
-                + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, getLogContent());
+                + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, logRecorder
+                .getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -389,8 +356,6 @@ public class DBMigrationEngineTest
                 }
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
-
-        logRecorder.reset();
         String message = "Missing script createLog.sql";
         try
         {
@@ -402,7 +367,8 @@ public class DBMigrationEngineTest
         }
         assertEquals("INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                 + "Database 'my 1. database' does not exist." + OSUtilities.LINE_SEPARATOR
-                + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, getLogContent());
+                + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, logRecorder
+                .getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -435,8 +401,6 @@ public class DBMigrationEngineTest
                 }
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
-
-        logRecorder.reset();
         try
         {
             migrationEngine.migrateTo(version);
@@ -449,7 +413,7 @@ public class DBMigrationEngineTest
                 + "Database 'my database' does not exist." + OSUtilities.LINE_SEPARATOR
                 + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Script 'createLog.sql' failed:"
                 + OSUtilities.LINE_SEPARATOR + "create log" + OSUtilities.LINE_SEPARATOR + getStackTrace(exception),
-                getLogContent());
+                logRecorder.getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -483,10 +447,10 @@ public class DBMigrationEngineTest
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
 
-        logRecorder.reset();
         migrationEngine.migrateTo(version);
         assertEquals("DEBUG OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
-                + "No migration needed for database 'my database'. It has the right version (042).", getLogContent());
+                + "No migration needed for database 'my database'. It has the right version (042).", logRecorder
+                .getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -527,12 +491,12 @@ public class DBMigrationEngineTest
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
 
-        logRecorder.reset();
         migrationEngine.migrateTo(toVersion);
         assertEquals("INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                 + "Trying to migrate database 'my 1. database' from version 099 to 101." + OSUtilities.LINE_SEPARATOR
                 + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
-                + "Database 'my 2. database' successfully migrated from version 099 to 101.", getLogContent());
+                + "Database 'my 2. database' successfully migrated from version 099 to 101.", logRecorder
+                .getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -570,7 +534,6 @@ public class DBMigrationEngineTest
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
 
-        logRecorder.reset();
         String errorMessage =
                 "Cannot migrate database 'my 2. database' from version 099 to 100 because of "
                         + "missing migration script.";
@@ -584,7 +547,8 @@ public class DBMigrationEngineTest
         }
         assertEquals("INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                 + "Trying to migrate database 'my 1. database' from version 099 to 101." + OSUtilities.LINE_SEPARATOR
-                + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + errorMessage, getLogContent());
+                + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + errorMessage, logRecorder
+                .getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -619,7 +583,6 @@ public class DBMigrationEngineTest
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
 
-        logRecorder.reset();
         String errorMessage = "Cannot revert database 'my database' from version 101 to earlier version 099.";
         try
         {
@@ -629,8 +592,8 @@ public class DBMigrationEngineTest
         {
             assertEquals(errorMessage, e.getMessage());
         }
-        assertEquals("ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + errorMessage,
-                getLogContent());
+        assertEquals("ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + errorMessage, logRecorder
+                .getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -658,7 +621,6 @@ public class DBMigrationEngineTest
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, true);
 
-        logRecorder.reset();
         String message = "Inconsistent database: Empty database version log.";
         try
         {
@@ -668,7 +630,8 @@ public class DBMigrationEngineTest
         {
             assertEquals(message, e.getMessage());
         }
-        assertEquals("ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, getLogContent());
+        assertEquals("ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, logRecorder
+                .getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -702,7 +665,6 @@ public class DBMigrationEngineTest
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, true);
 
-        logRecorder.reset();
         String message = "Inconsistent database: Last creation/migration didn't succeed. Last log entry: " + logEntry;
         try
         {
@@ -712,7 +674,8 @@ public class DBMigrationEngineTest
         {
             assertEquals(message, e.getMessage());
         }
-        assertEquals("ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, getLogContent());
+        assertEquals("ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, logRecorder
+                .getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -747,7 +710,6 @@ public class DBMigrationEngineTest
             });
         DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, true);
 
-        logRecorder.reset();
         String message = "Inconsistent database: Last creation/migration didn't succeed. Last log entry: " + logEntry;
         try
         {
@@ -757,7 +719,8 @@ public class DBMigrationEngineTest
         {
             assertEquals(message, e.getMessage());
         }
-        assertEquals("ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, getLogContent());
+        assertEquals("ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - " + message, logRecorder
+                .getLogContent());
 
         context.assertIsSatisfied();
     }
@@ -801,7 +764,6 @@ public class DBMigrationEngineTest
             });
         final DBMigrationEngine migrationEngine = new DBMigrationEngine(daoFactory, scriptProvider, false);
 
-        logRecorder.reset();
         try
         {
             migrationEngine.migrateTo(toVersion);
@@ -813,16 +775,11 @@ public class DBMigrationEngineTest
         assertEquals("INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                 + "Trying to migrate database 'my database' from version 001 to 002." + OSUtilities.LINE_SEPARATOR
                 + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Executing script 'm-1-2' failed."
-                + OSUtilities.LINE_SEPARATOR + getStackTrace(exception), getLogContent());
+                + OSUtilities.LINE_SEPARATOR + getStackTrace(exception), logRecorder.getLogContent());
 
         context.assertIsSatisfied();
     }
 
-    private String getLogContent()
-    {
-        return new String(logRecorder.toByteArray()).trim();
-    }
-
     private String getStackTrace(final Throwable throwable)
     {
         StringWriter stringWriter = new StringWriter();
-- 
GitLab