Skip to content

Commit 6f32f63

Browse files
save work
1 parent 2f533b5 commit 6f32f63

File tree

17 files changed

+1079
-670
lines changed

17 files changed

+1079
-670
lines changed

testing/testing/README.md

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,3 @@ Helidon Testing
22
-----
33

44
This module provides features that are most commonly required to test a Helidon application.
5-
6-
# TestConfigSource
7-
8-
Testing may require setting configuration values after the config instance is created, but before the value
9-
is used. This can be achieved through `TestConfigSource` (when creating configuration instance by hand or via
10-
ServiceRegistry; note that when using registry, there is nothing required to be done by you).
11-
12-
Simply create a new instance of `TestConfigSource` (may only be one for the whole test class, as config is created only once in the registry).
13-
14-
# Configuration annotations
15-
16-
Configuration can be further customized by using configuration annotations.
17-
18-
The following annotations are supported, to provide customized configuration.
19-
20-
The following table shows all configuration types and their weight used when constructing config (if relevant):
21-
22-
| Source | Weight | Description |
23-
|-----------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------|
24-
| `TestConfigSource` | 1_000_000 | Use `TestConfig` to set values, always the highest weight form this table |
25-
| `@TestConfig.Profile` | N/A | Customization of the config profile to use. Defaults to `test` |
26-
| `@TestConfig.Value` | 954_000 | Used to set a single key/value pair. Weight can be customized. Repeatable. |
27-
| `@TestConfig.Values` | 953_000 | Use to set multiple key/value pairs, with a format defined (such as yaml, properties). Weight can be customized. |
28-
| `@TestConfig.File` | 952_000 | Use to set a whole config file. Type is determined based on file suffix. Weight can be customized. Repeatable. |
29-
| `@TestConfig.Source` | 951_000 | Use to set a single config source. Weight can be customized. This annotation belongs to static method producing a config source. |
30-

tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLTestContainer.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,15 @@
2525
*/
2626
abstract class MySQLTestContainer {
2727

28-
private static final DockerImageName IMAGE = DockerImageName.parse("container-registry.oracle.com/mysql/community-server")
29-
.asCompatibleSubstituteFor("mysql");
28+
private static final boolean IS_ARM = System.getProperty("os.arch", "amd64").equals("aarch64");
29+
private static final DockerImageName X86_IMAGE =
30+
DockerImageName.parse("container-registry.oracle.com/mysql/community-server:9.4.0")
31+
.asCompatibleSubstituteFor("mysql");
32+
private static final DockerImageName ARM_IMAGE =
33+
DockerImageName.parse("container-registry.oracle.com/mysql/community-server:9.4.0-aarch64")
34+
.asCompatibleSubstituteFor("mysql");
3035

31-
static final MySQLContainer<?> CONTAINER = new MySQLContainer<>(IMAGE)
36+
static final MySQLContainer<?> CONTAINER = new MySQLContainer<>(IS_ARM ? ARM_IMAGE : X86_IMAGE)
3237
.withPassword("mysql123");
3338

3439
static Map<String, String> config() {

tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleTestContainer.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,21 @@
2727
*/
2828
abstract class OracleTestContainer {
2929

30-
private static final DockerImageName IMAGE = DockerImageName.parse("container-registry.oracle.com/database/express");
30+
private static final DockerImageName IMAGE = DockerImageName.parse("container-registry.oracle.com/database/free:latest-lite");
3131

3232
static final GenericContainer<?> CONTAINER = new GenericContainer<>(IMAGE)
3333
.withEnv("ORACLE_PWD", "oracle123")
3434
.withExposedPorts(1521)
35-
.withStartupAttempts(5)
36-
.waitingFor(Wait.forHealthcheck()
35+
.withStartupAttempts(3)
36+
.waitingFor(Wait.forLogMessage(".*DATABASE IS READY TO USE.*", 1)
3737
.withStartupTimeout(Duration.ofMinutes(5)));
3838

3939
static Map<String, String> config() {
40-
String jdbcUrl = String.format("jdbc:oracle:thin:@localhost:%s/XE", CONTAINER.getMappedPort(1521));
41-
return Map.of("javax.sql.DataSource.test.dataSource.url", jdbcUrl);
40+
return Map.of("javax.sql.DataSource.test.dataSource.url", jdbcUrl());
41+
}
42+
43+
private static String jdbcUrl() {
44+
return "jdbc:oracle:thin:@localhost:%s/FREE".formatted(CONTAINER.getMappedPort(1521));
4245
}
4346

4447
private OracleTestContainer() {

webserver/testing/junit5/junit5/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,10 @@
7171
<artifactId>junit-platform-testkit</artifactId>
7272
<scope>test</scope>
7373
</dependency>
74+
<dependency>
75+
<groupId>io.helidon.logging</groupId>
76+
<artifactId>helidon-logging-jul</artifactId>
77+
<scope>test</scope>
78+
</dependency>
7479
</dependencies>
7580
</project>

webserver/testing/junit5/junit5/src/main/java/io/helidon/webserver/testing/junit5/DirectClientServerContext.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
1616

1717
package io.helidon.webserver.testing.junit5;
1818

19+
import java.net.InetAddress;
20+
import java.net.UnknownHostException;
1921
import java.util.concurrent.ExecutorService;
2022
import java.util.concurrent.Executors;
2123

@@ -57,6 +59,7 @@ class DirectClientServerContext implements ConnectionContext, ListenerContext {
5759
.name("@default")
5860
.host(peerInfo.host())
5961
.port(peerInfo.port())
62+
.address(localhost())
6063
.listenerContext(Context.builder().id("test-direct-listener").build())
6164
.mediaContext(MediaContext.create())
6265
.contentEncoding(ContentEncodingContext.create())
@@ -116,22 +119,22 @@ public Router router() {
116119

117120
@Override
118121
public Context context() {
119-
return listenerConfiguration.listenerContext().get();
122+
return listenerConfiguration.listenerContext().orElseThrow();
120123
}
121124

122125
@Override
123126
public MediaContext mediaContext() {
124-
return listenerConfiguration.mediaContext().get();
127+
return listenerConfiguration.mediaContext().orElseThrow();
125128
}
126129

127130
@Override
128131
public ContentEncodingContext contentEncodingContext() {
129-
return listenerConfiguration.contentEncoding().get();
132+
return listenerConfiguration.contentEncoding().orElseThrow();
130133
}
131134

132135
@Override
133136
public DirectHandlers directHandlers() {
134-
return listenerConfiguration.directHandlers().get();
137+
return listenerConfiguration.directHandlers().orElseThrow();
135138
}
136139

137140
@Override
@@ -143,4 +146,12 @@ public ListenerConfig config() {
143146
public HelidonSocket serverSocket() {
144147
return serverSocket;
145148
}
149+
150+
private static InetAddress localhost() {
151+
try {
152+
return InetAddress.getLocalHost();
153+
} catch (UnknownHostException e) {
154+
throw new RuntimeException(e);
155+
}
156+
}
146157
}

webserver/testing/junit5/junit5/src/main/java/io/helidon/webserver/testing/junit5/HelidonRoutingJunitExtension.java

Lines changed: 59 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -21,179 +21,108 @@
2121
import java.lang.reflect.Parameter;
2222
import java.util.ArrayList;
2323
import java.util.List;
24-
import java.util.Optional;
25-
import java.util.ServiceLoader;
24+
import java.util.Set;
2625

27-
import io.helidon.common.HelidonServiceLoader;
28-
import io.helidon.common.config.GlobalConfig;
29-
import io.helidon.common.context.Contexts;
26+
import io.helidon.common.context.Context;
27+
import io.helidon.service.registry.Services;
3028
import io.helidon.webserver.WebServer;
31-
import io.helidon.webserver.WebServerConfig;
3229
import io.helidon.webserver.spi.ServerFeature;
3330
import io.helidon.webserver.testing.junit5.spi.DirectJunitExtension;
31+
import io.helidon.webserver.testing.junit5.spi.DirectJunitExtension.ParamHandler;
3432

3533
import org.junit.jupiter.api.extension.AfterAllCallback;
36-
import org.junit.jupiter.api.extension.AfterEachCallback;
37-
import org.junit.jupiter.api.extension.BeforeAllCallback;
38-
import org.junit.jupiter.api.extension.BeforeEachCallback;
3934
import org.junit.jupiter.api.extension.ExtensionContext;
40-
import org.junit.jupiter.api.extension.InvocationInterceptor;
4135
import org.junit.jupiter.api.extension.ParameterContext;
42-
import org.junit.jupiter.api.extension.ParameterResolutionException;
4336
import org.junit.jupiter.api.extension.ParameterResolver;
4437

38+
import static io.helidon.webserver.testing.junit5.Junit5Util.withStaticMethods;
39+
4540
/**
46-
* JUnit5 extension to support Helidon WebServer in tests.
41+
* JUnit5 extension to support Helidon WebServer in-memory unit tests.
4742
*/
48-
class HelidonRoutingJunitExtension extends JunitExtensionBase
49-
implements BeforeAllCallback,
50-
AfterAllCallback,
51-
InvocationInterceptor,
52-
BeforeEachCallback,
53-
AfterEachCallback,
54-
ParameterResolver {
55-
56-
private final List<DirectJunitExtension> extensions;
57-
private WebServerConfig serverConfig;
43+
class HelidonRoutingJunitExtension extends JunitExtensionBase<DirectJunitExtension>
44+
implements AfterAllCallback, ParameterResolver {
5845

5946
HelidonRoutingJunitExtension() {
60-
this.extensions = HelidonServiceLoader.create(ServiceLoader.load(DirectJunitExtension.class)).asList();
47+
super(DirectJunitExtension.class, Set.of());
6148
}
6249

6350
@Override
64-
public void beforeAll(ExtensionContext context) {
65-
super.beforeAll(context);
66-
67-
Class<?> testClass = context.getRequiredTestClass();
68-
super.testClass(testClass);
51+
@SuppressWarnings({"deprecation", "removal"})
52+
void init(Class<?> testClass, Context ctx) {
6953
RoutingTest testAnnot = testClass.getAnnotation(RoutingTest.class);
7054
if (testAnnot == null) {
71-
throw new IllegalStateException("Invalid test class for this extension: " + testClass + ", missing "
72-
+ RoutingTest.class.getName() + " annotation");
55+
throw new IllegalStateException(
56+
"Test class %s is not annotated with @RoutingTest"
57+
.formatted(testClass));
7358
}
7459

75-
WebServerConfig.Builder builder = WebServer.builder()
76-
.config(GlobalConfig.config().get("server"))
77-
.host("localhost");
78-
79-
extensions.forEach(it -> it.beforeAll(context));
80-
81-
setupFeatures(builder);
82-
setupServer(builder);
83-
84-
serverConfig = builder.buildPrototype();
85-
86-
initRoutings();
87-
}
88-
89-
@Override
90-
public void afterAll(ExtensionContext context) {
91-
extensions.forEach(it -> it.afterAll(context));
92-
super.afterAll(context);
93-
}
94-
95-
@Override
96-
public void beforeEach(ExtensionContext context) {
97-
extensions.forEach(it -> it.beforeAll(context));
98-
}
99-
100-
@Override
101-
public void afterEach(ExtensionContext context) {
102-
extensions.forEach(it -> it.afterEach(context));
103-
}
104-
105-
@Override
106-
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
107-
throws ParameterResolutionException {
60+
var config = Services.get(io.helidon.common.config.Config.class);
61+
var builder = WebServer.builder()
62+
.config(config.get("server"))
63+
.host("localhost")
64+
.port(0);
10865

109-
for (DirectJunitExtension extension : extensions) {
110-
if (extension.supportsParameter(parameterContext, extensionContext)) {
111-
return true;
112-
}
113-
}
66+
setupFeatures(builder, testClass);
67+
setupServer(builder, testClass);
11468

115-
Class<?> paramType = parameterContext.getParameter().getType();
116-
return Contexts.context()
117-
.orElseGet(Contexts::globalContext)
118-
.get(paramType)
119-
.isPresent();
69+
var server = builder.buildPrototype();
70+
withStaticMethods(testClass, SetUpRoute.class, (annot, method) -> {
71+
var socket = annot.value();
72+
handleParams(server.features(), method, socket);
73+
});
12074
}
12175

12276
@Override
123-
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
124-
throws ParameterResolutionException {
125-
126-
Class<?> paramType = parameterContext.getParameter().getType();
127-
128-
for (DirectJunitExtension extension : extensions) {
129-
if (extension.supportsParameter(parameterContext, extensionContext)) {
130-
return extension.resolveParameter(parameterContext, extensionContext, paramType);
77+
Object resolve(ParameterContext pc, ExtensionContext ctx) {
78+
for (DirectJunitExtension extension : extensions()) {
79+
if (extension.supportsParameter(pc, ctx)) {
80+
init(ctx);
81+
return extension.resolveParameter(pc, ctx, pc.getParameter().getType());
13182
}
13283
}
133-
134-
return Contexts.context()
135-
.orElseGet(Contexts::globalContext)
136-
.get(paramType)
137-
.orElseThrow(() -> new ParameterResolutionException("Failed to resolve parameter of type "
138-
+ paramType.getName()));
84+
return null;
13985
}
14086

141-
private void initRoutings() {
142-
List<ServerFeature> features = serverConfig.features();
143-
144-
Junit5Util.withStaticMethods(testClass(), SetUpRoute.class, (
145-
(setUpRoute, method) -> {
146-
String socketName = setUpRoute.value();
147-
SetUpRouteHandler methodConsumer = createRoutingMethodCall(features, method);
148-
methodConsumer.handle(socketName);
149-
}));
87+
@SuppressWarnings("unchecked")
88+
private <T> void handleParam(ParamHandler<T> handler, Method method, String socket, Object value) {
89+
handler.handle(method, socket, (T) value);
15090
}
15191

152-
private SetUpRouteHandler createRoutingMethodCall(List<ServerFeature> features, Method method) {
153-
154-
// @SetUpRoute may have parameters handled by different extensions
155-
List<DirectJunitExtension.ParamHandler> handlers = new ArrayList<>();
92+
private void handleParams(List<ServerFeature> features, Method method, String socket) {
93+
List<ParamHandler<?>> handlers = new ArrayList<>();
15694
for (Parameter parameter : method.getParameters()) {
157-
// for each parameter, resolve parameter handler
15895
boolean found = false;
159-
for (DirectJunitExtension extension : extensions) {
160-
Optional<? extends DirectJunitExtension.ParamHandler> paramHandler =
161-
extension.setUpRouteParamHandler(features, parameter.getType());
162-
if (paramHandler.isPresent()) {
163-
// we care about the extension with the highest priority only
164-
handlers.add(paramHandler.get());
96+
Class<?> paramType = parameter.getType();
97+
for (DirectJunitExtension e : extensions()) {
98+
var handler = e.setUpRouteParamHandler(features, paramType).orElse(null);
99+
if (handler != null) {
100+
handlers.add(handler);
165101
found = true;
166102
break;
167103
}
168104
}
169105
if (!found) {
170-
throw new IllegalArgumentException("Method " + method + " has a parameter " + parameter.getType() + " that is "
171-
+ "not supported by any available testing extension");
106+
throw new IllegalArgumentException(
107+
"Method %s has a parameter %s that is not supported"
108+
.formatted(method, paramType));
172109
}
173110
}
174-
return socketName -> {
175-
Object[] values = new Object[handlers.size()];
176111

177-
for (int i = 0; i < handlers.size(); i++) {
178-
values[i] = handlers.get(i).get(socketName);
179-
}
180-
181-
try {
182-
method.setAccessible(true);
183-
method.invoke(null, values);
184-
} catch (IllegalAccessException | InvocationTargetException e) {
185-
throw new IllegalStateException("Cannot invoke @SetUpRoute method", e);
186-
}
112+
var values = new Object[handlers.size()];
113+
for (int i = 0; i < handlers.size(); i++) {
114+
values[i] = handlers.get(i).get(socket);
115+
}
187116

188-
for (int i = 0; i < values.length; i++) {
189-
Object value = values[i];
190-
DirectJunitExtension.ParamHandler handler = handlers.get(i);
191-
handler.handle(method, socketName, value);
192-
}
193-
};
194-
}
117+
try {
118+
method.setAccessible(true);
119+
method.invoke(null, values);
120+
} catch (IllegalAccessException | InvocationTargetException e) {
121+
throw new IllegalStateException("Cannot invoke @SetUpRoute method", e);
122+
}
195123

196-
private interface SetUpRouteHandler {
197-
void handle(String socketName);
124+
for (int i = 0; i < values.length; i++) {
125+
handleParam(handlers.get(i), method, socket, values[i]);
126+
}
198127
}
199128
}

0 commit comments

Comments
 (0)