Skip to content

Commit 2cf70a9

Browse files
authored
Fix resolution happening on event loop (#391)
This commit makes sure that the resolution of test resources happens on the blocking executors. Without this, if an exception was thrown during startup of a test container, then the Netty event loop was corrupted and the server stopped responding.
1 parent f2db4a2 commit 2cf70a9

File tree

8 files changed

+188
-25
lines changed

8 files changed

+188
-25
lines changed

build.gradle

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ plugins {
33
id "io.micronaut.build.internal.quality-reporting"
44
}
55

6+
pluginManager.apply(io.micronaut.build.catalogs.MicronautVersionCatalogUpdatePlugin)
7+
8+
tasks.withType(io.micronaut.build.catalogs.tasks.VersionCatalogUpdate).configureEach {
9+
outputDirectory = file("gradle")
10+
}
11+
612
tasks.named("publishGuide") {
713
properties['micronautVersion'] = micronautVersion
814
}

gradle/libs.versions.toml

+19-19
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,34 @@
1616
# a managed version (a version which alias starts with "managed-"
1717

1818
[versions]
19-
micronaut = "4.0.0"
19+
micronaut = "4.0.1"
2020
micronaut-platform = "4.0.0-RC1"
2121
micronaut-aot = "2.0.0"
22-
micronaut-aws = "4.0.0"
23-
micronaut-data = "4.0.0"
24-
micronaut-discovery = "4.0.0"
22+
micronaut-aws = "4.0.1"
23+
micronaut-data = "4.0.2"
24+
micronaut-discovery = "4.0.1"
2525
micronaut-docs = "2.0.0"
26-
micronaut-elasticsearch = "5.0.0"
27-
micronaut-email = "2.0.0"
28-
micronaut-kafka = "5.0.0"
26+
micronaut-elasticsearch = "5.0.1"
27+
micronaut-email = "2.0.2"
28+
micronaut-kafka = "5.0.2"
2929
micronaut-logging = "1.0.0"
30-
micronaut-mongodb = "5.0.0-M8"
31-
micronaut-mqtt = "3.0.0"
32-
micronaut-neo4j = "6.0.0"
33-
micronaut-rabbitmq = "4.0.0"
34-
micronaut-r2dbc = "5.0.0"
35-
micronaut-reactor = "3.0.0"
30+
micronaut-mongodb = "5.0.1"
31+
micronaut-mqtt = "3.0.1"
32+
micronaut-neo4j = "6.0.1"
33+
micronaut-rabbitmq = "4.0.1"
34+
micronaut-r2dbc = "5.0.1"
35+
micronaut-reactor = "3.0.1"
3636
micronaut-gradle-plugin = "4.0.0-M3"
37-
micronaut-redis = "6.0.0"
38-
micronaut-security = "4.0.0"
39-
micronaut-serde = "2.0.0"
40-
micronaut-sql = "5.0.0"
37+
micronaut-redis = "6.0.1"
38+
micronaut-security = "4.0.1"
39+
micronaut-serde = "2.1.0"
40+
micronaut-sql = "5.0.1"
4141
micronaut-test = "4.0.0"
4242
groovy = "4.0.13"
4343
spock = "2.3-groovy-4.0"
4444
vertx-client = "4.4.2"
45-
amazon-awssdk-v1 = "1.12.503"
46-
amazon-awssdk-v2 = "2.20.98"
45+
amazon-awssdk-v1 = "1.12.517"
46+
amazon-awssdk-v2 = "2.20.114"
4747

4848
# Managed versions appear in the BOM
4949
managed-testcontainers = "1.18.3"

test-resources-server/build.gradle

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ plugins {
44
id 'io.micronaut.build.internal.kafka-testing'
55
alias(libs.plugins.micronaut.miniapp)
66
alias(libs.plugins.micronaut.aot)
7+
id('java-test-fixtures')
78
}
89

910
description = """
@@ -23,13 +24,16 @@ dependencies {
2324
runtimeOnly(mn.micronaut.management)
2425
runtimeOnly(mnSerde.micronaut.serde.jackson)
2526

27+
testFixturesImplementation(project(':micronaut-test-resources-core'))
28+
testFixturesImplementation(project(':micronaut-test-resources-testcontainers'))
2629
testImplementation(mn.micronaut.http.client)
2730
testImplementation(project(':micronaut-test-resources-client'))
2831
testRuntimeOnly(project(':micronaut-test-resources-kafka'))
2932

3033
// For logback conversion
3134
aotPlugins(mnLogging.logback.classic)
3235
aotPlugins("org.fusesource.jansi:jansi:1.18")
36+
aotPlugins(testFixtures(project(":micronaut-test-resources-server")))
3337
}
3438

3539
application {

test-resources-server/src/main/java/io/micronaut/testresources/server/TestResourcesController.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import io.micronaut.http.annotation.Controller;
2020
import io.micronaut.http.annotation.Get;
2121
import io.micronaut.http.annotation.Post;
22+
import io.micronaut.scheduling.TaskExecutors;
23+
import io.micronaut.scheduling.annotation.ExecuteOn;
2224
import io.micronaut.testresources.core.TestResourcesResolver;
2325
import io.micronaut.testresources.embedded.TestResourcesResolverLoader;
2426
import io.micronaut.testresources.testcontainers.TestContainers;
@@ -30,13 +32,13 @@
3032
import java.util.List;
3133
import java.util.Map;
3234
import java.util.Optional;
33-
import java.util.stream.Collectors;
3435

3536
/**
3637
* A client responsible for connecting to a test resources
3738
* server.
3839
*/
3940
@Controller("/")
41+
@ExecuteOn(TaskExecutors.BLOCKING)
4042
public final class TestResourcesController implements TestResourcesResolver {
4143
private static final Logger LOGGER = LoggerFactory.getLogger(TestResourcesController.class);
4244

@@ -56,7 +58,7 @@ public List<String> getResolvableProperties(Map<String, Collection<String>> prop
5658
.flatMap(Collection::stream)
5759
.distinct()
5860
.peek(p -> LOGGER.debug("For configuration {} and property entries {} , resolvable property: {}", testResourcesConfig, propertyEntries, p))
59-
.collect(Collectors.toList());
61+
.toList();
6062
}
6163

6264
@Override
@@ -67,7 +69,7 @@ public List<String> getRequiredProperties(String expression) {
6769
.map(testResourcesResolver -> testResourcesResolver.getRequiredProperties(expression))
6870
.flatMap(Collection::stream)
6971
.distinct()
70-
.collect(Collectors.toList());
72+
.toList();
7173
}
7274

7375
@Override
@@ -78,7 +80,7 @@ public List<String> getRequiredPropertyEntries() {
7880
.map(TestResourcesResolver::getRequiredPropertyEntries)
7981
.flatMap(Collection::stream)
8082
.distinct()
81-
.collect(Collectors.toList());
83+
.toList();
8284
}
8385

8486
@Post("/resolve")
@@ -125,7 +127,7 @@ public List<TestContainer> listContainersByScope(@Nullable String scope) {
125127
c.getContainerId(),
126128
entry.getKey().toString())
127129
))
128-
.collect(Collectors.toList());
130+
.toList();
129131
}
130132

131133
}

test-resources-server/src/test/groovy/io/micronaut/testresources/server/TestResourcesControllerTest.groovy

+53-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class TestResourcesControllerTest extends Specification {
2020

2121
def "verifies that server can instantiate container"() {
2222
expect:
23-
client.resolvableProperties == ['kafka.bootstrap.servers']
23+
client.resolvableProperties ==~ ['kafka.bootstrap.servers', 'failing.message', 'failing.container']
2424
client.listContainers().empty
2525

2626
when:
@@ -38,6 +38,58 @@ class TestResourcesControllerTest extends Specification {
3838
client.listContainers().empty
3939
}
4040

41+
def "can resolve a valid property after an error in a regular test resource"() {
42+
expect:
43+
client.resolvableProperties ==~ ['kafka.bootstrap.servers', 'failing.message', 'failing.container']
44+
client.listContainers().empty
45+
46+
when: "resolution of a property fails"
47+
client.resolve("failing.message", [:], [:])
48+
49+
then:
50+
RuntimeException ex = thrown()
51+
52+
when: "resolves another property"
53+
client.resolve("kafka.bootstrap.servers", [:], [:])
54+
55+
then:
56+
def containers = client.listContainers()
57+
containers.size() == 1
58+
containers[0].imageName.startsWith 'confluentinc/cp-kafka'
59+
60+
when:
61+
client.closeAll()
62+
63+
then:
64+
client.listContainers().empty
65+
}
66+
67+
def "can resolve a valid property after an error in a container test resource"() {
68+
expect:
69+
client.resolvableProperties ==~ ['kafka.bootstrap.servers', 'failing.message', 'failing.container']
70+
client.listContainers().empty
71+
72+
when: "resolution of a property which starts a container"
73+
client.resolve("failing.container", [:], [:])
74+
75+
then:
76+
RuntimeException ex = thrown()
77+
78+
when: "resolves another property"
79+
client.resolve("kafka.bootstrap.servers", [:], [:])
80+
81+
then:
82+
def containers = client.listContainers()
83+
containers.size() == 1
84+
containers[0].imageName.startsWith 'confluentinc/cp-kafka'
85+
86+
when:
87+
client.closeAll()
88+
89+
then:
90+
client.listContainers().empty
91+
}
92+
4193
@Client("/")
4294
static interface DiagnosticsClient extends TestResourcesClient {
4395
@Get("/testcontainers")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2003-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.testresources.fixtures;
17+
18+
import io.micronaut.testresources.testcontainers.AbstractTestContainersProvider;
19+
import org.testcontainers.containers.ContainerLaunchException;
20+
import org.testcontainers.containers.GenericContainer;
21+
import org.testcontainers.utility.DockerImageName;
22+
23+
import java.util.Collection;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Optional;
27+
28+
public class FailingContainer extends AbstractTestContainersProvider<GenericContainer<?>> {
29+
30+
public static final String FAILING_CONTAINER = "failing.container";
31+
32+
@Override
33+
public List<String> getResolvableProperties(Map<String, Collection<String>> propertyEntries, Map<String, Object> testResourcesConfig) {
34+
return List.of(FAILING_CONTAINER);
35+
}
36+
37+
@Override
38+
protected String getSimpleName() {
39+
return "failing";
40+
}
41+
42+
@Override
43+
protected String getDefaultImageName() {
44+
return "foo";
45+
}
46+
47+
@Override
48+
protected GenericContainer<?> createContainer(DockerImageName imageName, Map<String, Object> requestedProperties, Map<String, Object> testResourcesConfig) {
49+
throw new ContainerLaunchException("test");
50+
}
51+
52+
@Override
53+
protected Optional<String> resolveProperty(String propertyName, GenericContainer<?> container) {
54+
return Optional.empty();
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2003-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.testresources.fixtures;
17+
18+
import io.micronaut.testresources.core.TestResourcesResolver;
19+
20+
import java.util.Collection;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Optional;
24+
25+
public class FailingTestResource implements TestResourcesResolver {
26+
27+
public static final String FAILING_MESSAGE = "failing.message";
28+
29+
@Override
30+
public List<String> getResolvableProperties(Map<String, Collection<String>> propertyEntries, Map<String, Object> testResourcesConfig) {
31+
return List.of(FAILING_MESSAGE);
32+
}
33+
34+
@Override
35+
public Optional<String> resolve(String propertyName, Map<String, Object> properties, Map<String, Object> testResourcesConfig) {
36+
if (FAILING_MESSAGE.equals(propertyName)) {
37+
throw new RuntimeException("This is failing");
38+
}
39+
return Optional.empty();
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
io.micronaut.testresources.fixtures.FailingTestResource
2+
io.micronaut.testresources.fixtures.FailingContainer

0 commit comments

Comments
 (0)