Skip to content

Commit 2f6066d

Browse files
authored
[Pico] Fixed missing calls to start, stop and dispose handles (#2772)
Corrected the pico-container lifecycle: the start, stop and dispose methods are now called for every test scenarios. The code is the same as before #2725 (so it's safer in terms of lifecycle), except that the pico container is recycled in the `start()` method when it already exists. When a second call to `start()` is done (e.g. for the 2nd scenario), the container component instances are all in disposed state and the container lifecycle is in disposed state. The recycling operation consists in removing all container component instances (i.e. flushing the cache) and resetting the container lifecycle. Co-authored-by: Julien Kronegg <julien [at] kronegg.ch>
1 parent e61f818 commit 2f6066d

File tree

4 files changed

+66
-19
lines changed

4 files changed

+66
-19
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
### Changed
1414
- [TestNG] Update dependency org.testng:testng to v7.8.0
1515

16+
### Fixed
17+
- [Pico] Fixed missing calls to start, stop and dispose handles ([#2772](https://github.com/cucumber/cucumber-jvm/pull/2772) Julien Kronegg)
18+
1619
## [7.12.1] - 2023-06-02
1720
### Fixed
1821
- [Core] Set html report viewport width to device width ([html-formatter/#238](https://github.com/cucumber/html-formatter/pull/238) Tim Yao )

cucumber-picocontainer/src/main/java/io/cucumber/picocontainer/PicoFactory.java

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.picocontainer.MutablePicoContainer;
66
import org.picocontainer.PicoBuilder;
77
import org.picocontainer.behaviors.Cached;
8+
import org.picocontainer.lifecycle.DefaultLifecycleState;
89

910
import java.lang.reflect.Constructor;
1011
import java.lang.reflect.Modifier;
@@ -15,36 +16,45 @@
1516
public final class PicoFactory implements ObjectFactory {
1617

1718
private final Set<Class<?>> classes = new HashSet<>();
18-
private final MutablePicoContainer pico = new PicoBuilder()
19-
.withCaching()
20-
.withLifecycle()
21-
.build();
22-
23-
public PicoFactory() {
24-
this.pico.start();
25-
}
19+
private MutablePicoContainer pico;
2620

2721
private static boolean isInstantiable(Class<?> clazz) {
2822
boolean isNonStaticInnerClass = !Modifier.isStatic(clazz.getModifiers()) && clazz.getEnclosingClass() != null;
2923
return Modifier.isPublic(clazz.getModifiers()) && !Modifier.isAbstract(clazz.getModifiers())
3024
&& !isNonStaticInnerClass;
3125
}
3226

27+
@Override
3328
public void start() {
34-
// do nothing (was already started in constructor)
29+
if (pico == null) {
30+
pico = new PicoBuilder()
31+
.withCaching()
32+
.withLifecycle()
33+
.build();
34+
for (Class<?> clazz : classes) {
35+
pico.addComponent(clazz);
36+
}
37+
} else {
38+
// we already get a pico container which is in "disposed" lifecycle,
39+
// so recycle it by defining a new lifecycle and removing all
40+
// instances
41+
pico.setLifecycleState(new DefaultLifecycleState());
42+
pico.getComponentAdapters()
43+
.forEach(cached -> ((Cached<?>) cached).flush());
44+
}
45+
pico.start();
3546
}
3647

3748
@Override
3849
public void stop() {
39-
pico.getComponentAdapters()
40-
.forEach(cached -> ((Cached<?>) cached).flush());
50+
pico.stop();
51+
pico.dispose();
4152
}
4253

4354
@Override
4455
public boolean addClass(Class<?> clazz) {
4556
if (isInstantiable(clazz) && classes.add(clazz)) {
4657
addConstructorDependencies(clazz);
47-
pico.addComponent(clazz);
4858
}
4959
return true;
5060
}

cucumber-picocontainer/src/test/java/io/cucumber/picocontainer/DisposableCucumberBelly.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import org.picocontainer.Disposable;
44
import org.picocontainer.Startable;
55

6+
import java.util.ArrayList;
67
import java.util.List;
78

9+
import static org.junit.jupiter.api.Assertions.assertFalse;
10+
811
/**
912
* A test helper class which simulates a class that holds system resources which
1013
* need disposing at the end of the test.
@@ -13,19 +16,20 @@
1316
*/
1417
public class DisposableCucumberBelly
1518
implements Disposable, Startable {
19+
static final List<String> events = new ArrayList<>();
1620

1721
private List<String> contents;
1822
private boolean isDisposed = false;
1923
private boolean wasStarted = false;
2024
private boolean wasStopped = false;
2125

2226
public List<String> getContents() {
23-
assert !isDisposed;
27+
assertFalse(isDisposed);
2428
return contents;
2529
}
2630

2731
public void setContents(List<String> contents) {
28-
assert !isDisposed;
32+
assertFalse(isDisposed);
2933
this.contents = contents;
3034
}
3135

@@ -36,6 +40,7 @@ public void setContents(List<String> contents) {
3640
*/
3741
@Override
3842
public void dispose() {
43+
events.add("Disposed");
3944
isDisposed = true;
4045
}
4146

@@ -45,6 +50,7 @@ public boolean isDisposed() {
4550

4651
@Override
4752
public void start() {
53+
events.add("Started");
4854
wasStarted = true;
4955
}
5056

@@ -54,6 +60,7 @@ public boolean wasStarted() {
5460

5561
@Override
5662
public void stop() {
63+
events.add("Stopped");
5764
wasStopped = true;
5865
}
5966

cucumber-picocontainer/src/test/java/io/cucumber/picocontainer/StepDefinitions.java

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package io.cucumber.picocontainer;
22

3-
import io.cucumber.java.After;
4-
import io.cucumber.java.Before;
5-
import io.cucumber.java.Scenario;
3+
import io.cucumber.java.*;
64
import io.cucumber.java.en.Given;
75
import io.cucumber.java.en.Then;
86
import io.cucumber.java.en.When;
@@ -11,11 +9,12 @@
119
import java.util.Collections;
1210
import java.util.List;
1311

14-
import static org.junit.jupiter.api.Assertions.assertEquals;
12+
import static org.junit.jupiter.api.Assertions.*;
1513

1614
public class StepDefinitions {
1715

1816
private final DisposableCucumberBelly belly;
17+
private static int scenarioCount = 0;
1918

2019
public StepDefinitions(DisposableCucumberBelly belly) {
2120
this.belly = belly;
@@ -35,9 +34,37 @@ public void gh20() {
3534

3635
@After
3736
public void after() {
37+
scenarioCount++;
3838
// We might need to clean up the belly here, if it represented an
3939
// external resource.
40-
assert !belly.isDisposed();
40+
41+
// Call order should be Started > After > Stopped > Disposed, so here we
42+
// expect only Started
43+
assertTrue(belly.wasStarted());
44+
assertFalse(belly.wasStopped());
45+
assertFalse(belly.isDisposed());
46+
}
47+
48+
@BeforeAll
49+
@SuppressWarnings("unused")
50+
public static void beforeAll() {
51+
// reset static variables
52+
DisposableCucumberBelly.events.clear();
53+
scenarioCount = 0;
54+
}
55+
56+
@AfterAll
57+
@SuppressWarnings("unused")
58+
public static void afterAll() {
59+
List<String> events = DisposableCucumberBelly.events;
60+
// Call order should be Start > Stopped > Disposed, for each test
61+
// scenario
62+
assertEquals(3 * scenarioCount, events.size());
63+
for (int i = 0; i < scenarioCount; i += 3) {
64+
assertEquals("Started", events.get(i));
65+
assertEquals("Stopped", events.get(i + 1));
66+
assertEquals("Disposed", events.get(i + 2));
67+
}
4168
}
4269

4370
@Given("I have {int} {word} in my belly")

0 commit comments

Comments
 (0)