-
Notifications
You must be signed in to change notification settings - Fork 41.3k
Description
I've created a small project running atop of Spring Boot 2.2.0.BUILD-SNAPSHOT
version and using JUnit 5
as the test runner. There, running a single-test available, repeated times, using @Repeated
annotation, and in parallel, I've been facing the following stack trace, when an assertion fails because it took more than 100ms to respond back.
Of course, the point is not about the sample code, it's about how SpringBootMockMvcBuilderCustomizer
class handles to writing those lines down.
Would make sense change List collection for a thread-safe approach?
SpringBootMockMvcBuilderCustomizer.java
private static class SystemLinesWriter implements SpringBootMockMvcBuilderCustomizer.LinesWriter {
private final MockMvcPrint print;
SystemLinesWriter(MockMvcPrint print) {
this.print = print;
}
public void write(List<String> lines) {
PrintStream printStream = this.getPrintStream();
Iterator var3 = lines.iterator();
while(var3.hasNext()) {
String line = (String)var3.next();
printStream.println(line);
}
}
private PrintStream getPrintStream() {
return this.print == MockMvcPrint.SYSTEM_ERR ? System.err : System.out;
}
}
Sample test code
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
public class DummyApplicationTests {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@DisplayName("Should return transaction statistics respecting a response-time threshold even when a concurrent random traffic incomes")
@RepeatedTest(10000)
public void dummy() throws Exception {
TransactionDTO transactionDTO = new TransactionDTO(
(double) new Random().nextInt(100000),
OffsetDateTime.now().toEpochSecond() + new Random().nextInt(120));
this.mockMvc.perform(post("/transactions")
.content(objectMapper.writeValueAsString(transactionDTO))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isCreated());
assertTimeoutPreemptively(ofMillis(100), () -> {
this.mockMvc.perform(get("/statistics")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
});
}
}
junit-application.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
Stack trace
org.opentest4j.AssertionFailedError: execution timed out after 100 ms
at org.junit.jupiter.api.AssertTimeout.assertTimeoutPreemptively(AssertTimeout.java:144)
at org.junit.jupiter.api.AssertTimeout.assertTimeoutPreemptively(AssertTimeout.java:115)
at org.junit.jupiter.api.AssertTimeout.assertTimeoutPreemptively(AssertTimeout.java:97)
at org.junit.jupiter.api.AssertTimeout.assertTimeoutPreemptively(AssertTimeout.java:93)
at org.junit.jupiter.api.Assertions.assertTimeoutPreemptively(Assertions.java:3236)
at io.github.gandrade.n26.N26ApplicationTests.contextLoads(N26ApplicationTests.java:49)
at sun.reflect.GeneratedMethodAccessor35.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
at org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService$ExclusiveTask.compute(ForkJoinPoolHierarchicalTestExecutorService.java:170)
at java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:189)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Suppressed: java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer$SystemLinesWriter.write(SpringBootMockMvcBuilderCustomizer.java:296)
at org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer$DeferredLinesWriter.writeDeferredResult(SpringBootMockMvcBuilderCustomizer.java:246)
at org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener.afterTestMethod(MockMvcPrintOnlyOnFailureTestExecutionListener.java:44)
at org.springframework.test.context.TestContextManager.afterTestMethod(TestContextManager.java:443)
at org.springframework.test.context.junit.jupiter.SpringExtension.afterEach(SpringExtension.java:139)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAfterEachCallbacks$11(TestMethodTestDescriptor.java:218)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$13(TestMethodTestDescriptor.java:230)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAllAfterMethodsOrCallbacks(TestMethodTestDescriptor.java:228)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAfterEachCallbacks(TestMethodTestDescriptor.java:217)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:120)
... 11 more
Workaround
To suppress this behavior, I've added the following configuration for @AutoConfigureMockMvc
annotation:
@AutoConfigureMockMvc(print = MockMvcPrint.NONE)
, which AFAIK, doesn't make the code traverse the List.
After this change, placebo or not, I got the impression of a lower number of "false-positives" timeout assertions.