TestNG allows running tests in parallel, using the parallel=methods parameter. This causes tests to run in multiple threads using the same class instance.

However, the code in https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTestNGSpringContextTests.java#L148-L162 is not thread-safe.

As an example, consider the following minimal test case:

@ContextConfiguration(classes = {SampleJavaClassTest.Configuration.class})
public class SampleJavaClassTest extends AbstractTestNGSpringContextTests {
    public static class Configuration {

    }

    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message1")
    public void message1() {
        throw new RuntimeException("Message1");
    }
    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message2")
    public void message2() {
        throw new RuntimeException("Message2");
    }
    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message3")
    public void message3() {
        throw new RuntimeException("Message3");
    }
    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message4")
    public void message4() {
        throw new RuntimeException("Message4");
    }
    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message5")
    public void message5() {
        throw new RuntimeException("Message5");
    }
    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message6")
    public void message6() {
        throw new RuntimeException("Message6");
    }
    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message7")
    public void message7() {
        throw new RuntimeException("Message7");
    }
    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message8")
    public void message8() {
        throw new RuntimeException("Message8");
    }
    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message9")
    public void message9() {
        throw new RuntimeException("Message9");
    }
    @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Message10")
    public void message10() {
        throw new RuntimeException("Message10");
    }
}

Running this test with the following TestNG configuration:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
<suite name="TestSuite" verbose="2" thread-count="5">
    <test name="DemoTest" annotations="JDK" parallel="methods" verbose="2" preserve-order="true" thread-count="5">
        <packages>
            <package name="com.example.*"/>
        </packages>
    </test>
</suite>

(note thread-count=5 and parallel=methods)

Causes the tests to fail randomly:

org.testng.TestException: The exception was thrown with the wrong message: expected "Message2" but got "Message10"

The stacktrace from such failures also shows the stacktrace originating from the wrong test. The message2 test failure stack trace looks like this:

Caused by: java.lang.RuntimeException: Message10
    at com.example.SampleJavaClassTest.message10(SampleJavaClassTest.java:54)
...

In the code above, this.testException is being clobbered by a different thread, causing the issues described.

Comment From: sharky5102

That was quick! LGTM.

Comment From: github-actions[bot]

Fixed via 5cd2cb38e1b1ed3e1951499d9b5ad63372dd7e3e

Comment From: sbrannen

This has been fixed in 6.2.x and main.

It would be great it you could try out 6.2.12 snapshots to ensure that the fix addresses the issues in your project.

Cheers,

Sam