Skip to content

Commit 111aeec

Browse files
committed
Split lifecycle into orthogonal scope and managed attribute
Replace the single TestcontainerLifecycle enum (SUITE, CLASS, MANUAL) with two independent @TestContainer attributes: boolean value() for managed/manual control (matching upstream/main) and TestcontainerScope scope() for CLASS/SUITE scoping. This allows combinations like a manually managed suite-scoped container. Signed-off-by: Radoslav Husar <radosoft@gmail.com>
1 parent d80a069 commit 111aeec

9 files changed

Lines changed: 66 additions & 59 deletions

File tree

README.adoc

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,32 +89,38 @@ public class TypeSpecifiedInjectionTest {
8989
}
9090
----
9191

92-
=== Container Lifecycle
92+
=== Container Scope
9393

9494
By default, this extension manages the lifecycle of each Testcontainer per test class. The `@Testcontainer` annotation
95-
accepts a `TestcontainerLifecycle` value to control when containers are started and stopped:
95+
accepts a `scope` attribute to control the container's scope:
9696

97-
* `TestcontainerLifecycle.CLASS` (default) -- the container is started before each test class and stopped after it
97+
* `TestcontainerScope.CLASS` (default) -- the container is started before each test class and stopped after it
9898
completes.
99-
* `TestcontainerLifecycle.SUITE` -- the container is started once on first encounter and stopped when the test suite
100-
ends. The same container instance is shared across all test classes that request the same type with suite lifecycle.
99+
* `TestcontainerScope.SUITE` -- the container is started once on first encounter and stopped when the test suite
100+
ends. The same container instance is shared across all test classes that request the same type with suite scope.
101101
This is useful for expensive containers that do not need to be restarted between test classes.
102-
* `TestcontainerLifecycle.MANUAL` -- the container is created and injected but never started or stopped by the
103-
framework. The user is responsible for managing the container lifecycle.
102+
103+
The `value` attribute (default `true`) controls whether Arquillian manages the container lifecycle. Setting it to
104+
`false` means the container is created and injected but never started by the framework. The user is responsible for
105+
starting the container manually. This is orthogonal to scope -- you can have a manually managed suite-scoped container.
104106

105107
[source,java]
106108
----
107109
// Suite-scoped container shared across test classes
108-
@Testcontainer(TestcontainerLifecycle.SUITE)
110+
@Testcontainer(scope = TestcontainerScope.SUITE)
109111
private KeycloakContainer keycloak;
110112
111113
// Class-scoped container (default behavior)
112114
@Testcontainer
113115
private SimpleTestContainer container;
114116
115117
// Manually managed container
116-
@Testcontainer(TestcontainerLifecycle.MANUAL)
118+
@Testcontainer(false)
117119
private SimpleTestContainer manualContainer;
120+
121+
// Manually managed suite-scoped container
122+
@Testcontainer(value = false, scope = TestcontainerScope.SUITE)
123+
private SimpleTestContainer manualSuiteContainer;
118124
----
119125

120126
== Helpers

src/main/java/org/arquillian/testcontainers/TestContainersObserver.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import java.lang.reflect.InvocationTargetException;
99

1010
import org.arquillian.testcontainers.api.TestcontainerEventContext;
11-
import org.arquillian.testcontainers.api.TestcontainerLifecycle;
1211
import org.arquillian.testcontainers.api.TestcontainersRequired;
1312
import org.arquillian.testcontainers.spi.event.AfterTestcontainerStart;
1413
import org.arquillian.testcontainers.spi.event.AfterTestcontainerStop;
@@ -115,12 +114,10 @@ public void stopContainer(@Observes AfterClass afterClass) {
115114
TestcontainerRegistryView registries = containerRegistries.get();
116115
if (registries != null) {
117116
for (TestcontainerDescription container : registries.classRegistry()) {
118-
if (container.testcontainer.value() == TestcontainerLifecycle.CLASS) {
119-
final TestcontainerEventContext context = createContext(container);
120-
beforeTestcontainerStop.fire(new BeforeTestcontainerStop(context));
121-
container.instance.stop();
122-
afterTestcontainerStop.fire(new AfterTestcontainerStop(context));
123-
}
117+
final TestcontainerEventContext context = createContext(container);
118+
beforeTestcontainerStop.fire(new BeforeTestcontainerStop(context));
119+
container.instance.stop();
120+
afterTestcontainerStop.fire(new AfterTestcontainerStop(context));
124121
}
125122
}
126123
}
@@ -138,7 +135,7 @@ public void startContainer(@Observes(precedence = 500) final AfterEnrichment eve
138135
}
139136

