Skip to content

Commit d140a9a

Browse files
authored
Merge pull request #1148 from cucumber/ishmeister-master
[Spring] Support multithreaded execution of scenarios
2 parents aa4c9a0 + 372619e commit d140a9a

File tree

7 files changed

+113
-5
lines changed

7 files changed

+113
-5
lines changed

History.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## [2.0.0-SNAPSHOT](https://github.com/cucumber/cucumber-jvm/compare/v1.2.5...master) (In Git)
22

3+
* [Spring] Support multithreaded execution of scenarios ([#1106](https://github.com/cucumber/cucumber-jvm/issues/1106), [#1107](https://github.com/cucumber/cucumber-jvm/issues/1107), [#1148](https://github.com/cucumber/cucumber-jvm/issues/1148) Ismail Bhana, M.P. Korstanje)
34
* [Java8, Kotlin Java8] Support java 8 method references ([#1140](https://github.com/cucumber/cucumber-jvm/pull/1140) M.P. Korstanje)
45
* [Core] Show explicit error message when field name missed in table header ([#1014](https://github.com/cucumber/cucumber-jvm/pull/1014) Mykola Gurov)
56
* [Examples] Properly quit selenium in webbit examples ([#1146](https://github.com/cucumber/cucumber-jvm/pull/1146) Alberto Scotto)

spring/src/main/java/cucumber/runtime/java/spring/GlueCodeContext.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,24 @@
44
import java.util.Map;
55

66
class GlueCodeContext {
7-
public static final GlueCodeContext INSTANCE = new GlueCodeContext();
7+
8+
private static final ThreadLocal<GlueCodeContext> localContext = new ThreadLocal<GlueCodeContext>() {
9+
protected GlueCodeContext initialValue() {
10+
return new GlueCodeContext();
11+
}
12+
};
13+
814
private final Map<String, Object> objects = new HashMap<String, Object>();
915
private final Map<String, Runnable> callbacks = new HashMap<String, Runnable>();
1016
private int counter;
1117

1218
private GlueCodeContext() {
1319
}
1420

21+
public static GlueCodeContext getInstance() {
22+
return localContext.get();
23+
}
24+
1525
public void start() {
1626
cleanUp();
1727
counter++;

spring/src/main/java/cucumber/runtime/java/spring/GlueCodeScope.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
class GlueCodeScope implements Scope {
77
public static final String NAME = "cucumber-glue";
88

9-
private final GlueCodeContext context = GlueCodeContext.INSTANCE;
10-
119
@Override
1210
public Object get(String name, ObjectFactory<?> objectFactory) {
11+
GlueCodeContext context = GlueCodeContext.getInstance();
1312
Object obj = context.get(name);
1413
if (obj == null) {
1514
obj = objectFactory.getObject();
@@ -21,11 +20,13 @@ public Object get(String name, ObjectFactory<?> objectFactory) {
2120

2221
@Override
2322
public Object remove(String name) {
23+
GlueCodeContext context = GlueCodeContext.getInstance();
2424
return context.remove(name);
2525
}
2626

2727
@Override
2828
public void registerDestructionCallback(String name, Runnable callback) {
29+
GlueCodeContext context = GlueCodeContext.getInstance();
2930
context.registerDestructionCallback(name, callback);
3031
}
3132

@@ -36,6 +37,7 @@ public Object resolveContextualObject(String key) {
3637

3738
@Override
3839
public String getConversationId() {
40+
GlueCodeContext context = GlueCodeContext.getInstance();
3941
return context.getId();
4042
}
4143
}

spring/src/main/java/cucumber/runtime/java/spring/SpringFactory.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public void start() {
112112
registerStepClassBeanDefinition(beanFactory, stepClass);
113113
}
114114
}
115-
GlueCodeContext.INSTANCE.start();
115+
GlueCodeContext.getInstance().start();
116116
}
117117

118118
@SuppressWarnings("resource")
@@ -161,7 +161,7 @@ private void registerStepClassBeanDefinition(ConfigurableListableBeanFactory bea
161161
@Override
162162
public void stop() {
163163
notifyContextManagerAboutTestClassFinished();
164-
GlueCodeContext.INSTANCE.stop();
164+
GlueCodeContext.getInstance().stop();
165165
}
166166

167167
private void notifyContextManagerAboutTestClassFinished() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cucumber.runtime.java.spring.threading;
2+
3+
import static java.util.concurrent.Executors.newFixedThreadPool;
4+
import static org.junit.Assert.assertEquals;
5+
6+
import cucumber.api.cli.Main;
7+
import org.junit.Test;
8+
9+
import java.util.concurrent.Callable;
10+
import java.util.concurrent.ExecutionException;
11+
import java.util.concurrent.ExecutorService;
12+
import java.util.concurrent.Future;
13+
14+
15+
public class RunParallelCukesTest {
16+
17+
private final Callable<Byte> runCuke = new Callable<Byte>() {
18+
@Override
19+
public Byte call() throws Exception {
20+
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
21+
String[] args = {
22+
"--glue", "cucumber.runtime.java.spring.threading",
23+
"classpath:cucumber/runtime/java/spring/threadingCukes.feature",
24+
"--strict"
25+
};
26+
return Main.run(args, classLoader);
27+
}
28+
};
29+
30+
@Test
31+
public void test() throws InterruptedException, ExecutionException {
32+
ExecutorService executorService = newFixedThreadPool(2);
33+
Future<Byte> result1 = executorService.submit(runCuke);
34+
Future<Byte> result2 = executorService.submit(runCuke);
35+
assertEquals(result1.get().byteValue(), 0x0);
36+
assertEquals(result2.get().byteValue(), 0x0);
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package cucumber.runtime.java.spring.threading;
2+
3+
import static java.lang.Thread.currentThread;
4+
import static org.junit.Assert.assertEquals;
5+
import static org.junit.Assert.assertNotSame;
6+
import static org.junit.Assert.assertSame;
7+
8+
import cucumber.api.java.en.Given;
9+
import cucumber.api.java.en.Then;
10+
import cucumber.api.java.en.When;
11+
import org.springframework.test.context.ContextConfiguration;
12+
import org.springframework.test.context.web.WebAppConfiguration;
13+
14+
import java.util.Map;
15+
import java.util.concurrent.ConcurrentHashMap;
16+
import java.util.concurrent.CountDownLatch;
17+
import java.util.concurrent.TimeUnit;
18+
19+
@WebAppConfiguration
20+
@ContextConfiguration("classpath:cucumber.xml")
21+
public class ThreadingStepDefs {
22+
23+
private static final ConcurrentHashMap<Thread, ThreadingStepDefs> map = new ConcurrentHashMap<Thread, ThreadingStepDefs>();
24+
25+
private static final CountDownLatch latch = new CountDownLatch(2);
26+
27+
@Given("^I am a step definition$")
28+
public void iAmAStepDefinition() throws Throwable {
29+
map.put(currentThread(), this);
30+
}
31+
32+
@When("^when executed in parallel$")
33+
public void whenExecutedInParallel() throws Throwable {
34+
latch.await(10, TimeUnit.SECONDS);
35+
}
36+
37+
@Then("^I should not be shared between threads$")
38+
public void iShouldNotBeSharedBetweenThreads() throws Throwable {
39+
for (Map.Entry<Thread, ThreadingStepDefs> entries : map.entrySet()) {
40+
if (entries.getKey().equals(currentThread())) {
41+
assertSame(entries.getValue(), this);
42+
} else {
43+
assertNotSame(entries.getValue(), this);
44+
}
45+
}
46+
assertEquals(2, map.size());
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Feature: Spring Threading Cukes
2+
In order to have a completely clean system for each scenario
3+
As a purity activist
4+
I want that beans have both scenario and thread scope.
5+
6+
Scenario: A parallel execution
7+
Given I am a step definition
8+
When when executed in parallel
9+
Then I should not be shared between threads

0 commit comments

Comments
 (0)