Skip to content

Latest commit

 

History

History
157 lines (119 loc) · 4.73 KB

File metadata and controls

157 lines (119 loc) · 4.73 KB

Usage Examples

Fray is a tool that helps you to test multithreaded code. It is especially useful when you want to test the behavior of your code under different thread interleavings. Here is an example of how to add Fray to a Gradle project. You may find the complete source code here

Gradle Configuration

Add the following plugin to your build.gradle file:

plugins {
    id("org.pastalab.fray.gradle") version "0.2.5"
}

Class You Want to Test

Fray does not require any special annotations or modifications to your code. You can write your test code as usual. Here is an example of a simple class that you want to test:

import java.util.concurrent.atomic.AtomicInteger;

public class BankAccount {
    public AtomicInteger balance = new AtomicInteger(1000);
    public void withdraw(int amount) {
        // Check if there is enough balance
        if (balance.get() >= amount) {
            // Deduct the amount
            balance.set(balance.get() - amount);
        }
    }
}

Test Code

Original Test Code

Here is an example of a test code that tests the BankAccount class without Fray:

import org.junit.jupiter.api.Test;
public class BankAccountTest {
    public void myBankAccountTest() throws InterruptedException {
        BankAccount account = new BankAccount();
        Thread t1 = new Thread(() -> {
            account.withdraw(500);
            assert(account.balance.get() > 0);
        });
        Thread t2 = new Thread(() -> {
            account.withdraw(700);
            assert(account.balance.get() > 0);
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }

    @Test
    public void runTestUsingJunit() throws InterruptedException {
        myBankAccountTest();
    }
}

Test Code with Fray

Here is an example of a test code that tests the BankAccount class with Fray:

...
import org.junit.jupiter.api.extension.ExtendWith;
import org.pastalab.fray.junit.junit5.FrayTestExtension;
import org.pastalab.fray.junit.junit5.annotations.ConcurrencyTest;

@ExtendWith(FrayTestExtension.class)
public class BankAccountTest {
    public void myBankAccountTest() throws InterruptedException {
        ...
    }

    @ConcurrencyTest(
            iterations = 1000
    )
    public void runTestUsingFray() throws InterruptedException {
        myBankAccountTest();
    }
}
  • First you need to add the @ExtendWith(FrayTestExtension.class) annotation to the test class so that Fray can run the test.
  • Then you need to add the @ConcurrencyTest annotation to the test method. The iterations parameter specifies how many times the test method should be executed.
    • You may also specify scheduling algorithms and other parameters in the @ConcurrencyTest annotation. For more information, see the ConcurrencyTest.kt

Run the Test

Run test from command line

You can run the test from the command line using the following command:

./gradlew frayTest

Fray will launch all tests annotated with @ConcurrencyTest and run them multiple times.

Run test from IDE

If you are using an IDE, you can run the test as you would run any other JUnit test.

Reproduce a Failure

If a test fails, Fray will automatically generate a test case that reproduces the failure. Fray prints the path of the recording in the standard output.

Bug found in iteration test runTestUsingFray() repetition 0 of 1000, you may find detailed report and replay files in PATH_TO_FRAY_REPORT

In the report folder, Fray logs the detailed information of the test failure in fray.log file.

2025-02-17 13:43:40 [INFO]: Error found at iter: 1, step: 19, Elapsed time: 22ms
2025-02-17 13:43:40 [INFO]: Error: java.lang.AssertionError
Thread: Thread[#20037,Thread-20003,5,main]
java.lang.AssertionError
	at BankAccountTest.lambda$myBankAccountTest$0(BankAccountTest.java:14)
	at java.base/java.lang.Thread.run(Thread.java:1583)

2025-02-17 13:43:40 [INFO]: The recording is saved to PATH_TO_FRAY_REPORT/recording

Next, you may replay the failure using Fray by providing the recording path:

@ConcurrencyTest(
        replay = "PATH_TO_FRAY_REPORT/recording"
)

NixOS

Fray downloads Corretto JDK 23 and runs ConcurrencyTest with it by default. However, NixOS cannot run dynamically linked executables. To run Fray on NixOS, you can provide environment variable JDK23_HOME and Fray will use provided JDK 23 instead of downloading it.

packages = with pkgs; [
  ...
  jdk23
];
shellHook = ''
  export JDK23_HOME="${pkgs.jdk23.home}"
''