140137
for (TestcontainerDescription description : registries.classRegistry()) {
141-
if (description.testcontainer.value() == TestcontainerLifecycle.CLASS) {
138+
if (description.testcontainer.value()) {
142139
final TestcontainerEventContext context = createContext(description);
143140
beforeTestcontainerStart.fire(new BeforeTestcontainerStart(context));
144141
description.instance.start();
@@ -147,7 +144,7 @@ public void startContainer(@Observes(precedence = 500) final AfterEnrichment eve
147144
}
148145

149146
for (TestcontainerDescription description : registries.suiteRegistry()) {
150-
if (!description.instance.isRunning()) {
147+
if (description.testcontainer.value() && !description.instance.isRunning()) {
151148
final TestcontainerEventContext context = createContext(description);
152149
beforeTestcontainerStart.fire(new BeforeTestcontainerStart(context));
153150
description.instance.start();
@@ -174,7 +171,8 @@ public void stopSuiteContainers(@Observes AfterSuite afterSuite) {
174171
}
175172

176173
private static TestcontainerEventContext createContext(final TestcontainerDescription description) {
177-
return new TestcontainerEventContext(description.name, description.testcontainer.value(), description.instance);
174+
return new TestcontainerEventContext(description.name, description.testcontainer.scope(),
175+
description.testcontainer.value(), description.instance);
178176
}
179177

180178
@SuppressWarnings({ "resource", "BooleanMethodIsAlwaysInverted" })

src/main/java/org/arquillian/testcontainers/TestcontainerRegistryView.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
import java.util.List;
99

1010
import org.arquillian.testcontainers.api.Testcontainer;
11-
import org.arquillian.testcontainers.api.TestcontainerLifecycle;
11+
import org.arquillian.testcontainers.api.TestcontainerScope;
1212
import org.testcontainers.containers.GenericContainer;
1313

1414
/**
1515
* A unified view over both the suite-scoped and class-scoped {@link TestcontainerRegistry} instances, routing
1616
* container lookup and creation to the appropriate registry based on the container's configured
17-
* {@link TestcontainerLifecycle lifecycle}.
17+
* {@link TestcontainerScope scope}.
1818
* <p>
1919
* This class exists because Arquillian's {@link org.jboss.arquillian.core.api.Instance Instance&lt;T&gt;} injection
2020
* in {@link org.jboss.arquillian.test.spi.TestEnricher TestEnricher} services cannot carry scope annotations
@@ -37,7 +37,7 @@ class TestcontainerRegistryView {
3737

3838
GenericContainer<?> lookupOrCreate(final Class<GenericContainer<?>> type, final Testcontainer testcontainer,
3939
final List<Annotation> qualifiers) {
40-
if (testcontainer.value() == TestcontainerLifecycle.SUITE) {
40+
if (testcontainer.scope() == TestcontainerScope.SUITE) {
4141
return suiteRegistry.lookupOrCreate(type, testcontainer, qualifiers);
4242
}
4343
return classRegistry.lookupOrCreate(type, testcontainer, qualifiers);

src/main/java/org/arquillian/testcontainers/api/Testcontainer.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,26 @@
4848
public @interface Testcontainer {
4949

5050
/**
51-
* Defines the lifecycle scope for this Testcontainer.
51+
* Indicates whether Arquillian should manage the starting of the Testcontainer. With a value of {@code false},
52+
* Arquillian will not start the server. It will still attempt to stop the server at the end of the configured
53+
* {@link #scope() scope}.
54+
*
55+
* @return {@code true} to have Arquillian manage the lifecycle of the Testcontainer
56+
*/
57+
boolean value() default true;
58+
59+
/**
60+
* Defines the scope for this Testcontainer.
5261
* <ul>
53-
* <li>{@link TestcontainerLifecycle#SUITE SUITE} - the container is started once on first encounter and stopped
62+
* <li>{@link TestcontainerScope#SUITE SUITE} - the container is started once on first encounter and stopped
5463
* when the test suite ends; the same instance is shared across test classes</li>
55-
* <li>{@link TestcontainerLifecycle#CLASS CLASS} - the container is started before each test class and stopped
64+
* <li>{@link TestcontainerScope#CLASS CLASS} - the container is started before each test class and stopped
5665
* after it completes (default)</li>
57-
* <li>{@link TestcontainerLifecycle#MANUAL MANUAL} - the container is created and injected but never started or
58-
* stopped by the extension</li>
5966
* </ul>
6067
*
61-
* @return the lifecycle scope for this Testcontainer
68+
* @return the scope for this Testcontainer
6269
*/
63-
TestcontainerLifecycle value() default TestcontainerLifecycle.CLASS;
70+
TestcontainerScope scope() default TestcontainerScope.CLASS;
6471

6572
/**
6673
* An optional name for the container. When set, the container is registered and looked up by this name, allowing

src/main/java/org/arquillian/testcontainers/api/TestcontainerEventContext.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,23 @@
77
import org.testcontainers.containers.GenericContainer;
88

99
/**
10-
* Describes a testcontainer in the context of a lifecycle event. Provides the container name, lifecycle scope,
11-
* and the container instance.
10+
* Describes a testcontainer in the context of a lifecycle event. Provides the container name, scope, whether it is
11+
* managed by Arquillian, and the container instance.
1212
*
1313
* @author Radoslav Husar
1414
*/
1515
public class TestcontainerEventContext {
1616

1717
private final String name;
18-
private final TestcontainerLifecycle lifecycle;
18+
private final TestcontainerScope scope;
19+
private final boolean managed;
1920
private final GenericContainer<?> container;
2021

21-
public TestcontainerEventContext(final String name, final TestcontainerLifecycle lifecycle,
22+
public TestcontainerEventContext(final String name, final TestcontainerScope scope, final boolean managed,
2223
final GenericContainer<?> container) {
2324
this.name = name;
24-
this.lifecycle = lifecycle;
25+
this.scope = scope;
26+
this.managed = managed;
2527
this.container = container;
2628
}
2729

@@ -35,21 +37,21 @@ public String getName() {
3537
}
3638

3739
/**
38-
* Returns the lifecycle scope of this container.
40+
* Returns the scope of this container.
3941
*
40-
* @return the lifecycle scope
42+
* @return the scope
4143
*/
42-
public TestcontainerLifecycle getLifecycle() {
43-
return lifecycle;
44+
public TestcontainerScope getScope() {
45+
return scope;
4446
}
4547

4648
/**
47-
* Returns whether this container's lifecycle is managed by Arquillian (i.e. not {@link TestcontainerLifecycle#MANUAL}).
49+
* Returns whether this container's lifecycle is managed by Arquillian.
4850
*
4951
* @return {@code true} if Arquillian manages start/stop
5052
*/
5153
public boolean isManaged() {
52-
return lifecycle != TestcontainerLifecycle.MANUAL;
54+
return managed;
5355
}
5456

5557
/**

src/main/java/org/arquillian/testcontainers/api/TestcontainerLifecycle.java renamed to src/main/java/org/arquillian/testcontainers/api/TestcontainerScope.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,22 @@
55
package org.arquillian.testcontainers.api;
66

77
/**
8-
* Defines the lifecycle scope for a container managed by the Arquillian Testcontainers extension.
8+
* Defines the scope for a container managed by the Arquillian Testcontainers extension.
99
*
1010
* @author Radoslav Husar
1111
*/
12-
public enum TestcontainerLifecycle {
13-
14-
/**
15-
* The container is started once on first encounter and stopped when the test suite ends. The same container
16-
* instance is shared across all test classes that request the same container type with suite lifecycle.
17-
*/
18-
SUITE,
12+
public enum TestcontainerScope {
1913

2014
/**
2115
* The container is started before each test class and stopped after the test class completes. This is the default
22-
* behavior.
16+
* scope.
2317
*/
2418
CLASS,
2519

2620
/**
27-
* The container is created and injected but never started or stopped by the framework. The user is responsible for
28-
* managing the container lifecycle.
21+
* The container is started once on first encounter and stopped when the test suite ends. The same container
22+
* instance is shared across all test classes that request the same container type with suite scope.
2923
*/
30-
MANUAL,
24+
SUITE,
25+
3126
}

src/test/java/org/arquillian/testcontainers/ManualContainerTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package org.arquillian.testcontainers;
77

88
import org.arquillian.testcontainers.api.Testcontainer;
9-
import org.arquillian.testcontainers.api.TestcontainerLifecycle;
109
import org.arquillian.testcontainers.api.TestcontainersRequired;
1110
import org.arquillian.testcontainers.common.SimpleTestContainer;
1211
import org.jboss.arquillian.container.test.api.Deployment;
@@ -33,7 +32,7 @@
3332
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
3433
public class ManualContainerTest {
3534

36-
@Testcontainer(TestcontainerLifecycle.MANUAL)
35+
@Testcontainer(false)
3736
private static SimpleTestContainer container;
3837

3938
@Deployment

src/test/java/org/arquillian/testcontainers/MixedLifecycleTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
package org.arquillian.testcontainers;
77

88
import org.arquillian.testcontainers.api.Testcontainer;
9-
import org.arquillian.testcontainers.api.TestcontainerLifecycle;
9+
import org.arquillian.testcontainers.api.TestcontainerScope;
1010
import org.arquillian.testcontainers.api.TestcontainersRequired;
1111
import org.arquillian.testcontainers.common.SimpleTestContainer;
1212
import org.arquillian.testcontainers.common.WildFlyContainer;
@@ -31,10 +31,10 @@
3131
@TestcontainersRequired(TestAbortedException.class)
3232
public class MixedLifecycleTest {
3333

34-
@Testcontainer(TestcontainerLifecycle.SUITE)
34+
@Testcontainer(scope = TestcontainerScope.SUITE)
3535
private SimpleTestContainer suiteContainer;
3636

37-
@Testcontainer(TestcontainerLifecycle.CLASS)
37+
@Testcontainer(scope = TestcontainerScope.CLASS)
3838
private WildFlyContainer classContainer;
3939

4040
@Deployment

src/test/java/org/arquillian/testcontainers/SuiteContainerTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
package org.arquillian.testcontainers;
77

88
import org.arquillian.testcontainers.api.Testcontainer;
9-
import org.arquillian.testcontainers.api.TestcontainerLifecycle;
9+
import org.arquillian.testcontainers.api.TestcontainerScope;
1010
import org.arquillian.testcontainers.api.TestcontainersRequired;
1111
import org.arquillian.testcontainers.common.SimpleTestContainer;
1212
import org.jboss.arquillian.container.test.api.Deployment;
@@ -28,7 +28,7 @@
2828
@TestcontainersRequired(TestAbortedException.class)
2929
public class SuiteContainerTest {
3030

31-
@Testcontainer(TestcontainerLifecycle.SUITE)
31+
@Testcontainer(scope = TestcontainerScope.SUITE)
3232
private SimpleTestContainer suiteContainer;
3333

3434
@Deployment

0 commit comments

Comments
 (0)