Tuesday, 30 November 2010

a Custom Log4j appender to create a logfile for each User

you don't want to have a cumulative log file for all usages of your client applications (e.g. Swing), but create a log file for each user? the answer is extend the RollingFileAppender and inject your own filename handling to it.

here is how you can do it:




public class UserLog4jFileAppender extends RollingFileAppender {
    /**
     * A default file path if no value is set in the log4j.xml.
     */
    private static final String defaultFilePath = ".";

    /**
     * A default date pattern if no value is set in the log4j.xml.
     */
    private static final String defaultDatePattern = "yyMMdd";

    /**
     * This is the absolute path were the logger-structure should be created.
     */
    protected String filePath = null;

    /**
     * This is the filename of the log-file.
     */
    protected String file = null;

    /**
     * This is a date pattern used to create a directory for each day.
     
     @see SimpleDateFormat
     */
    protected String datePattern = null;

    /**
     * The default maximum file size is 10MB.
     */
    protected long maxFileSize;

    /**
     * There is one backup file by default.
     */
    protected int maxBackupIndex;

    /**
     * The default constructor does nothing.
     */
    public UserLog4jFileAppender() {
        maxFileSize = 10485760L;
        maxBackupIndex = 1;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void activateOptions() {
        setupLogFile();
        super.activateOptions();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setFile(final String file) {
        this.file = file;
    }

    /**
     * Gets the date pattern.
     
     @return the date pattern.
     */
    public String getDatePattern() {
        return datePattern;
    }

    /**
     * Sets the date pattern.
     
     @param datePattern
     * the date pattern to set.
     */
    public void setDatePattern(final String datePattern) {
        this.datePattern = datePattern;
    }

    /**
     * Gets the file path.
     
     @return the file path.
     */
    public String getFilePath() {
        return filePath;
    }

    /**
     * Sets the file path.
     
     @param filePath
     * the file path to set.
     */
    public void setFilePath(final String filePath) {
        this.filePath = filePath;
    }

    /**
     * This method sets up the logfile structure. This method creates the needed
     * directories and builds the log-filename.
     */
    private void setupLogFile() {
        if (file != null) {
            if (filePath == null) {
                filePath = defaultFilePath;
            }

            if (datePattern == null) {
                datePattern = defaultDatePattern;
            }

            // build logfile-path
            final String userName = System.getProperty("user.name");
            final String fileName = file.replace("@username@", userName);

            // create file if not exists
            final String parentName = new File(fileName).getParent();
            if (parentName != null) {
                final File parentDir = new File(parentName);
                if (!parentDir.exists()) {
                    parentDir.mkdirs();
                }
            }

            // find first not existing logfile
            final File f = new File(fileName);

            super.setFile(f.getAbsoluteFile().toString());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void reset() {
        filePath = null;
        file = null;
        datePattern = null;
        super.reset();
    }
}




all we need now is to define this appender in our log4j.xml file:




    <appender name="file"

        class="com.xxx.UserLog4jFileAppender">

        <param name="File"

            value="logs/myapp-gui-@username@.log" />

        <param name="MaxFileSize" value="100MB" />

        <param name="MaxBackupIndex" value="10" />

        <param name="Append" value="true" />

        <layout class="org.apache.log4j.PatternLayout">

            <param name="ConversionPattern"

                value="%d{yyMMdd HH\:mm\:ss.SSS} [%t] %-5p %c %x - %m%n" />

        </layout>

    </appender>


Monday, 29 November 2010

Parallel JUnit tests using a custom Concurrent Annotation



concurrent test runs in JUnit 4 are still in experimental phase, a very good alternative to running parallel tests is migrating to TestNG, although if you don't like it or have no possibility to simply migrate to it, you can still use a custom runner by extending the BlockJUnit4ClassRunner class.

first we parametrize the concurrency using an Annotation:


Concurrent.java



@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Concurrent {
    int threadPoolSize() default 3;
    int invocationCount() default 10;
    long timeOut() default 10000;
}



and here comes the actual runner which controls our parallel runs:


ConcurrentRunner.java



public class ConcurrentRunner extends BlockJUnit4ClassRunner {

    public ConcurrentRunner(Class<?> klassthrows InitializationError {
        super(klass);
    }

    @Override
    public void run(final RunNotifier notifier) {

        // run BeforeClass tests
        final List<FrameworkMethod> beforeClassMethods = getTestClass().getAnnotatedMethods(BeforeClass.class);
        for (final FrameworkMethod frameworkMethod : beforeClassMethods) {
            notifier.fireTestStarted(getDescription());
            try {
                methodBlock(frameworkMethod).evaluate();
            }
            catch (Throwable e) {
                Exceptions.throwException(e);
            }
        }

        // run non-concurrent tests
        final List<FrameworkMethod> testMethods = getTestClass().getAnnotatedMethods(Test.class);
        for (final FrameworkMethod frameworkMethod : testMethods) {
            if (frameworkMethod.getAnnotation(Concurrent.class== null) {
                notifier.fireTestRunStarted(getDescription());
                runChild(frameworkMethod, notifier);
            }
        }

        // run concurrent tests, Before and After tests will run automatically before and after each
        // test
        final List<FrameworkMethod> concurrentMethods = getTestClass().getAnnotatedMethods(Concurrent.class);
        for (final FrameworkMethod frameworkMethod : concurrentMethods) {
            final Concurrent concurrent = frameworkMethod.getAnnotation(Concurrent.class);
            if (concurrent != null) {
                final int invocationCount = concurrent.invocationCount();
                final int threadPoolSize = concurrent.threadPoolSize();
                final long timeout = concurrent.timeOut();

                final ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);

                final List<Callable<Object>> calls = new ArrayList<Callable<Object>>();
                for (int i = 0; i < invocationCount; i++)
                    calls.add(new JUnitRunner(frameworkMethod, notifier));

                try {
                    executorService.invokeAll(calls, timeout, TimeUnit.MILLISECONDS);
                }
                catch (InterruptedException e) {
                    Exceptions.throwException(e);
                }

                executorService.shutdown();
            }
        }

        // run AfterClass tests
        final List<FrameworkMethod> afterClassMethods = getTestClass().getAnnotatedMethods(AfterClass.class);
        for (final FrameworkMethod frameworkMethod : afterClassMethods) {
            try {
                methodBlock(frameworkMethod).evaluate();
            }
            catch (Throwable e) {
                Exceptions.throwException(e);
            }
        }
    }

    private class JUnitRunner implements Callable<Object> {

        private final FrameworkMethod method;
        private final RunNotifier notifier;

        private JUnitRunner(final FrameworkMethod method, final RunNotifier notifier) {
            this.method = method;
            this.notifier = notifier;
        }

        @Override
        public Object call() {
            notifier.fireTestRunStarted(getDescription());
            runChild(method, notifier);

            return null;
        }
    }

}



all we need is to test the stuff:


ConcurrentRunnerTest.java



@RunWith(ConcurrentRunner.class)
public class ConcurrentRunnerTest {

    private static Object aGlobalObject = null;
    private static Integer aGlobalInteger = null;

    @BeforeClass
    public static void setUp() throws Exception {
        if (aGlobalObject == null) {
            aGlobalObject = new String("aGlobalObject");
            System.out.println("=========>> Setup test");
        }
    }

    @AfterClass
    public static void coolDown() throws Exception {
        assertNotNull(aGlobalObject);
        assertNotNull(aGlobalInteger);

        assertEquals(aGlobalInteger.intValue()591);

        aGlobalObject = null;
        aGlobalInteger = null;

        System.out.println("=========>> coolDown");
    }

    @BeforeClass
    public static void setUpAgain() throws Exception {
        if (aGlobalInteger == null) {
            aGlobalInteger = new Integer(4177);

            System.out.println("=========>> Setup test 2");
        }
    }

    @Test
    public void testOnce() {
        assertNotNull(aGlobalObject);
        assertNotNull(aGlobalInteger);

        aGlobalInteger = 591;

        System.out.println("===> testOnce");
    }

    @Test
    @Concurrent(threadPoolSize = 4, invocationCount = 10, timeOut = 60000)
    public void testConcurrent() {
        assertNotNull(aGlobalObject);
        assertNotNull(aGlobalInteger);

        System.out.println("===> testConcurrent: " + Thread.currentThread());
    }

    @Test
    public void testOnceAgain() {
        assertNotNull(aGlobalObject);
        assertNotNull(aGlobalInteger);

        System.out.println("===> testOnceAgain");
    }

}




and here is the Output:



=========>> Setup test 2
=========>> Setup test
===> testOnce
===> testOnceAgain
===> testConcurrent: Thread[pool-1-thread-2,5,main]
===> testConcurrent: Thread[pool-1-thread-2,5,main]
===> testConcurrent: Thread[pool-1-thread-1,5,main]
===> testConcurrent: Thread[pool-1-thread-2,5,main]
===> testConcurrent: Thread[pool-1-thread-3,5,main]
===> testConcurrent: Thread[pool-1-thread-3,5,main]
===> testConcurrent: Thread[pool-1-thread-3,5,main]
===> testConcurrent: Thread[pool-1-thread-3,5,main]
===> testConcurrent: Thread[pool-1-thread-2,5,main]
===> testConcurrent: Thread[pool-1-thread-4,5,main]
=========>> coolDown