Thursday, April 11, 2013

Check that your code is thread-safe with JUnit and ContiPerf

This short article shows how to check that you write thread-safe code by transforming a JUnit test into a concurrent one with ContiPerf.

Suppose that you have to test a date formatter component that uses the famous SimpleDateFormat class, as illustrated below:
package org.javabenchmark;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Helper dedicated to format date in a standard way.
 */
public class NonThreadSafeDateFormatHelper {

    /**
     * the date format for standard representation.
     */
    private SimpleDateFormat standardDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * formats the given date using the standard date format: yyyy-MM-dd.
     *
     * @param date the date to format
     * @return a literal representation of the given date.
     */
    public String toStandardString(Date date) {
        return standardDateFormat.format(date);
    }
}

Then, you could write a junit test like this:
package org.javabenchmark;

import org.junit.Test;

import java.util.Calendar;

import static org.fest.assertions.api.Assertions.*;

public class NonThreadSafeDateFormatHelperTest {

    @Test
    public void shouldFormatRandomDate() {

        // random date
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, (int) (1000 + Math.random() * 1000));
        calendar.set(Calendar.DAY_OF_YEAR, (int) (Math.random() * 365));

        // test
        NonThreadSafeDateFormatHelper dateFormatHelperToTest = new NonThreadSafeDateFormatHelper();
        String randomDateString = dateFormatHelperToTest.toStandardString(calendar.getTime());

        // general controls
        assertThat(randomDateString).isNotNull();
        assertThat(randomDateString).hasSize(10);
        // year control
        String literalYear = String.valueOf(calendar.get(Calendar.YEAR));
        assertThat(literalYear).isEqualTo(randomDateString.substring(0, 4));
        // month control
        String literalMonth = String.valueOf(calendar.get(Calendar.MONTH) + 1);
        if (literalMonth.length() == 1) {
            literalMonth = "0" + literalMonth;
        }
        assertThat(literalMonth).isEqualTo(randomDateString.substring(5, 7));
        // day control
        String literalDayh = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
        if (literalDayh.length() == 1) {
            literalDayh = "0" + literalDayh;
        }
        assertThat(literalDayh).isEqualTo(randomDateString.substring(8));
    }
}

Next, running the test will produce the following output:
Testsuite: org.javabenchmark.NonThreadSafeDateFormatHelperTest
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.173 sec

The test passes, and you could think that everything is fine, but what will happen if the DateFormatHelper class is used in a concurrent way, for instance from a JSF page to display the current date ?

To check that your code can handle concurrency, you can modify the previous JUnit test like this:
package org.javabenchmark;

import org.junit.Rule;
import org.junit.Test;

import java.text.ParseException;
import java.util.Calendar;
import org.databene.contiperf.PerfTest;
import org.databene.contiperf.junit.ContiPerfRule;

import static org.fest.assertions.api.Assertions.assertThat;

public class NonThreadSafeDateFormatHelperPerfTest {

    @Rule
    public ContiPerfRule i = new ContiPerfRule();
    /**
     * the date format helper to test.
     */
    private NonThreadSafeDateFormatHelper dateFormatHelperToTest = new NonThreadSafeDateFormatHelper();

    @Test
    @PerfTest(invocations = 1000, threads = 2)
    public void shouldFormatRandomDatesConcurrently() throws ParseException {

        // random date
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, (int) (1000 + Math.random() * 1013));
        calendar.set(Calendar.DAY_OF_YEAR, (int) (Math.random() * 365));
        
        // test
        String randomDateString = dateFormatHelperToTest.toStandardString(calendar.getTime());

        // general controls
        assertThat(randomDateString).isNotNull();
        assertThat(randomDateString).hasSize(10);
        // year control
        String literalYear = String.valueOf(calendar.get(Calendar.YEAR));
        assertThat(literalYear).isEqualTo(randomDateString.substring(0, 4));
        // month control
        String literalMonth = String.valueOf(calendar.get(Calendar.MONTH) + 1);
        if (literalMonth.length() == 1) {
            literalMonth = "0" + literalMonth;
        }
        assertThat(literalMonth).isEqualTo(randomDateString.substring(5, 7));
        // day control
        String literalDayh = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
        if (literalDayh.length() == 1) {
            literalDayh = "0" + literalDayh;
        }
        assertThat(literalDayh).isEqualTo(randomDateString.substring(8));
    }
}

This is the same test, except that the test method is invoked 1000 times by 2 threads, producing this output:
Testsuite: org.javabenchmark.NonThreadSafeDateFormatHelperPerfTest
org.javabenchmark.NonThreadSafeDateFormatHelperPerfTest.shouldFormatRandomDatesConcurrently
samples: 999
max:     19
average: 0.04804804804804805
median:  0
Tests run: 1, Failures: 0, Errors: 1, Time elapsed: 0.377 sec
So, there is now an error, indicating that the DateFormatHelper component is not thread-safe: do not let it go into production :)

Summary

Good unit tests are not sufficient when you are writing components that will evolve in multi-threaded environment, like web applications. You can easily check if your code is vulnerable to race condition with contiperf.

