/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.IntStream;
import org.junit.Test;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toCollection;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* Integration tests that verify proper concurrency support between a
* {@link TestContextManager} and the {@link TestContext} it manages
* when a registered {@link TestExecutionListener} updates the mutable
* state and attributes of the context from concurrently executing threads.
*
* <p>In other words, these tests verify that mutated state and attributes
* are only be visible to the thread in which the mutation occurred.
*
* @author Sam Brannen
* @since 5.0
* @see org.springframework.test.context.junit4.concurrency.SpringJUnit4ConcurrencyTests
*/
public class TestContextConcurrencyTests {
private static Set<String> expectedMethods = stream(TestCase.class.getDeclaredMethods()).map(
Method::getName).collect(toCollection(TreeSet::new));
private static final Set<String> actualMethods = Collections.synchronizedSet(new TreeSet<>());
private static final TestCase testInstance = new TestCase();
@Test
public void invokeTestContextManagerFromConcurrentThreads() {
TestContextManager tcm = new TestContextManager(TestCase.class);
// Run the actual test several times in order to increase the chance of threads
// stepping on each others' toes by overwriting the same mutable state in the
// TestContext.
IntStream.range(1, 20).forEach(i -> {
actualMethods.clear();
// Execute TestExecutionListener in parallel, thereby simulating parallel
// test method execution.
stream(TestCase.class.getDeclaredMethods()).parallel().forEach(testMethod -> {
try {
tcm.beforeTestClass();
tcm.beforeTestMethod(testInstance, testMethod);
// no need to invoke the actual test method
tcm.afterTestMethod(testInstance, testMethod, null);
tcm.afterTestClass();
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
});
assertThat(actualMethods, equalTo(expectedMethods));
});
assertEquals(0, tcm.getTestContext().attributeNames().length);
}
@TestExecutionListeners(TrackingListener.class)
@SuppressWarnings("unused")
private static class TestCase {
void test_001() {
}
void test_002() {
}
void test_003() {
}
void test_004() {
}
void test_005() {
}
void test_006() {
}
void test_007() {
}
void test_008() {
}
void test_009() {
}
void test_010() {
}
}
private static class TrackingListener implements TestExecutionListener {
private ThreadLocal<String> methodName = new ThreadLocal<>();
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
String name = testContext.getTestMethod().getName();
actualMethods.add(name);
testContext.setAttribute("method", name);
this.methodName.set(name);
}
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
assertEquals(this.methodName.get(), testContext.getAttribute("method"));
}
}
}