|
21 | 21 | import java.lang.reflect.Parameter; |
22 | 22 | import java.util.ArrayList; |
23 | 23 | import java.util.List; |
24 | | -import java.util.Optional; |
25 | | -import java.util.ServiceLoader; |
| 24 | +import java.util.Set; |
26 | 25 |
|
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; |
30 | 28 | import io.helidon.webserver.WebServer; |
31 | | -import io.helidon.webserver.WebServerConfig; |
32 | 29 | import io.helidon.webserver.spi.ServerFeature; |
33 | 30 | import io.helidon.webserver.testing.junit5.spi.DirectJunitExtension; |
| 31 | +import io.helidon.webserver.testing.junit5.spi.DirectJunitExtension.ParamHandler; |
34 | 32 |
|
35 | 33 | 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; |
39 | 34 | import org.junit.jupiter.api.extension.ExtensionContext; |
40 | | -import org.junit.jupiter.api.extension.InvocationInterceptor; |
41 | 35 | import org.junit.jupiter.api.extension.ParameterContext; |
42 | | -import org.junit.jupiter.api.extension.ParameterResolutionException; |
43 | 36 | import org.junit.jupiter.api.extension.ParameterResolver; |
44 | 37 |
|
| 38 | +import static io.helidon.webserver.testing.junit5.Junit5Util.withStaticMethods; |
| 39 | + |
45 | 40 | /** |
46 | | - * JUnit5 extension to support Helidon WebServer in tests. |
| 41 | + * JUnit5 extension to support Helidon WebServer in-memory unit tests. |
47 | 42 | */ |
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 { |
58 | 45 |
|
59 | 46 | HelidonRoutingJunitExtension() { |
60 | | - this.extensions = HelidonServiceLoader.create(ServiceLoader.load(DirectJunitExtension.class)).asList(); |
| 47 | + super(DirectJunitExtension.class, Set.of()); |
61 | 48 | } |
62 | 49 |
|
63 | 50 | @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) { |
69 | 53 | RoutingTest testAnnot = testClass.getAnnotation(RoutingTest.class); |
70 | 54 | 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)); |
73 | 58 | } |
74 | 59 |
|
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); |
108 | 65 |
|
109 | | - for (DirectJunitExtension extension : extensions) { |
110 | | - if (extension.supportsParameter(parameterContext, extensionContext)) { |
111 | | - return true; |
112 | | - } |
113 | | - } |
| 66 | + setupFeatures(builder, testClass); |
| 67 | + setupServer(builder, testClass); |
114 | 68 |
|
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 | + }); |
120 | 74 | } |
121 | 75 |
|
122 | 76 | @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()); |
131 | 82 | } |
132 | 83 | } |
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; |
139 | 85 | } |
140 | 86 |
|
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); |
150 | 90 | } |
151 | 91 |
|
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<>(); |
156 | 94 | for (Parameter parameter : method.getParameters()) { |
157 | | - // for each parameter, resolve parameter handler |
158 | 95 | 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); |
165 | 101 | found = true; |
166 | 102 | break; |
167 | 103 | } |
168 | 104 | } |
169 | 105 | 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)); |
172 | 109 | } |
173 | 110 | } |
174 | | - return socketName -> { |
175 | | - Object[] values = new Object[handlers.size()]; |
176 | 111 |
|
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 | + } |
187 | 116 |
|
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 | + } |
195 | 123 |
|
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 | + } |
198 | 127 | } |
199 | 128 | } |
0 commit comments