9 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. This is a cool idea, and I like the syntax a lot.

    The problem from my point of view: If you are smart enough to use a "is my code threadsafe test", you likely are smart enough to already write threadsafe code :)

    Plus the fact that some race conditions may only happen 1 time in 10 years.. you would need to run this test for a really long time to weed out all of your race conditions ;) Much better to just read up on JCIP and try to make thread safe code...

    ReplyDelete
    Replies
    1. Hi Brian, thanks for the comment. I agree with you but sometimes you test code that relies on customer framework, or third-party libraries. With this approach, you can spot concurrency issues that are hidden in the dependencies that your code is using without exploring source or javadoc to be sure that all component are thread-safe.

      Delete
  3. I agree with Brian,
    the title of this post could be "Check that your code is probably thread-safe with JUnit and ContiPerf".
    Regards.

    ReplyDelete
  4. Thanks! Does it work with TestNG?

    ReplyDelete
    Replies
    1. As far as i know, ContiPerf is designed for JUnit only.

      Delete
  5. Hi,

    I developed a tool called http://vmlens.com which can detect such bugs. It searches for memory fields which are accessed from different threads without synchronization. It will detect such bugs when at least two threads accessing a field without synchronization.

    Regards
    Thomas

    ReplyDelete
  6. Bạn muốn tìm mua thực phẩm chức năng nhưng bạn không biết Thực phẩm chức năng Herbalife có thật sự tốt không?.Hãy đến với megavita chúng tôi sẽ giúp bạn hiểu rỏ hơn về thực phẩm chức năng herbalife có tốt không.Người bạn quá gầy,bạn muốn tăng cân ư....hãy sử dụng Thực phẩm chức năng Herbalife tăng cân.Bạn quá mập,bạn muốn giảm cân,đừng lo,hãy Giảm cân bằng thực phẩm chức năng Herbalife bạn sẽ thấy hiệu quả ngay.Xem thêm :
    thực phẩm chức năng herbalife giảm cân,
    Giảm cân bằng thực phẩm chức năng Herbalife.
    Thực phẩm chức năng Herbalife tăng cân
    thuc pham chuc nang Herbalife giam can
    thuc pham chuc nang Herbalife

    ReplyDelete
  7. cách làm mặt nạ tự nhiên tại nhà Hiện nay vấn đề bữa ăn giấc ngủ đang rất được quan tâm chăm sóc kĩ lưỡng. Tình trạng mất ngủ là căn bệnh phổ biến hiện nay.
    đi biển nên dùng kem chống nắng loại nàoTáo có thành phần dinh dưỡng rất phong phú, đặc biệt là các loại vi chất,sinh tố và axit hoa quả.
    phuong phap lam trang da don gian nhatTrong đậu xanh có chứa nhiều vitamin B6, Loại vitamin này sản xuất ra melatonin một loại hormone có liên quan mật thiết đến giấc ngủ.
    bí quyết giảm cân cho người khó giảm cânChuối có chứa nhiều chất vitamin B6 giúp tế bào thần kinh khoẻ mạnh. Nên ăn 2-3 quả chuối mỗi ngày sẽ tốt cho sức khoẻ và giúp bạn an thần dể ngủ.
    các món ăn chữa bệnh mất ngủCơ thể bị rối loạn đường huyết cũng thường xuyên buồn ngủ, mãnh liệt nhất là sau bữa ăn.
    cách trị sẹo thâm bằng dầu dừaCách làm rất đơn giản, mỗi tối trước khi đi ngủ bạn rữa mặt thật sạch, dùng dầu dừa thoa lên những vùng da bị sẹo do mụn.
    Cách làm tinh dầu bưởi trị rụng tócCái răng cái tóc là gốc con người, nhưng do một số nguyên nhân khiến tóc bạn bị rụng. Có thể do cơ địa của bạn không tốt, hoặc là do chế độ dinh dưỡng bạn không phù hợp, nhưng do nguyên nhân gì đi nữa thì điều này chẳng tốt chút nào.
    Cách trị nám da từ thiên nhiênCó nhiều nguyên nhân khiến da chúng ta bị nám, có thể do bẩm sinh và cũng có thể do những tác động bên ngoài từ cuộc sống.
    Cách trị sẹo lõm bằng phương pháp tự nhiênNha đam là loại thảo mộc không chỉ tốt cho da, mà còn có khả năng làm lành da nên rất hữu hiệu với những vùng da bị sẹo lõm do mụn, viêm.
    Cách làm trắng da tự nhiên cấp tốcSử dung bơ để làm đẹp rất hiệu quả, bơ giúp tái tạo và giữ ẩm, giúp da không bị khô, có tác dụng làm sáng và tăng khả năng đàn hồi của da
    các loại thực phẩm tốt cho mắtTheo các chuyên gia, trong cà chua có chứa nhiều carotenoid và lycopene giúp ngăn ngừa những tác nhân làm ảnh hưởng đến võng mạc và khu vực khác của mắt.

    ReplyDelete