Browse Source
Add support for a `spring.testcontainers.startup` property that can be set to "sequential" or "parallel" to change how containers are started. Closes gh-37073pull/37905/head
11 changed files with 293 additions and 16 deletions
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
/* |
||||
* Copyright 2012-2023 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 |
||||
* |
||||
* https://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.boot.testcontainers.lifecycle; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
import org.testcontainers.lifecycle.Startable; |
||||
import org.testcontainers.lifecycle.Startables; |
||||
|
||||
import org.springframework.core.env.ConfigurableEnvironment; |
||||
import org.springframework.core.env.Environment; |
||||
|
||||
/** |
||||
* Testcontainers startup strategies. The strategy to use can be configured in the Spring |
||||
* {@link Environment} with a {@value #PROPERTY} property. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 3.2.0 |
||||
*/ |
||||
public enum TestcontainersStartup { |
||||
|
||||
/** |
||||
* Startup containers sequentially. |
||||
*/ |
||||
SEQUENTIAL { |
||||
|
||||
@Override |
||||
void start(Collection<? extends Startable> startables) { |
||||
startables.forEach(Startable::start); |
||||
} |
||||
|
||||
}, |
||||
|
||||
/** |
||||
* Startup containers in parallel. |
||||
*/ |
||||
PARALLEL { |
||||
|
||||
@Override |
||||
void start(Collection<? extends Startable> startables) { |
||||
Startables.deepStart(startables).join(); |
||||
} |
||||
|
||||
}; |
||||
|
||||
/** |
||||
* The {@link Environment} property used to change the {@link TestcontainersStartup} |
||||
* strategy. |
||||
*/ |
||||
public static final String PROPERTY = "spring.testcontainers.startup"; |
||||
|
||||
abstract void start(Collection<? extends Startable> startables); |
||||
|
||||
static TestcontainersStartup get(ConfigurableEnvironment environment) { |
||||
return get((environment != null) ? environment.getProperty(PROPERTY) : null); |
||||
} |
||||
|
||||
private static TestcontainersStartup get(String value) { |
||||
if (value == null) { |
||||
return SEQUENTIAL; |
||||
} |
||||
String canonicalName = getCanonicalName(value); |
||||
for (TestcontainersStartup candidate : values()) { |
||||
if (candidate.name().equalsIgnoreCase(canonicalName)) { |
||||
return candidate; |
||||
} |
||||
} |
||||
throw new IllegalArgumentException("Unknown '%s' property value '%s'".formatted(PROPERTY, value)); |
||||
} |
||||
|
||||
private static String getCanonicalName(String name) { |
||||
StringBuilder canonicalName = new StringBuilder(name.length()); |
||||
name.chars() |
||||
.filter(Character::isLetterOrDigit) |
||||
.map(Character::toLowerCase) |
||||
.forEach((c) -> canonicalName.append((char) c)); |
||||
return canonicalName.toString(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
{ |
||||
"properties": [ |
||||
{ |
||||
"name": "spring.testcontainers.startup", |
||||
"type": "org.springframework.boot.testcontainers.lifecycle.TestcontainersStartup", |
||||
"description": "Testcontainers startup modes.", |
||||
"defaultValue": "sequential" |
||||
} |
||||
] |
||||
} |
||||
@ -0,0 +1,122 @@
@@ -0,0 +1,122 @@
|
||||
/* |
||||
* Copyright 2012-2023 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 |
||||
* |
||||
* https://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.boot.testcontainers.lifecycle; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.testcontainers.lifecycle.Startable; |
||||
|
||||
import org.springframework.mock.env.MockEnvironment; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link TestcontainersStartup}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class TestcontainersStartupTests { |
||||
|
||||
private static final String PROPERTY = TestcontainersStartup.PROPERTY; |
||||
|
||||
private final AtomicInteger counter = new AtomicInteger(); |
||||
|
||||
@Test |
||||
void startWhenSquentialStartsSequentially() { |
||||
List<TestStartable> startables = createTestStartables(100); |
||||
TestcontainersStartup.SEQUENTIAL.start(startables); |
||||
for (int i = 0; i < startables.size(); i++) { |
||||
assertThat(startables.get(i).getIndex()).isEqualTo(i); |
||||
assertThat(startables.get(i).getThreadName()).isEqualTo(Thread.currentThread().getName()); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
void startWhenParallelStartsInParallel() { |
||||
List<TestStartable> startables = createTestStartables(100); |
||||
TestcontainersStartup.PARALLEL.start(startables); |
||||
assertThat(startables.stream().map(TestStartable::getThreadName)).hasSizeGreaterThan(1); |
||||
} |
||||
|
||||
@Test |
||||
void getWhenNoPropertyReturnsDefault() { |
||||
MockEnvironment environment = new MockEnvironment(); |
||||
assertThat(TestcontainersStartup.get(environment)).isEqualTo(TestcontainersStartup.SEQUENTIAL); |
||||
} |
||||
|
||||
@Test |
||||
void getWhenPropertyReturnsBasedOnValue() { |
||||
MockEnvironment environment = new MockEnvironment(); |
||||
assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "SEQUENTIAL"))) |
||||
.isEqualTo(TestcontainersStartup.SEQUENTIAL); |
||||
assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "sequential"))) |
||||
.isEqualTo(TestcontainersStartup.SEQUENTIAL); |
||||
assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "SEQuenTIaL"))) |
||||
.isEqualTo(TestcontainersStartup.SEQUENTIAL); |
||||
assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "S-E-Q-U-E-N-T-I-A-L"))) |
||||
.isEqualTo(TestcontainersStartup.SEQUENTIAL); |
||||
assertThat(TestcontainersStartup.get(environment.withProperty(PROPERTY, "parallel"))) |
||||
.isEqualTo(TestcontainersStartup.PARALLEL); |
||||
} |
||||
|
||||
@Test |
||||
void getWhenUnknownPropertyThrowsException() { |
||||
MockEnvironment environment = new MockEnvironment(); |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> TestcontainersStartup.get(environment.withProperty(PROPERTY, "bad"))) |
||||
.withMessage("Unknown 'spring.testcontainers.startup' property value 'bad'"); |
||||
} |
||||
|
||||
private List<TestStartable> createTestStartables(int size) { |
||||
List<TestStartable> testStartables = new ArrayList<>(size); |
||||
for (int i = 0; i < size; i++) { |
||||
testStartables.add(new TestStartable()); |
||||
} |
||||
return testStartables; |
||||
} |
||||
|
||||
private class TestStartable implements Startable { |
||||
|
||||
private int index; |
||||
|
||||
private String threadName; |
||||
|
||||
@Override |
||||
public void start() { |
||||
this.index = TestcontainersStartupTests.this.counter.getAndIncrement(); |
||||
this.threadName = Thread.currentThread().getName(); |
||||
} |
||||
|
||||
@Override |
||||
public void stop() { |
||||
} |
||||
|
||||
int getIndex() { |
||||
return this.index; |
||||
} |
||||
|
||||
String getThreadName() { |
||||
return this.threadName; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue