diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index cdf932620ec..ee15fce0c99 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -100,7 +100,7 @@ jobs: strategy: matrix: os: [ ubuntu-20.04 ] - moduleSet: [ core, it, dbclient, dbclient-oracle, others ] + moduleSet: [ core, it, jpa, jpa-oracle, dbclient, dbclient-oracle, others ] include: - { os: ubuntu-20.04, platform: linux } runs-on: ${{ matrix.os }} diff --git a/bom/pom.xml b/bom/pom.xml index 1b2b21945fd..c9594ed6f38 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -1093,6 +1093,11 @@ helidon-common-testing-virtual-threads ${helidon.version} + + io.helidon.microprofile.testing + helidon-microprofile-testing + ${helidon.version} + io.helidon.microprofile.testing helidon-microprofile-testing-junit5 diff --git a/common/testing/virtual-threads/src/main/java/module-info.java b/common/testing/virtual-threads/src/main/java/module-info.java index 246c15c45b2..aa49cc8532a 100644 --- a/common/testing/virtual-threads/src/main/java/module-info.java +++ b/common/testing/virtual-threads/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,8 @@ module io.helidon.common.testing.vitualthreads { requires jdk.jfr; exports io.helidon.common.testing.virtualthreads to + io.helidon.microprofile.testing, io.helidon.microprofile.testing.junit5, io.helidon.microprofile.testing.testng, io.helidon.webserver.testing.junit5; -} \ No newline at end of file +} diff --git a/docs/src/main/asciidoc/se/config/introduction.adoc b/docs/src/main/asciidoc/se/config/introduction.adoc index 3d434f7a722..3d704f2ed59 100644 --- a/docs/src/main/asciidoc/se/config/introduction.adoc +++ b/docs/src/main/asciidoc/se/config/introduction.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2018, 2024 Oracle and/or its affiliates. + Copyright (c) 2018, 2025 Oracle and/or its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -248,7 +248,7 @@ You can retrieve a `ConfigValue` using the following `as` methods in `Config` ConfigValue can be used to obtain: * an `Optional` value _from a single node_, -* the `T` value _from a single node_ interpreted as a basic Java type (primitive or simple object) already known to the config system (such as a `boolean` or a `Double`), or +* the `N` value _from a single node_ interpreted as a basic Java type (primitive or simple object) already known to the config system (such as a `boolean` or a `Double`), or * a complex Java type _from a subtree_ of the config tree. + The config system automatically knows how to return `List` and `Map` complex types, and you can provide _config mappers_ to convert a config subtree to whatever diff --git a/docs/src/main/asciidoc/se/fault-tolerance.adoc b/docs/src/main/asciidoc/se/fault-tolerance.adoc index 5ad7e3eca2b..cceeccb6445 100644 --- a/docs/src/main/asciidoc/se/fault-tolerance.adoc +++ b/docs/src/main/asciidoc/se/fault-tolerance.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2020, 2024 Oracle and/or its affiliates. + Copyright (c) 2020, 2025 Oracle and/or its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -93,7 +93,7 @@ The sample code above will retry calls to the supplier `this::retryOnFailure` for up to 3 times with a 100-millisecond delay between them. NOTE: The return type of method `retryOnFailure` in the example above must -be some `T` and the parameter to the retry handler's `invoke` +be some `N` and the parameter to the retry handler's `invoke` method `Supplier`. If the call to the supplier provided completes exceptionally, it will be treated as @@ -130,7 +130,7 @@ we may fall back to the last result obtained from that service at an earlier time. A `Fallback` instance is created by providing a function that takes a `Throwable` -and produces some `T` to be used when the intended method failed to return a value: +and produces some `N` to be used when the intended method failed to return a value: [source,java] ---- @@ -219,7 +219,7 @@ being at maximum capacity. === Asynchronous Asynchronous tasks can be created or forked by using an `Async` instance. A supplier of type -`T` is provided as the argument when invoking this handler. For example: +`N` is provided as the argument when invoking this handler. For example: [source,java] ---- diff --git a/docs/src/main/asciidoc/se/guides/upgrade.adoc b/docs/src/main/asciidoc/se/guides/upgrade.adoc index a04ee634672..816617aab81 100644 --- a/docs/src/main/asciidoc/se/guides/upgrade.adoc +++ b/docs/src/main/asciidoc/se/guides/upgrade.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2020, 2024 Oracle and/or its affiliates. + Copyright (c) 2020, 2025 Oracle and/or its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ include::{rootdir}/includes/guides/upgrade.adoc[] == Getters -Some methods that act as getters of type `T` have been modified to return `Optional`. You will +Some methods that act as getters of type `N` have been modified to return `Optional`. You will need to change your code to handle the `Optional` return type. For example `ServerRequest.spanContext()` in 1.x had a return type of `SpanContext`. In 2.x it has a return type of `Optional`. So if you had code like: diff --git a/docs/src/main/asciidoc/se/sse.adoc b/docs/src/main/asciidoc/se/sse.adoc index 4b5e3789b17..55e631d54ee 100644 --- a/docs/src/main/asciidoc/se/sse.adoc +++ b/docs/src/main/asciidoc/se/sse.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2023, 2024 Oracle and/or its affiliates. + Copyright (c) 2023, 2025 Oracle and/or its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -143,7 +143,7 @@ include::{sourcedir}/se/SseSnippets.java[tag=snippet_4, indent=0] ---- The `SseSource` type defines other methods such as `onOpen`, `onClose` and `onError`. The following example -waits for zero or more string events until the connection is closed. A `CountDownLatch` is a convenient +waits for zero or more string events until the connection is closed. A `MaskingLatch` is a convenient way to asynchronously wait until all the events are received. [source,java] diff --git a/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/RetryTest.java b/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/RetryTest.java index 65d6908cdd0..49113222a96 100644 --- a/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/RetryTest.java +++ b/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/RetryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024 Oracle and/or its affiliates. + * Copyright (c) 2018, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; import java.util.stream.Stream; import io.helidon.microprofile.testing.junit5.AddBean; @@ -36,17 +37,18 @@ */ @AddBean(RetryBean.class) @AddBean(SyntheticRetryBean.class) -public class RetryTest extends FaultToleranceTest { +class RetryTest extends FaultToleranceTest { static Stream createBeans() { return Stream.of( - Arguments.of(newBean(RetryBean.class), "ManagedRetryBean"), - Arguments.of(newNamedBean(SyntheticRetryBean.class), "SyntheticRetryBean")); + Arguments.of((Supplier) () -> newBean(RetryBean.class), "ManagedRetryBean"), + Arguments.of((Supplier) () -> newNamedBean(SyntheticRetryBean.class), "SyntheticRetryBean")); } @ParameterizedTest(name = "{1}") @MethodSource("createBeans") - public void testRetryBean(RetryBean bean, String unused) { + void testRetryBean(Supplier supplier, String unused) { + RetryBean bean = supplier.get(); bean.reset(); assertThat(bean.getInvocations(), is(0)); bean.retry(); @@ -55,7 +57,8 @@ public void testRetryBean(RetryBean bean, String unused) { @ParameterizedTest(name = "{1}") @MethodSource("createBeans") - public void testRetryBeanFallback(RetryBean bean, String unused) { + void testRetryBeanFallback(Supplier supplier, String unused) { + RetryBean bean = supplier.get(); bean.reset(); assertThat(bean.getInvocations(), is(0)); String value = bean.retryWithFallback(); @@ -65,7 +68,8 @@ public void testRetryBeanFallback(RetryBean bean, String unused) { @ParameterizedTest(name = "{1}") @MethodSource("createBeans") - public void testRetryAsync(RetryBean bean, String unused) throws Exception { + void testRetryAsync(Supplier supplier, String unused) throws Exception { + RetryBean bean = supplier.get(); bean.reset(); CompletableFuture future = bean.retryAsync(); future.get(); @@ -74,7 +78,8 @@ public void testRetryAsync(RetryBean bean, String unused) throws Exception { @ParameterizedTest(name = "{1}") @MethodSource("createBeans") - public void testRetryWithDelayAndJitter(RetryBean bean, String unused) throws Exception { + void testRetryWithDelayAndJitter(Supplier supplier, String unused) { + RetryBean bean = supplier.get(); bean.reset(); long millis = System.currentTimeMillis(); bean.retryWithDelayAndJitter(); @@ -84,13 +89,13 @@ public void testRetryWithDelayAndJitter(RetryBean bean, String unused) throws Ex /** * Inspired by a TCK test which makes sure failed executions propagate correctly. * - * @param bean the bean to invoke + * @param supplier supplier of the bean to invoke * @param unused bean name to use for the specific test invocation - * @throws Exception */ @ParameterizedTest(name = "{1}") @MethodSource("createBeans") - public void testRetryWithException(RetryBean bean, String unused) throws Exception { + void testRetryWithException(Supplier supplier, String unused) { + RetryBean bean = supplier.get(); bean.reset(); CompletionStage future = bean.retryWithException(); assertCompleteExceptionally(future.toCompletableFuture(), IOException.class, "Simulated error"); @@ -99,7 +104,8 @@ public void testRetryWithException(RetryBean bean, String unused) throws Excepti @ParameterizedTest(name = "{1}") @MethodSource("createBeans") - public void testRetryCompletionStageWithEventualSuccess(RetryBean bean, String unused) { + void testRetryCompletionStageWithEventualSuccess(Supplier supplier, String unused) { + RetryBean bean = supplier.get(); bean.reset(); assertCompleteOk(bean.retryWithUltimateSuccess(), "success"); assertThat(bean.getInvocations(), is(3)); @@ -107,7 +113,8 @@ public void testRetryCompletionStageWithEventualSuccess(RetryBean bean, String u @ParameterizedTest(name = "{1}") @MethodSource("createBeans") - public void testRetryWithCustomRuntimeException(RetryBean bean, String unused) { + void testRetryWithCustomRuntimeException(Supplier supplier, String unused) { + RetryBean bean = supplier.get(); bean.reset(); assertThat(bean.getInvocations(), is(0)); assertCompleteOk(bean.retryOnCustomRuntimeException(), "success"); diff --git a/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/LoadBalancedCoordinatorTest.java b/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/LoadBalancedCoordinatorTest.java index fe31cc26999..6fe000cca47 100644 --- a/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/LoadBalancedCoordinatorTest.java +++ b/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/LoadBalancedCoordinatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. + * Copyright (c) 2021, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,6 +52,7 @@ import io.helidon.microprofile.testing.junit5.AddBean; import io.helidon.microprofile.testing.junit5.AddConfig; import io.helidon.microprofile.testing.junit5.AddExtension; +import io.helidon.microprofile.testing.junit5.AddJaxRs; import io.helidon.microprofile.testing.junit5.DisableDiscovery; import io.helidon.microprofile.testing.junit5.HelidonTest; import io.helidon.webclient.http1.Http1Client; @@ -66,11 +67,11 @@ import jakarta.ws.rs.core.UriBuilder; import org.eclipse.microprofile.lra.annotation.LRAStatus; import org.eclipse.microprofile.lra.annotation.ParticipantStatus; -import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; import org.hamcrest.core.AnyOf; import org.hamcrest.core.IsNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER; import static org.hamcrest.MatcherAssert.assertThat; @@ -88,11 +89,9 @@ */ @HelidonTest @DisableDiscovery +@AddJaxRs // Helidon MP @AddExtension(ConfigCdiExtension.class) -@AddExtension(ServerCdiExtension.class) -@AddExtension(JaxRsCdiExtension.class) -@AddExtension(CdiComponentProvider.class) // LRA client @AddExtension(LraCdiExtension.class) // resources @@ -126,6 +125,7 @@ @AddConfig(key = "server.sockets.2.name", value = CoordinatorClusterDeploymentService.COORDINATOR_B_NAME) @AddConfig(key = "server.sockets.2.port", value = "0") @AddConfig(key = "server.sockets.2.bind-address", value = "localhost") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class LoadBalancedCoordinatorTest { private static final System.Logger LOGGER = System.getLogger(LoadBalancedCoordinatorTest.class.getName()); diff --git a/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraDisabledDiscoveryResourceTest.java b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraDisabledDiscoveryResourceTest.java index 77607fc7005..43ddf662010 100644 --- a/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraDisabledDiscoveryResourceTest.java +++ b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraDisabledDiscoveryResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ import org.eclipse.microprofile.lra.annotation.LRAStatus; import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; @@ -54,6 +55,7 @@ @AddBean(TestLraCoordinator.class) @AddExtension(LraCdiExtension.class) @AddExtension(ConfigCdiExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) @Path("/test/internal") public class LraDisabledDiscoveryResourceTest { diff --git a/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraMultiPortTest.java b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraMultiPortTest.java index a0487365e1a..a5c63560a2b 100644 --- a/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraMultiPortTest.java +++ b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraMultiPortTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ import org.eclipse.microprofile.lra.annotation.LRAStatus; import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; @@ -55,6 +56,7 @@ @AddConfig(key = "server.sockets.0.port", value = "0") @AddConfig(key = "server.sockets.0.bind-address", value = "localhost") @AddBean(TestLraCoordinator.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) @Path("/test/multi-port") public class LraMultiPortTest { diff --git a/microprofile/messaging/core/src/test/java/io/helidon/microprofile/messaging/MockConnectorTest.java b/microprofile/messaging/core/src/test/java/io/helidon/microprofile/messaging/MockConnectorTest.java index 83ece48a143..b4f1885b987 100644 --- a/microprofile/messaging/core/src/test/java/io/helidon/microprofile/messaging/MockConnectorTest.java +++ b/microprofile/messaging/core/src/test/java/io/helidon/microprofile/messaging/MockConnectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import org.eclipse.microprofile.reactive.messaging.Outgoing; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import static io.helidon.messaging.connectors.mock.MockConnector.CONNECTOR_NAME; import static org.eclipse.microprofile.reactive.messaging.spi.ConnectorFactory.INCOMING_PREFIX; @@ -54,6 +55,7 @@ @AddConfig(key = INCOMING_PREFIX + "test-channel-5.mock-data-type", value = "java.lang.Long") @AddConfig(key = INCOMING_PREFIX + "test-channel-5.mock-data", value = "9,10,11,12") @AddConfig(key = OUTGOING_PREFIX + "test-channel-6.connector", value = CONNECTOR_NAME) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MockConnectorTest { private static final Duration TIMEOUT = Duration.ofSeconds(5); diff --git a/microprofile/scheduling/src/test/java/io/helidon/microprofile/scheduling/SchedulingTest.java b/microprofile/scheduling/src/test/java/io/helidon/microprofile/scheduling/SchedulingTest.java index 16673f81c1f..5c30d620968 100644 --- a/microprofile/scheduling/src/test/java/io/helidon/microprofile/scheduling/SchedulingTest.java +++ b/microprofile/scheduling/src/test/java/io/helidon/microprofile/scheduling/SchedulingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. + * Copyright (c) 2021, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger; import io.helidon.microprofile.testing.junit5.AddBean; -import io.helidon.microprofile.testing.junit5.AddBeans; import io.helidon.microprofile.testing.junit5.AddExtension; -import io.helidon.microprofile.testing.junit5.AddExtensions; import io.helidon.microprofile.testing.junit5.Configuration; import io.helidon.microprofile.testing.junit5.DisableDiscovery; import io.helidon.microprofile.testing.junit5.HelidonTest; @@ -35,6 +33,7 @@ import jakarta.inject.Inject; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -43,13 +42,10 @@ @HelidonTest @DisableDiscovery -@AddBeans({ - @AddBean(ScheduledBean.class) -}) -@AddExtensions({ - @AddExtension(SchedulingCdiExtension.class), -}) +@AddBean(ScheduledBean.class) +@AddExtension(SchedulingCdiExtension.class) @Configuration(configSources = "test.properties") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class SchedulingTest { static final long TWO_SEC_MILLIS = 2 * 1000L; diff --git a/microprofile/testing/junit5/pom.xml b/microprofile/testing/junit5/pom.xml index f4b47e981aa..0856b92bf04 100644 --- a/microprofile/testing/junit5/pom.xml +++ b/microprofile/testing/junit5/pom.xml @@ -33,10 +33,14 @@ + + io.helidon.microprofile.testing + helidon-microprofile-testing + io.helidon.microprofile.server helidon-microprofile-server - true + provided io.helidon.common.testing @@ -50,7 +54,7 @@ org.glassfish.jersey.ext.cdi jersey-weld2-se - true + provided org.junit.jupiter @@ -67,9 +71,8 @@ org.hamcrest - hamcrest-core + hamcrest-all test - diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddBean.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddBean.java index fa6ebbee729..9e4342a4169 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddBean.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,37 +17,45 @@ import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import jakarta.enterprise.context.ApplicationScoped; - /** - * Add a bean. - * This is intended for test sources where we do not want to add {@code beans.xml} as this would add - * all test classes as beans. - * The bean will be added by default with {@link jakarta.enterprise.context.ApplicationScoped}. - * The class will be instantiated using CDI and will be available for injection into test classes and other beans. + * Add a CDI bean to the container. + *

* This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + *

+ * The bean scope is defined as follows: + *

    + *
  • If a scope is set with {@link #value()}, it overrides any scope defined on the bean
  • + *
  • Otherwise, the scope defined on the bean is used
  • + *
  • If the bean does not define a scope, {@link jakarta.enterprise.context.ApplicationScoped ApplicationScoped} + * is used
  • + *
+ * @deprecated Use {@link io.helidon.microprofile.testing.AddBean} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Repeatable(AddBeans.class) +@Deprecated(since = "4.2.0") public @interface AddBean { /** - * Class of the bean. - * @return the class of a bean + * The bean class. + * + * @return bean class */ Class value(); /** - * Scope of the bean. - * Only {@link jakarta.inject.Singleton}, {@link jakarta.enterprise.context.ApplicationScoped} - * and {@link jakarta.enterprise.context.RequestScoped} scopes are supported. + * Override the bean scope. * - * @return scope of the bean + * @return scope class */ - Class scope() default ApplicationScoped.class; + Class scope() default Annotation.class; } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddBeans.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddBeans.java index 77b9f845dbd..f8b9e1cf7dc 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddBeans.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddBeans.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,34 @@ package io.helidon.microprofile.testing.junit5; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * A repeatable container for {@link io.helidon.microprofile.testing.junit5.AddBean}. - * No need to use this annotation, just repeat {@link io.helidon.microprofile.testing.junit5.AddBean} annotation - * on test class. + * A repeatable container for {@link AddBean}. + *

+ * This annotation is optional, you can instead repeat {@link AddBean}. + *

+ * E.g. + *

+ * @AddBean(FooBean.class)
+ * @AddBean(BarBean.class)
+ * class MyTest {
+ * }
+ * 
+ * @deprecated Use {@link io.helidon.microprofile.testing.AddBeans} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) +@Deprecated(since = "4.2.0") public @interface AddBeans { /** - * Beans to be added. - * @return add bean annotations + * Get the contained annotations. + * + * @return annotations */ AddBean[] value(); } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfig.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfig.java index a1c5c4da63a..3cb9aa8c6cb 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfig.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,27 +16,40 @@ package io.helidon.microprofile.testing.junit5; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Add a configuration key/value pair to MicroProfile configuration. + * Add a configuration key/value pair to the {@link Configuration#useExisting() synthetic test configuration}. + *

* This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * + * @see AddConfigs + * @see AddConfigBlock + * @see Configuration + * @deprecated Use {@link io.helidon.microprofile.testing.AddConfig} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Repeatable(AddConfigs.class) +@Deprecated(since = "4.2.0") public @interface AddConfig { /** * Configuration property key. + * * @return key */ String key(); /** * Configuration property value. + * * @return value */ String value(); diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfigBlock.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfigBlock.java index 595cb8bed1d..8a952079719 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfigBlock.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfigBlock.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,28 +23,35 @@ import java.lang.annotation.Target; /** - * Defines the configuration as a String in {@link #value()} for the - * given type. + * Add a configuration fragment to the {@link Configuration#useExisting() synthetic test configuration}. + *

+ * This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * + * @see AddConfig + * @see AddConfigs + * @see Configuration + * @deprecated Use {@link io.helidon.microprofile.testing.AddConfigBlock} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) -@Inherited +@Deprecated(since = "4.2.0") public @interface AddConfigBlock { - /** - * Specifies the format type of the {@link #value()}. - * - * It defaults to 'properties'. + * Specifies the configuration format. + *

+ * The default format is 'properties' * * @return the supported type */ String type() default "properties"; /** - * Configuration value. + * Configuration fragment. * - * @return String with value. + * @return fragment */ String value(); - } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfigs.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfigs.java index 82553a8dd0f..4fbdb280e7e 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfigs.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddConfigs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,22 +16,37 @@ package io.helidon.microprofile.testing.junit5; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * A repeatable container for {@link io.helidon.microprofile.testing.junit5.AddConfig}. - * No need to use this annotation, just repeat {@link io.helidon.microprofile.testing.junit5.AddConfig} annotation - * on test class. + * A repeatable container for {@link AddConfig}. + *

+ * This annotation is optional, you can instead repeat {@link AddConfig}. + *

+ * E.g. + *

+ * @AddConfig(key="foo", value="1")
+ * @AddConfig(key="bar", value="2")
+ * class MyTest {
+ * }
+ * 
+ * + * @see AddConfig + * @see Configuration + * @deprecated Use {@link io.helidon.microprofile.testing.AddConfigs} instead */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) +@Inherited +@Deprecated(since = "4.2.0") public @interface AddConfigs { /** - * Configuration properties to be added. + * Get the contained annotations. * - * @return properties + * @return annotations */ AddConfig[] value(); } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddExtension.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddExtension.java index c81db4b784b..850440b812f 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddExtension.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.microprofile.testing.junit5; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -24,15 +25,22 @@ import jakarta.enterprise.inject.spi.Extension; /** - * Add a CDI extension to the test container. + * Add a CDI extension to the container. + *

* This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * @deprecated Use {@link io.helidon.microprofile.testing.AddExtension} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Repeatable(AddExtensions.class) +@Deprecated(since = "4.2.0") public @interface AddExtension { /** * Class of the extension to add. The class must be public. + * * @return extension class. */ Class value(); diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddExtensions.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddExtensions.java index 565f7f0d2f6..14b5fa99687 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddExtensions.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddExtensions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,34 @@ package io.helidon.microprofile.testing.junit5; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * A repeatable container for {@link io.helidon.microprofile.testing.junit5.AddExtension}. - * No need to use this annotation, just repeat {@link io.helidon.microprofile.testing.junit5.AddExtension} annotation - * on test class. + * A repeatable container for {@link AddExtension}. + *

+ * This annotation is optional, you can instead repeat {@link AddExtension}. + *

+ * E.g. + *

+ * @AddExtension(FooExtension.class)
+ * @AddExtension(BarExtension.class)
+ * class MyTest {
+ * }
+ * 
+ * @deprecated Use {@link io.helidon.microprofile.testing.AddExtensions} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) +@Deprecated(since = "4.2.0") public @interface AddExtensions { /** - * Extensions to be added. - * @return extensions + * Get the contained annotations. + * + * @return annotations */ AddExtension[] value(); } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddJaxRs.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddJaxRs.java index 1f776358f0d..6311d4e82ee 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddJaxRs.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AddJaxRs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,32 @@ package io.helidon.microprofile.testing.junit5; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import io.helidon.microprofile.server.JaxRsCdiExtension; +import io.helidon.microprofile.server.ServerCdiExtension; + +import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; +import org.glassfish.jersey.ext.cdi1x.internal.ProcessAllAnnotatedTypes; +import org.glassfish.jersey.weld.se.WeldRequestScope; + /** - * Add JaxRS support for Request-scoped beans. + * Add JAX-RS (Jersey) support. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * @deprecated Use {@link io.helidon.microprofile.testing.AddJaxRs} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ElementType.TYPE, ElementType.METHOD}) +@AddExtension(ProcessAllAnnotatedTypes.class) +@AddExtension(ServerCdiExtension.class) +@AddExtension(JaxRsCdiExtension.class) +@AddExtension(CdiComponentProvider.class) +@AddBean(WeldRequestScope.class) +@Deprecated(since = "4.2.0") public @interface AddJaxRs { } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AfterStop.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AfterStop.java index ae3225d462a..80fdc972207 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AfterStop.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/AfterStop.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,25 @@ package io.helidon.microprofile.testing.junit5; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Mark a static method to be executed after the container is stopped. + *

+ * E.g. + *

+ * @AfterStop
+ * static void afterStop() {
+ *     // ...
+ * }
+ * @deprecated Use {@link io.helidon.microprofile.testing.AfterStop} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) +@Deprecated(since = "4.2.0") public @interface AfterStop { } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/Configuration.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/Configuration.java index f75de831b10..7493a480207 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/Configuration.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/Configuration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,38 +23,55 @@ import java.lang.annotation.Target; /** - * Additional configuration of config itself. + * General setting for the test configuration. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * + * @see AddConfig + * @see AddConfigs + * @see AddConfigBlock + * @deprecated Use {@link io.helidon.microprofile.testing.Configuration} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) -@Inherited +@Deprecated(since = "4.2.0") public @interface Configuration { /** - * If set to {@code true}, the existing (or default) MicroProfile configuration would be used. In this case it is - * important to set property {@code mp.initializer.allow=true} in order CDI container to start, when used with - * {@link HelidonTest}. - * By default uses a configuration constructed using all {@link io.helidon.microprofile.testing.junit5.AddConfig} - * annotations and {@link #configSources()}. - * When set to false and a {@link org.junit.jupiter.api.BeforeAll} method registers a custom configuration - * with {@link org.eclipse.microprofile.config.spi.ConfigProviderResolver}, the result is undefined, though - * tests have shown that the registered config may be used (as BeforeAll ordering is undefined by - * JUnit, it may be called after our extension) + * If set to {@code false}, the synthetic test configuration is used. + *

+ * The synthetic test configuration is expressed with the following: + *

    + *
  • {@link #configSources()}
  • + *
  • {@link #profile()}
  • + *
  • {@link AddConfig}
  • + *
  • {@link AddConfigs}
  • + *
  • {@link AddConfigBlock}
  • + *
+ *

+ * If set to {@code true}, only the existing (or default) MicroProfile configuration is used + * and the annotations listed previously are ignored. + *

+ * You can use {@link org.eclipse.microprofile.config.spi.ConfigProviderResolver ConfigProviderResolver} to define + * the configuration programmatically before the CDI container starts. * - * @return whether to use existing (or default) configuration, or customized one + * @return whether to use existing (or default) configuration */ boolean useExisting() default false; /** - * Class path properties config sources to add to configuration of this test class or method. + * Class-path resources to add as config sources to the synthetic test configuration. * - * @return config sources to add + * @return config sources */ String[] configSources() default {}; /** * Configuration profile. + *

+ * The default profile is 'test' * - * @return String with default value "test". + * @return profile */ String profile() default "test"; } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/ContextHelper.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/ContextHelper.java new file mode 100644 index 00000000000..e3563d69917 --- /dev/null +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/ContextHelper.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.junit5; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; + +/** + * JUnit context helper. + */ +class ContextHelper { + + private static final Namespace NAMESPACE = Namespace.create(HelidonJunitExtension.class); + + private ContextHelper() { + } + + /** + * Get a store. + * + * @param context context + * @param methods methods + * @return store + */ + static ExtensionContext.Store store(ExtensionContext context, Method... methods) { + Namespace ns; + if (methods.length > 0) { + ns = NAMESPACE.append(Arrays.stream(methods) + .map(Method::getName) + .toArray()); + } else { + ns = NAMESPACE; + } + return context.getStore(ns); + } + + /** + * Get an object from the given store. + * + * @param store store + * @param type type + * @param name name + * @param object type + * @return optional + */ + static Optional lookup(ExtensionContext.Store store, Class type, String name) { + return Optional.ofNullable(store.get(name, type)); + } + + /** + * Get the class context for a given context. + * + * @param context context + * @return class context + */ + static ExtensionContext classContext(ExtensionContext context) { + ExtensionContext c = context; + while (!c.getElement().map(Class.class::isInstance).orElse(false)) { + c = c.getParent().orElseThrow(); + } + return c; + } +} diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/DisableDiscovery.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/DisableDiscovery.java index 08c6c2b7b94..9916cd1cfdf 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/DisableDiscovery.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/DisableDiscovery.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,30 +23,39 @@ import java.lang.annotation.Target; /** - * Whether discovery is automated or disabled. If discovery is desired, do not annotate test - * class with this annotation. + * Disables CDI discovery. *

- * When discovery is enabled, the whole classpath is scanned for bean archives (jar files containing - * {@code META-INF/beans.xml}) and all beans and extensions are added automatically. + * If discovery is desired, do not annotate test class with this annotation. *

- * When discovery is disabled, CDI would only contain the CDI implementation itself and beans and extensions added - * through annotations {@link io.helidon.microprofile.testing.junit5.AddBean} and - * {@link io.helidon.microprofile.testing.junit5.AddExtension} - * - * If discovery is disabled on class level and desired on method level, - * the value can be set to {@code false}. + * If used on a method, the container will be reset regardless of the test lifecycle. + *

+ * When disabling discovery, you are responsible for adding the beans and extensions needed to activate the features you need. + * You can use the following annotations to do that: + *

    + *
  • {@link AddBean} to add CDI beans
  • + *
  • {@link AddExtension} to add CDI extensions
  • + *
  • {@link AddJaxRs} a short-hand to add JAX-RS (Jersey)
  • + *
+ *

+ * See also the following "core" CDI extensions: + *

    + *
  • {@link io.helidon.microprofile.server.ServerCdiExtension ServerCdiExtension} optional if using {@link AddJaxRs}
  • + *
  • {@link io.helidon.microprofile.server.JaxRsCdiExtension JaxRsCdiExtension} optional if using {@link AddJaxRs}
  • + *
  • {@link io.helidon.microprofile.config.ConfigCdiExtension ConfigCdiExtension}
  • + *
*/ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) -@Inherited +@Deprecated(since = "4.2.0") public @interface DisableDiscovery { /** - * By default if you annotate a class or a method, discovery gets disabled. + * By default, if you annotate a class or a method, discovery gets disabled. * If you want to override configuration on method to differ from class, you * can configure the value to {@code false}, effectively enabling discovery. * * @return whether to disable discovery ({@code true}), or enable it ({@code false}). If this - * annotation is not present, discovery is enabled + * annotation is not present, discovery is enabled */ boolean value() default true; } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonJunitExtension.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonJunitExtension.java index 9632943ab6c..c2eb96562e5 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonJunitExtension.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonJunitExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,792 +16,200 @@ package io.helidon.microprofile.testing.junit5; -import java.io.IOException; -import java.io.Serial; -import java.io.StringReader; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import io.helidon.common.testing.virtualthreads.PinningRecorder; -import io.helidon.config.mp.MpConfigSources; -import io.helidon.microprofile.server.JaxRsCdiExtension; -import io.helidon.microprofile.server.ServerCdiExtension; +import io.helidon.microprofile.testing.HelidonTestInfo.ClassInfo; +import io.helidon.microprofile.testing.HelidonTestInfo.MethodInfo; +import io.helidon.microprofile.testing.HelidonTestScope; +import io.helidon.microprofile.testing.Instrumented; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.Dependent; -import jakarta.enterprise.context.RequestScoped; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.se.SeContainer; -import jakarta.enterprise.inject.se.SeContainerInitializer; -import jakarta.enterprise.inject.spi.AfterBeanDiscovery; -import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; -import jakarta.enterprise.inject.spi.CDI; -import jakarta.enterprise.inject.spi.Extension; -import jakarta.enterprise.inject.spi.InjectionPoint; -import jakarta.enterprise.inject.spi.ProcessInjectionPoint; -import jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator; -import jakarta.enterprise.util.AnnotationLiteral; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.WebTarget; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.spi.ConfigBuilder; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; - +import org.junit.jupiter.api.extension.TestInstanceFactory; +import org.junit.jupiter.api.extension.TestInstanceFactoryContext; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import static io.helidon.microprofile.testing.HelidonTestInfo.classInfo; +import static io.helidon.microprofile.testing.HelidonTestInfo.methodInfo; +import static io.helidon.microprofile.testing.Instrumented.instrument; +import static io.helidon.microprofile.testing.junit5.ContextHelper.classContext; +import static io.helidon.microprofile.testing.junit5.ContextHelper.lookup; +import static io.helidon.microprofile.testing.junit5.ContextHelper.store; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; /** - * Junit5 extension to support Helidon CDI container in tests. + * A JUnit5 extension that integrates CDI with JUnit to support Helidon MP. + *

+ * This extension starts a CDI container and adds the test class as a bean with support for injection. The test class uses + * a CDI scope that follows the test lifecycle as defined by {@link org.junit.jupiter.api.TestInstance TestInstance}. + *

+ * The container is started lazily during test execution to ensure that it is started after all other extensions. + *

+ * The container can be customized with the following annotations: + *

    + *
  • {@link HelidonTest#resetPerTest()} force a new CDI container per test
  • + *
  • {@link io.helidon.microprofile.testing.DisableDiscovery} disables CDI discovery
  • + *
  • {@link io.helidon.microprofile.testing.AddBean} add CDI beans
  • + *
  • {@link io.helidon.microprofile.testing.AddExtension} add CDI extension
  • + *
  • {@link io.helidon.microprofile.testing.AddJaxRs} add JAX-RS (Jersey)
  • + *
+ *

+ * The configuration can be customized with the following annotations: + *

    + *
  • {@link io.helidon.microprofile.testing.Configuration} global setting for MicroProfile configuration
  • + *
  • {@link io.helidon.microprofile.testing.AddConfig} declarative key/value pair configuration
  • + *
  • {@link io.helidon.microprofile.testing.AddConfigBlock} declarative fragment configuration
  • + *
  • {@link io.helidon.microprofile.testing.AddConfigSource} programmatic configuration
  • + *
+ *

+ * See also {@link io.helidon.microprofile.testing.Socket}, a CDI qualifier to inject JAX-RS client or URI. + *

+ * The container is created per test class by default, unless + * {@link HelidonTest#resetPerTest()} is {@code true}, in + * which case the container is created per test method. + *

+ * The container and the configuration can be customized per method regardless of the value of + * {@link HelidonTest#resetPerTest()}. The container will be reset accordingly. + *

+ * It is not recommended to provide a {@code beans.xml} along the test classes, as it would combine beans from all tests. + * Instead, you should use {@link io.helidon.microprofile.testing.AddBean} to specify the beans per test or method. + * + * @see HelidonTest */ -class HelidonJunitExtension implements BeforeAllCallback, - AfterAllCallback, - BeforeEachCallback, - AfterEachCallback, - InvocationInterceptor, - ParameterResolver { - private static final Set> HELIDON_TEST_ANNOTATIONS = - Set.of(AddBean.class, AddConfig.class, AddExtension.class, Configuration.class, - AddJaxRs.class, AddConfigBlock.class); - private static final Map, Annotation> BEAN_DEFINING = new HashMap<>(); - - static { - BEAN_DEFINING.put(ApplicationScoped.class, ApplicationScoped.Literal.INSTANCE); - BEAN_DEFINING.put(Singleton.class, ApplicationScoped.Literal.INSTANCE); - BEAN_DEFINING.put(RequestScoped.class, RequestScoped.Literal.INSTANCE); - BEAN_DEFINING.put(Dependent.class, Dependent.Literal.INSTANCE); - } +public class HelidonJunitExtension implements BeforeEachCallback, + TestInstanceFactory, + InvocationInterceptor, + ParameterResolver { - private final List classLevelExtensions = new ArrayList<>(); - private final List classLevelBeans = new ArrayList<>(); - private final ConfigMeta classLevelConfigMeta = new ConfigMeta(); - private boolean classLevelDisableDiscovery = false; - private boolean resetPerTest; - - private Class testClass; - private ConfigProviderResolver configProviderResolver; - private Config config; - private SeContainer container; - private PinningRecorder pinningRecorder; - - @SuppressWarnings("unchecked") @Override - public void beforeAll(ExtensionContext context) { - testClass = context.getRequiredTestClass(); - - List metaAnnotations = extractMetaAnnotations(testClass); - - AddConfig[] configs = getAnnotations(testClass, AddConfig.class, metaAnnotations); - classLevelConfigMeta.addConfig(configs); - classLevelConfigMeta.configuration(getAnnotation(testClass, Configuration.class, metaAnnotations)); - classLevelConfigMeta.addConfigBlock(getAnnotation(testClass, AddConfigBlock.class, metaAnnotations)); - configProviderResolver = ConfigProviderResolver.instance(); - - AddExtension[] extensions = getAnnotations(testClass, AddExtension.class, metaAnnotations); - classLevelExtensions.addAll(Arrays.asList(extensions)); - - AddBean[] beans = getAnnotations(testClass, AddBean.class, metaAnnotations); - classLevelBeans.addAll(Arrays.asList(beans)); - - HelidonTest testAnnot = testClass.getAnnotation(HelidonTest.class); - if (testAnnot != null) { - resetPerTest = testAnnot.resetPerTest(); - if (testAnnot.pinningDetection()) { - pinningRecorder = PinningRecorder.create(); - pinningRecorder.record(Duration.ofMillis(testAnnot.pinningThreshold())); - } - } - - - DisableDiscovery discovery = getAnnotation(testClass, DisableDiscovery.class, metaAnnotations); - if (discovery != null) { - classLevelDisableDiscovery = discovery.value(); - } - - if (resetPerTest) { - validatePerTest(); - - return; - } - validatePerClass(); - - // add beans when using JaxRS - AddJaxRs addJaxRsAnnotation = getAnnotation(testClass, AddJaxRs.class, metaAnnotations); - if (addJaxRsAnnotation != null){ - classLevelExtensions.add(ProcessAllAnnotatedTypesLiteral.INSTANCE); - classLevelExtensions.add(ServerCdiExtensionLiteral.INSTANCE); - classLevelExtensions.add(JaxRsCdiExtensionLiteral.INSTANCE); - classLevelExtensions.add(CdiComponentProviderLiteral.INSTANCE); - classLevelBeans.add(WeldRequestScopeLiteral.INSTANCE); - } - - configure(classLevelConfigMeta); - - if (!classLevelConfigMeta.useExisting) { - // the container startup is delayed in case we `useExisting`, so the is first set up by the user - // when we do not need to `useExisting`, we want to start early, so parameterized test method sources that use CDI - // can work - startContainer(classLevelBeans, classLevelExtensions, classLevelDisableDiscovery); - } - } - - private List extractMetaAnnotations(Class testClass) { - Annotation[] testAnnotations = testClass.getAnnotations(); - for (Annotation testAnnotation : testAnnotations) { - List annotations = List.of(testAnnotation.annotationType().getAnnotations()); - List> annotationsClass = annotations.stream() - .map(Annotation::annotationType).collect(Collectors.toList()); - if (!Collections.disjoint(HELIDON_TEST_ANNOTATIONS, annotationsClass)) { - // Contains at least one of HELIDON_TEST_ANNOTATIONS - return annotations; - } - } - return List.of(); - } - - private T getAnnotation(Class testClass, Class annotClass, - List metaAnnotations) { - T annotation = testClass.getAnnotation(annotClass); - if (annotation == null) { - List byType = annotationsByType(annotClass, metaAnnotations); - if (!byType.isEmpty()) { - annotation = byType.get(0); - } - } - return annotation; - } - - @SuppressWarnings("unchecked") - private T[] getAnnotations(Class testClass, Class annotClass, - List metaAnnotations) { - // inherited does not help, as it only returns annot from superclass if - // child has none - T[] directAnnotations = testClass.getAnnotationsByType(annotClass); - - List allAnnotations = new ArrayList<>(List.of(directAnnotations)); - // Include meta annotations - allAnnotations.addAll(annotationsByType(annotClass, metaAnnotations)); - - Class superClass = testClass.getSuperclass(); - while (superClass != null) { - directAnnotations = superClass.getAnnotationsByType(annotClass); - allAnnotations.addAll(List.of(directAnnotations)); - superClass = superClass.getSuperclass(); - } - - Object result = Array.newInstance(annotClass, allAnnotations.size()); - for (int i = 0; i < allAnnotations.size(); i++) { - Array.set(result, i, allAnnotations.get(i)); - } - - return (T[]) result; - } - - private List annotationsByType(Class annotClass, List metaAnnotations) { - List byType = new ArrayList<>(); - for (Annotation annotation : metaAnnotations) { - if (annotation.annotationType() == annotClass) { - byType.add((T) annotation); - } - } - return byType; + public Object createTestInstance(TestInstanceFactoryContext fc, ExtensionContext context) { + // Instrument the test class + // Use a proxy to start the container lazily + Class testClass = instrument(context.getRequiredTestClass(), List.of(), List.of(), + (type, method) -> { + // class context store specific to the intercepted method + Store store = store(context, method); + return requiredContainer(store).resolveInstance(type); + }); + return Instrumented.allocateInstance(testClass); } @Override - public void beforeEach(ExtensionContext context) throws Exception { - if (resetPerTest) { - Method method = context.getRequiredTestMethod(); - AddConfig[] configs = method.getAnnotationsByType(AddConfig.class); - ConfigMeta methodLevelConfigMeta = classLevelConfigMeta.nextMethod(); - methodLevelConfigMeta.addConfig(configs); - methodLevelConfigMeta.configuration(method.getAnnotation(Configuration.class)); - methodLevelConfigMeta.addConfigBlock(method.getAnnotation(AddConfigBlock.class)); - - configure(methodLevelConfigMeta); - - List methodLevelExtensions = new ArrayList<>(classLevelExtensions); - List methodLevelBeans = new ArrayList<>(classLevelBeans); - boolean methodLevelDisableDiscovery = classLevelDisableDiscovery; + public void interceptBeforeEachMethod(Invocation invocation, + ReflectiveInvocationContext ic, + ExtensionContext context) throws Throwable { - AddExtension[] extensions = method.getAnnotationsByType(AddExtension.class); - methodLevelExtensions.addAll(Arrays.asList(extensions)); - - AddBean[] beans = method.getAnnotationsByType(AddBean.class); - methodLevelBeans.addAll(Arrays.asList(beans)); - - DisableDiscovery discovery = method.getAnnotation(DisableDiscovery.class); - if (discovery != null) { - methodLevelDisableDiscovery = discovery.value(); - } - - startContainer(methodLevelBeans, methodLevelExtensions, methodLevelDisableDiscovery); - } + intercept(invocation, ic, context); } @Override - public void afterEach(ExtensionContext context) throws Exception { - if (resetPerTest) { - releaseConfig(); - stopContainer(); - } -// pinningRecorder.checkAndThrow(); - } - - private void validatePerClass() { - Method[] methods = testClass.getMethods(); - for (Method method : methods) { - if (method.getAnnotation(Test.class) != null) { - // a test method - if (hasHelidonTestAnnotation(method)) { - throw new RuntimeException("When a class is annotated with @HelidonTest, " - + "there is a single CDI container used to invoke all " - + "test methods on the class. Method " + method - + " has an annotation that modifies container behavior."); - } - } - } - - methods = testClass.getDeclaredMethods(); - for (Method method : methods) { - if (method.getAnnotation(Test.class) != null) { - // a test method - if (hasHelidonTestAnnotation(method)) { - throw new RuntimeException("When a class is annotated with @HelidonTest, " - + "there is a single CDI container used to invoke all " - + "test methods on the class. Method " + method - + " has an annotation that modifies container behavior."); - } - } - } + public void interceptAfterEachMethod(Invocation invocation, + ReflectiveInvocationContext ic, + ExtensionContext context) throws Throwable { - AddJaxRs addJaxRsAnnotation = testClass.getAnnotation(AddJaxRs.class); - if (addJaxRsAnnotation != null){ - if (testClass.getAnnotation(DisableDiscovery.class) == null){ - throw new RuntimeException("@AddJaxRs annotation should be used only with @DisableDiscovery annotation."); - } - } - } - - private boolean hasHelidonTestAnnotation(AnnotatedElement element) { - for (Class aClass : HELIDON_TEST_ANNOTATIONS) { - if (element.getAnnotation(aClass) != null) { - return true; - } - } - return false; + intercept(invocation, ic, context); } - private void validatePerTest() { - Constructor[] constructors = testClass.getConstructors(); - if (constructors.length > 1) { - throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," - + " the class must have only a single no-arg constructor"); - } - if (constructors.length == 1) { - Constructor c = constructors[0]; - if (c.getParameterCount() > 0) { - throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," - + " the class must have a no-arg constructor"); - } - } + @Override + public void beforeEach(ExtensionContext context) { + Method testMethod = context.getRequiredTestMethod(); + Class testClass = context.getRequiredTestClass(); - Field[] fields = testClass.getFields(); - for (Field field : fields) { - if (field.getAnnotation(Inject.class) != null) { - throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," - + " injection into fields or constructor is not supported, as each" - + " test method uses a different CDI container. Field " + field - + " is annotated with @Inject"); - } - } + ClassInfo classInfo = classInfo(testClass, HelidonTestDescriptorImpl::new); + MethodInfo methodInfo = methodInfo(testMethod, classInfo, HelidonTestDescriptorImpl::new); - fields = testClass.getDeclaredFields(); - for (Field field : fields) { - if (field.getAnnotation(Inject.class) != null) { - throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," - + " injection into fields or constructor is not supported, as each" - + " test method uses a different CDI container. Field " + field - + " is annotated with @Inject"); - } - } - } + ExtensionContext classContext = classContext(context); + Store classStore = store(classContext); + HelidonTestContainerImpl container = container(classStore); - private void configure(ConfigMeta configMeta) { - if (config != null) { - configProviderResolver.releaseConfig(config); - } - if (!configMeta.useExisting) { - // only create a custom configuration if not provided by test method/class - // prepare configuration - ConfigBuilder builder = configProviderResolver.getBuilder(); + if (context.getExecutionMode() == ExecutionMode.SAME_THREAD + && container != null && !container.closed() + && methodInfo.requiresReset()) { - configMeta.additionalSources.forEach(it -> { - String fileName = it.trim(); - int idx = fileName.lastIndexOf('.'); - String type = idx > -1 ? fileName.substring(idx + 1) : "properties"; - try { - Enumeration urls = Thread.currentThread().getContextClassLoader().getResources(fileName); - urls.asIterator().forEachRemaining(url -> builder.withSources(MpConfigSources.create(type, url))); - } catch (IOException e) { - throw new IllegalStateException("Failed to read \"" + fileName + "\" from classpath", e); - } - }); - if (configMeta.type != null && configMeta.block != null) { - builder.withSources(MpConfigSources.create(configMeta.type, new StringReader(configMeta.block))); - } - config = builder - .withSources(MpConfigSources.create(configMeta.additionalKeys)) - .addDefaultSources() - .addDiscoveredSources() - .addDiscoveredConverters() - .build(); - configProviderResolver.registerConfig(config, Thread.currentThread().getContextClassLoader()); - } - } - private void releaseConfig() { - if (configProviderResolver != null && config != null) { - configProviderResolver.releaseConfig(config); - config = null; - } - } - - @SuppressWarnings("unchecked") - private void startContainer(List beanAnnotations, - List extensionAnnotations, - boolean disableDiscovery) { - - // now let's prepare the CDI bootstrapping - SeContainerInitializer initializer = SeContainerInitializer.newInstance(); - - if (disableDiscovery) { - initializer.disableDiscovery(); + // close the "class container" only for sequential executions + // parallel & requireReset use multiple containers + container.close(); } - initializer.addExtensions(new AddBeansExtension(testClass, beanAnnotations)); - - for (AddExtension addExtension : extensionAnnotations) { - Class extensionClass = addExtension.value(); - if (Modifier.isPublic(extensionClass.getModifiers())) { - initializer.addExtensions(addExtension.value()); + if (container == null || container.closed()) { + Store methodStore = store(context); + Lifecycle lifecycle = context.getTestInstanceLifecycle().orElse(PER_METHOD); + HelidonTestScope scope; + if (lifecycle == Lifecycle.PER_CLASS) { + scope = HelidonTestScope.ofContainer(); } else { - throw new IllegalArgumentException("Extension classes must be public, but " + extensionClass - .getName() + " is not"); + scope = HelidonTestScope.ofThread(); + // put the scope in the method context store to auto-close + methodStore.put("scope", (CloseableResource) scope::close); + } + if (methodInfo.requiresReset()) { + // put in the method store to auto-close + container = new HelidonTestContainerImpl(methodInfo, scope); + methodStore.put("container", container); + } else { + // put the "class container" in the class context store + // to re-use between methods + container = classStore.getOrComputeIfAbsent("container", + k -> new HelidonTestContainerImpl(classInfo, scope), HelidonTestContainerImpl.class); } } - - container = initializer.initialize(); - } - - private void stopContainer() { - if (container != null) { - container.close(); - container = null; - } + // proxy handler uses class context + // hence we use a class context store specific to the test method + store(classContext, testMethod).put("container", container); } @Override - public void afterAll(ExtensionContext context) { - stopContainer(); - releaseConfig(); - callAfterStop(); - if (pinningRecorder != null) { - pinningRecorder.close(); - pinningRecorder = null; - } - } - - @Override - public T interceptTestClassConstructor(Invocation invocation, - ReflectiveInvocationContext> invocationContext, - ExtensionContext extensionContext) throws Throwable { - - if (resetPerTest) { - // Junit creates test instance - return invocation.proceed(); - } - - // we need to start container before the test class is instantiated, to honor @BeforeAll that - // creates a custom MP config - if (container == null) { - // at this early stage the class should be checked whether it is annotated with - // @TestInstance(TestInstance.Lifecycle.PER_CLASS) to start correctly the container - TestInstance testClassAnnotation = testClass.getAnnotation(TestInstance.class); - if (testClassAnnotation != null && testClassAnnotation.value().equals(TestInstance.Lifecycle.PER_CLASS)){ - throw new RuntimeException("When a class is annotated with @HelidonTest, " - + "it is not compatible with @TestInstance(TestInstance.Lifecycle.PER_CLASS)" - + "annotation, as it is a Singleton CDI Bean."); - } - startContainer(classLevelBeans, classLevelExtensions, classLevelDisableDiscovery); - } - - // we need to replace instantiation with CDI lookup, to properly injection into fields (and constructors) - invocation.skip(); - - return container.select(invocationContext.getExecutable().getDeclaringClass()) - .get(); - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + public boolean supportsParameter(ParameterContext pc, ExtensionContext context) throws ParameterResolutionException { - Executable executable = parameterContext.getParameter().getDeclaringExecutable(); - - if (resetPerTest) { - if (executable instanceof Constructor) { - throw new ParameterResolutionException( - "When a test class is annotated with @HelidonTest(resetPerMethod=true), constructor must not have " - + "parameters."); - } - } else { - // we need to start container before the test class is instantiated, to honor @BeforeAll that - // creates a custom MP config - if (container == null) { - startContainer(classLevelBeans, classLevelExtensions, classLevelDisableDiscovery); - } - } - - Class paramType = parameterContext.getParameter().getType(); - - if (executable instanceof Constructor) { - return !container.select(paramType).isUnsatisfied(); - } else if (executable instanceof Method) { - if (paramType.equals(SeContainer.class)) { - return true; - } - if (paramType.equals(WebTarget.class)) { - return true; - } - } - - return false; + Store store = store(context, context.getRequiredTestMethod()); + HelidonTestContainerImpl container = requiredContainer(store); + return !container.initFailed() && container.isSupported(pc.getParameter().getType()); } @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + public Object resolveParameter(ParameterContext pc, ExtensionContext context) throws ParameterResolutionException { - Executable executable = parameterContext.getParameter().getDeclaringExecutable(); - Class paramType = parameterContext.getParameter().getType(); - - if (executable instanceof Method) { - if (paramType.equals(SeContainer.class)) { - return container; - } - if (paramType.equals(WebTarget.class)) { - return container.select(WebTarget.class).get(); - } - } - // we return null, as construction of the object is done by CDI - // for primitive types we must return appropriate primitive default - if (paramType.isPrimitive()) { - // a hack to get to default value of a primitive type - return Array.get(Array.newInstance(paramType, 1), 0); - } else { - return null; - } - } - - private void callAfterStop() { - List toInvoke = new ArrayList<>(); - - Method[] methods = testClass.getMethods(); - for (Method method : methods) { - AfterStop annotation = method.getAnnotation(AfterStop.class); - if (annotation != null) { - if (method.getParameterCount() != 0) { - throw new IllegalStateException("Method " + method + " is annotated with @AfterStop, but it has parameters"); - } - if (Modifier.isStatic(method.getModifiers())) { - method.setAccessible(true); - toInvoke.add(method); - } else { - throw new IllegalStateException("Method " + method + " is annotated with @AfterStop, but it is not static"); - } - } - } - - for (Method method : toInvoke) { - try { - method.invoke(testClass); - } catch (Exception e) { - throw new IllegalStateException("Failed to invoke method: " + method, e); - } - } - } - - // this is not registered as a bean - we manually register an instance - @SuppressWarnings("CdiManagedBeanInconsistencyInspection") - private static class AddBeansExtension implements Extension { - private final Class testClass; - private final List addBeans; - - private final HashMap socketAnnotations = new HashMap<>(); - - private AddBeansExtension(Class testClass, List addBeans) { - this.testClass = testClass; - this.addBeans = addBeans; - } - - - void processSocketInjectionPoints(@Observes ProcessInjectionPoint event) throws Exception{ - InjectionPoint injectionPoint = event.getInjectionPoint(); - Set qualifiers = injectionPoint.getQualifiers(); - for (Annotation qualifier : qualifiers) { - if (qualifier.annotationType().equals(Socket.class)) { - String value = ((Socket) qualifier).value(); - socketAnnotations.put(value, qualifier); - break; - } - } - - } - - void registerOtherBeans(@Observes AfterBeanDiscovery event) { - - Client client = ClientBuilder.newClient(); - - //register for all named Ports - socketAnnotations.forEach((namedPort, qualifier) -> { - - event.addBean() - .addType(WebTarget.class) - .scope(ApplicationScoped.class) - .qualifiers(qualifier) - .createWith(context -> getWebTarget(client, namedPort)); - }); - - event.addBean() - .addType(jakarta.ws.rs.client.WebTarget.class) - .scope(ApplicationScoped.class) - .createWith(context -> getWebTarget(client, "@default")); - - } - - @SuppressWarnings("unchecked") - private static WebTarget getWebTarget(Client client, String namedPort) { - try { - Class extClass = (Class) Class - .forName("io.helidon.microprofile.server.ServerCdiExtension"); - Extension extension = CDI.current().getBeanManager().getExtension(extClass); - Method m = extension.getClass().getMethod("port", String.class); - int port = (int) m.invoke(extension, new Object[]{namedPort}); - String uri = "http://localhost:" + port; - return client.target(uri); - } catch (ReflectiveOperationException e) { - return client.target("http://localhost:7001"); - } - } - - void registerAddedBeans(@Observes BeforeBeanDiscovery event) { - event.addAnnotatedType(testClass, "junit-" + testClass.getName()) - .add(ApplicationScoped.Literal.INSTANCE); - - for (AddBean addBean : addBeans) { - Annotation scope; - Class definedScope = addBean.scope(); - - scope = BEAN_DEFINING.get(definedScope); - - if (scope == null) { - throw new IllegalStateException( - "Only on of " + BEAN_DEFINING.keySet() + " scopes are allowed in tests. Scope " - + definedScope.getName() + " is not allowed for bean " + addBean.value().getName()); - } - - AnnotatedTypeConfigurator configurator = event - .addAnnotatedType(addBean.value(), "junit-" + addBean.value().getName()); - if (!hasBda(addBean.value())) { - configurator.add(scope); - } - } - } - - private boolean hasBda(Class value) { - // does it have bean defining annotation? - for (Class aClass : BEAN_DEFINING.keySet()) { - if (value.getAnnotation(aClass) != null) { - return true; - } - } - - return false; - } - - } - - private static final class ConfigMeta { - private final Map additionalKeys = new HashMap<>(); - private final List additionalSources = new ArrayList<>(); - private String type; - private String block; - private boolean useExisting; - private String profile; - - private ConfigMeta() { - // to allow SeContainerInitializer (forbidden by default because of native image) - additionalKeys.put("mp.initializer.allow", "true"); - additionalKeys.put("mp.initializer.no-warn", "true"); - // to run on random port - additionalKeys.put("server.port", "0"); - // higher ordinal then all the defaults, system props and environment variables - additionalKeys.putIfAbsent(ConfigSource.CONFIG_ORDINAL, "1000"); - // profile - additionalKeys.put("mp.config.profile", "test"); - } - - private void addConfig(AddConfig[] configs) { - for (AddConfig config : configs) { - additionalKeys.put(config.key(), config.value()); - } - } - - private void configuration(Configuration config) { - if (config == null) { - return; - } - useExisting = config.useExisting(); - profile = config.profile(); - additionalSources.addAll(List.of(config.configSources())); - //set additional key for profile - additionalKeys.put("mp.config.profile", profile); - } - - private void addConfigBlock(AddConfigBlock config) { - if (config == null) { - return; - } - this.type = config.type(); - this.block = config.value(); - } - ConfigMeta nextMethod() { - ConfigMeta methodMeta = new ConfigMeta(); - - methodMeta.additionalKeys.putAll(this.additionalKeys); - methodMeta.additionalSources.addAll(this.additionalSources); - methodMeta.useExisting = this.useExisting; - methodMeta.profile = this.profile; - - return methodMeta; - } + Store store = store(context, context.getRequiredTestMethod()); + HelidonTestContainerImpl container = requiredContainer(store); + return container.initFailed() ? null : container.resolveInstance(pc.getParameter().getType()); } + private void intercept(Invocation invocation, + ReflectiveInvocationContext ic, + ExtensionContext context) throws Throwable { - /** - * Add WeldRequestScope. Used with {@code AddJaxRs}. - */ - private static final class WeldRequestScopeLiteral extends AnnotationLiteral implements AddBean { - - static final WeldRequestScopeLiteral INSTANCE = new WeldRequestScopeLiteral(); - - @Serial - private static final long serialVersionUID = 1L; - - @Override - public Class value() { - return org.glassfish.jersey.weld.se.WeldRequestScope.class; - } - - @Override - public Class scope() { - return RequestScoped.class; - } - } - - - /** - * Add ProcessAllAnnotatedTypes. Used with {@code AddJaxRs}. - */ - private static final class ProcessAllAnnotatedTypesLiteral extends AnnotationLiteral implements AddExtension { - - static final ProcessAllAnnotatedTypesLiteral INSTANCE = new ProcessAllAnnotatedTypesLiteral(); - - @Serial - private static final long serialVersionUID = 1L; - - @Override - public Class value() { - return org.glassfish.jersey.ext.cdi1x.internal.ProcessAllAnnotatedTypes.class; - } - } - - /** - * Add ServerCdiExtension. Used with {@code AddJaxRs}. - */ - private static final class ServerCdiExtensionLiteral extends AnnotationLiteral implements AddExtension { - - static final ServerCdiExtensionLiteral INSTANCE = new ServerCdiExtensionLiteral(); - - @Serial - private static final long serialVersionUID = 1L; - - @Override - public Class value() { - return ServerCdiExtension.class; + Store methodStore = store(context, context.getRequiredTestMethod()); + HelidonTestContainerImpl container = requiredContainer(methodStore); + if (container.initFailed()) { + invocation.skip(); + } else { + // proxy handler uses class context + // hence we use a class context store specific to the test method + ExtensionContext classContext = classContext(context); + Store store = store(classContext, ic.getExecutable()); + store.put("container", container); + invocation.proceed(); } } - /** - * Add WeldRequestScope. Used with {@code AddJaxRs}. - */ - private static final class JaxRsCdiExtensionLiteral extends AnnotationLiteral implements AddExtension { - - static final JaxRsCdiExtensionLiteral INSTANCE = new JaxRsCdiExtensionLiteral(); - - @Serial - private static final long serialVersionUID = 1L; - - @Override - public Class value() { - return JaxRsCdiExtension.class; - } + private static HelidonTestContainerImpl container(Store store) { + return lookup(store, HelidonTestContainerImpl.class, "container") + .orElse(null); } - /** - * Add CdiComponentProvider. Used with {@code AddJaxRs}. - */ - private static final class CdiComponentProviderLiteral extends AnnotationLiteral implements AddExtension { - - static final CdiComponentProviderLiteral INSTANCE = new CdiComponentProviderLiteral(); - - @Serial - private static final long serialVersionUID = 1L; - - @Override - public Class value() { - return CdiComponentProvider.class; - } + private static HelidonTestContainerImpl requiredContainer(Store store) { + return lookup(store, HelidonTestContainerImpl.class, "container") + .orElseThrow(() -> new IllegalStateException("Container not set")); } - } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTest.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTest.java index cb85395b11a..d3400e0d6d6 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTest.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,16 +26,9 @@ import static io.helidon.common.testing.virtualthreads.PinningRecorder.DEFAULT_THRESHOLD; /** - * An annotation making this test class a CDI bean with support for injection. - *

- * There is no need to provide {@code beans.xml} (actually it is not recommended, as it would combine beans - * from all tests), instead use {@link io.helidon.microprofile.testing.junit5.AddBean}, - * {@link io.helidon.microprofile.testing.junit5.AddExtension}, - * and {@link io.helidon.microprofile.testing.junit5.AddConfig} - * annotations to control the shape of the container. - *

- * To disable automated bean and extension discovery, annotate the class with - * {@link io.helidon.microprofile.testing.junit5.DisableDiscovery}. + * A shorthand to use {@link HelidonJunitExtension} with additional settings. + * + * @see HelidonJunitExtension */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @@ -43,15 +36,11 @@ @Inherited public @interface HelidonTest { /** - * By default, CDI container is created once before the class is initialized and shut down - * after. All test methods run within the same container. - * - * If this is set to {@code true}, a container is created per test method invocation. - * This restricts the test in the following way: - * 1. No injection into fields - * 2. No injection into constructor + * Forces the CDI container to be initialized and shutdown for each test method. + *

+ * The value of {@link org.junit.jupiter.api.TestInstance TestInstance} is ignored. * - * @return whether to reset container per test method + * @return whether to reset per test method */ boolean resetPerTest() default false; diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTestContainerImpl.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTestContainerImpl.java new file mode 100644 index 00000000000..19ba8c68406 --- /dev/null +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTestContainerImpl.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.junit5; + +import io.helidon.microprofile.testing.HelidonTestContainer; +import io.helidon.microprofile.testing.HelidonTestInfo; +import io.helidon.microprofile.testing.HelidonTestScope; + +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; + +/** + * Closeable test container. + */ +final class HelidonTestContainerImpl extends HelidonTestContainer implements CloseableResource { + + HelidonTestContainerImpl(HelidonTestInfo testInfo, HelidonTestScope testScope) { + super(testInfo, testScope, HelidonTestExtensionImpl::new); + } + + @Override + public void close() { + super.close(); + } +} diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTestDescriptorImpl.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTestDescriptorImpl.java new file mode 100644 index 00000000000..8d774e6203e --- /dev/null +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTestDescriptorImpl.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.junit5; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import io.helidon.microprofile.testing.HelidonTestDescriptorBase; + +import static io.helidon.microprofile.testing.Proxies.mirror; + +/** + * Base descriptor implementation that supports the deprecated annotations. + */ +@SuppressWarnings("deprecation") +class HelidonTestDescriptorImpl extends HelidonTestDescriptorBase { + + HelidonTestDescriptorImpl(T element) { + super(element); + } + + @Override + protected List lookupAddBeans() { + return lookup(io.helidon.microprofile.testing.AddBean.class, super.lookupAddBeans().stream(), + AddBean.class, AddBeans.class, AddBeans::value).toList(); + } + + @Override + protected List lookupAddConfigs() { + return lookup(io.helidon.microprofile.testing.AddConfig.class, super.lookupAddConfigs().stream(), + AddConfig.class, AddConfigs.class, AddConfigs::value).toList(); + } + + @Override + protected List lookupAddConfigBlocks() { + return Stream.concat(super.lookupAddConfigBlocks().stream(), annotations(AddConfigBlock.class) + .map(a -> mirror(io.helidon.microprofile.testing.AddConfigBlock.class, a))) + .toList(); + } + + @Override + protected List lookupAddExtensions() { + return lookup(io.helidon.microprofile.testing.AddExtension.class, super.lookupAddExtensions().stream(), + AddExtension.class, AddExtensions.class, AddExtensions::value).toList(); + } + + @Override + protected Optional lookupConfiguration() { + return super.lookupConfiguration().or(() -> annotations(Configuration.class) + .map(a -> mirror(io.helidon.microprofile.testing.Configuration.class, a)) + .findFirst()); + } + + @Override + protected boolean lookupAddJaxRs() { + return super.lookupAddJaxRs() || annotations(AddJaxRs.class) + .findFirst() + .isPresent(); + } + + @Override + protected boolean lookupDisableDiscovery() { + return super.lookupDisableDiscovery() || annotations(DisableDiscovery.class) + .findFirst() + .map(DisableDiscovery::value) + .orElse(false); + } + + @Override + protected boolean lookupResetPerTest() { + return annotations(HelidonTest.class) + .findFirst() + .map(HelidonTest::resetPerTest) + .orElse(false); + } + + @Override + protected boolean lookupPinningDetection() { + return annotations(HelidonTest.class) + .findFirst() + .map(HelidonTest::pinningDetection) + .orElse(false); + } + + @Override + public long pinningThreshold() { + return annotations(HelidonTest.class) + .findFirst() + .map(HelidonTest::pinningThreshold) + .orElse(20L); + } + + private Stream lookup(Class tType, + Stream initial, + Class aType, + Class cType, + Function function) { + + return Stream.concat(initial, annotations(aType, cType, function) + .map(a -> mirror(tType, a))); + } +} diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTestExtensionImpl.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTestExtensionImpl.java new file mode 100644 index 00000000000..e30d2ba72b8 --- /dev/null +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonTestExtensionImpl.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.junit5; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.helidon.microprofile.testing.HelidonTestExtension; +import io.helidon.microprofile.testing.HelidonTestInfo; +import io.helidon.microprofile.testing.HelidonTestScope; + +import static io.helidon.microprofile.testing.Proxies.mirror; + +/** + * An implementation of {@link HelidonTestExtension} that supports the deprecated annotations. + */ +@SuppressWarnings("deprecation") +final class HelidonTestExtensionImpl extends HelidonTestExtension { + + private static final Set> TYPE_ANNOTATION_TYPES = Set.of( + AddConfig.class, + AddConfigs.class, + AddConfigBlock.class, + Configuration.class); + + private static final Set> PARAMETER_ANNOTATION_TYPES = Set.of( + Socket.class); + + private static final Set> FIELD_ANNOTATION_TYPES = Set.of( + Socket.class); + + private static final Set> METHOD_ANNOTATION_TYPES = Set.of( + AddConfig.class, + AddConfigs.class, + AddConfigBlock.class, + AfterStop.class, + Configuration.class); + + private final Set> typeAnnotationTypes; + private final Set> parameterAnnotationTypes; + private final Set> fieldAnnotationTypes; + private final Set> methodAnnotationTypes; + + HelidonTestExtensionImpl(HelidonTestInfo testInfo, HelidonTestScope testScope) { + super(testInfo, testScope); + this.typeAnnotationTypes = concat(super.typeAnnotationTypes(), TYPE_ANNOTATION_TYPES); + this.parameterAnnotationTypes = concat(super.parameterAnnotationTypes(), PARAMETER_ANNOTATION_TYPES); + this.fieldAnnotationTypes = concat(super.fieldAnnotationTypes(), FIELD_ANNOTATION_TYPES); + this.methodAnnotationTypes = concat(super.methodAnnotationTypes(), METHOD_ANNOTATION_TYPES); + } + + @Override + protected Set> typeAnnotationTypes() { + return typeAnnotationTypes; + } + + @Override + protected Set> parameterAnnotationTypes() { + return parameterAnnotationTypes; + } + + @Override + protected Set> fieldAnnotationTypes() { + return fieldAnnotationTypes; + } + + @Override + protected Set> methodAnnotationTypes() { + return methodAnnotationTypes; + } + + @Override + protected void processTypeAnnotation(Annotation annotation) { + switch (annotation) { + case Configuration e -> processConfiguration(e); + case AddConfig e -> processAddConfig(e); + case AddConfigs e -> processAddConfig(e.value()); + case AddConfigBlock e -> processAddConfigBlock(e); + default -> super.processTypeAnnotation(annotation); + } + } + + @Override + protected void processParameterAnnotation(Annotation annotation) { + if (Objects.requireNonNull(annotation) instanceof Socket s) { + processSocket(s, s.value()); + } else { + super.processParameterAnnotation(annotation); + } + } + + @Override + protected void processFieldAnnotation(Annotation annotation) { + if (Objects.requireNonNull(annotation) instanceof Socket s) { + processSocket(s, s.value()); + } else { + super.processFieldAnnotation(annotation); + } + } + + @Override + protected void processStaticMethodAnnotation(Annotation annotation, Method method) { + if (annotation instanceof AfterStop) { + processAfterStop(method); + } else { + super.processStaticMethodAnnotation(annotation, method); + } + } + + @Override + protected void processTestMethodAnnotation(Annotation annotation, Method method) { + switch (annotation) { + case Configuration e -> processConfiguration(e); + case AddConfig e -> processAddConfig(e); + case AddConfigs e -> processAddConfig(e.value()); + case AddConfigBlock e -> processAddConfigBlock(e); + default -> super.processTestMethodAnnotation(annotation, method); + } + } + + private void processConfiguration(Configuration annotation) { + processConfiguration(mirror(io.helidon.microprofile.testing.Configuration.class, annotation)); + } + + private void processAddConfigBlock(AddConfigBlock annotation) { + processAddConfigBlock(mirror(io.helidon.microprofile.testing.AddConfigBlock.class, annotation)); + } + + private void processAddConfig(AddConfig... annotations) { + for (AddConfig annotation : annotations) { + processAddConfig(mirror(io.helidon.microprofile.testing.AddConfig.class, annotation)); + } + } + + private static Set> concat(Set> set1, + Set> set2) { + + return Stream.concat(set1.stream(), set2.stream()).collect(Collectors.toSet()); + } +} diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/Socket.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/Socket.java index 9852617ae4d..2d81c90b2c0 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/Socket.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/Socket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,18 +23,53 @@ import jakarta.inject.Qualifier; /** - * Named socket Qualifier for {@code WebTarget}. + * CDI qualifier to inject a JAX-RS client or URI for a named socket. + *

+ * The supported types are: + *

+ *

+ * This annotation can be used on constructor parameters, or class fields. + * Test method parameter injection may be supported depending on the test framework integration. + *

+ * Also note that the default socket name is {@code "@default"}. + *

+ * E.g. constructor injection: + *

+ * class MyTest {
+ *     private final WebTarget target;
+ *
+ *     @Inject
+ *     MyTest(@Socket("@default") URI uri) {
+ *         target = ClientBuilder.newClient().target(uri);
+ *     }
+ * }
+ * 
+ *

+ * E.g. field injection: + *

+ * class MyTest {
+ *
+ *     @Inject // optional
+ *     @Socket("@default")
+ *     private WebTarget target;
+ * }
+ * 
+ * @deprecated Use {@link io.helidon.microprofile.testing.Socket} instead */ @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER}) +@Deprecated(since = "4.2.0") public @interface Socket { /** * Name of the socket. * - * @return String with the name of the Socket + * @return socket name */ String value(); - } diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/package-info.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/package-info.java index d299e74a35e..5e997c48de9 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/package-info.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ /** - * JUnit5 extension to run CDI tests. + * JUnit5 extension to support Helidon MP testing. * - * @see io.helidon.microprofile.testing.junit5.HelidonTest + * @see io.helidon.microprofile.testing.junit5.HelidonJunitExtension */ package io.helidon.microprofile.testing.junit5; diff --git a/microprofile/testing/junit5/src/main/java/module-info.java b/microprofile/testing/junit5/src/main/java/module-info.java index 398fc045b50..6e7474e5307 100644 --- a/microprofile/testing/junit5/src/main/java/module-info.java +++ b/microprofile/testing/junit5/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,22 +17,11 @@ /** * JUnit5 extension module to run CDI tests. */ +@SuppressWarnings("JavaModuleNaming") module io.helidon.microprofile.testing.junit5 { - requires io.helidon.config.mp; - requires io.helidon.config.yaml.mp; - requires io.helidon.microprofile.cdi; - requires jakarta.inject; + requires transitive io.helidon.microprofile.testing; requires org.junit.jupiter.api; - requires transitive jakarta.cdi; - requires transitive jakarta.ws.rs; - - requires static io.helidon.microprofile.server; - requires static jersey.cdi1x; - requires static jersey.weld2.se; - requires io.helidon.common.testing.vitualthreads; - exports io.helidon.microprofile.testing.junit5; - } diff --git a/microprofile/testing/mocking/src/main/java/io/helidon/microprofile/testing/mocking/MockBeansCdiExtension.java b/microprofile/testing/mocking/src/main/java/io/helidon/microprofile/testing/mocking/MockBeansCdiExtension.java index 855c9ac2d89..495eb21b188 100644 --- a/microprofile/testing/mocking/src/main/java/io/helidon/microprofile/testing/mocking/MockBeansCdiExtension.java +++ b/microprofile/testing/mocking/src/main/java/io/helidon/microprofile/testing/mocking/MockBeansCdiExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ import org.mockito.Mockito; /** - * CDI extension for Mocking implementation. + * CDI extension that supports {@link MockBean}. */ public class MockBeansCdiExtension implements Extension { diff --git a/microprofile/testing/pom.xml b/microprofile/testing/pom.xml index e89c6692c9b..75e5dbcdab5 100644 --- a/microprofile/testing/pom.xml +++ b/microprofile/testing/pom.xml @@ -32,6 +32,7 @@ Helidon Microprofile Testing Project + testing junit5 testng mocking diff --git a/microprofile/testing/testing/pom.xml b/microprofile/testing/testing/pom.xml new file mode 100644 index 00000000000..2f8a3b359ac --- /dev/null +++ b/microprofile/testing/testing/pom.xml @@ -0,0 +1,73 @@ + + + + + 4.0.0 + + io.helidon.microprofile.testing + helidon-microprofile-testing-project + 4.2.0-SNAPSHOT + + helidon-microprofile-testing + Helidon Microprofile Testing + + + + io.helidon.common.testing + helidon-common-testing-virtual-threads + + + io.helidon.microprofile.server + helidon-microprofile-server + true + + + io.helidon.microprofile.cdi + helidon-microprofile-cdi + provided + + + org.glassfish.jersey.ext.cdi + jersey-weld2-se + true + + + net.bytebuddy + byte-buddy + + + io.helidon.jersey + helidon-jersey-client + + + io.helidon.config + helidon-config-yaml-mp + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddBean.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddBean.java new file mode 100644 index 00000000000..48e40d0cfba --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddBean.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Add a CDI bean to the container. + *

+ * This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + *

+ * The bean scope is defined as follows: + *

    + *
  • If a scope is set with {@link #value()}, it overrides any scope defined on the bean
  • + *
  • Otherwise, the scope defined on the bean is used
  • + *
  • If the bean does not define a scope, {@link jakarta.enterprise.context.ApplicationScoped ApplicationScoped} + * is used
  • + *
+ */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable(AddBeans.class) +public @interface AddBean { + /** + * The bean class. + * + * @return bean class + */ + Class value(); + + /** + * Override the bean scope. + * + * @return scope class + */ + Class scope() default Annotation.class; +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddBeans.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddBeans.java new file mode 100644 index 00000000000..3929d7c6c6e --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddBeans.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A repeatable container for {@link AddBean}. + *

+ * This annotation is optional, you can instead repeat {@link AddBean}. + *

+ * E.g. + *

+ * @AddBean(FooBean.class)
+ * @AddBean(BarBean.class)
+ * class MyTest {
+ * }
+ * 
+ */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface AddBeans { + /** + * Get the contained annotations. + * + * @return annotations + */ + AddBean[] value(); +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfig.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfig.java new file mode 100644 index 00000000000..39ab59686ba --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfig.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Add a configuration key/value pair to the {@link Configuration#useExisting() synthetic test configuration}. + *

+ * This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * + * @see io.helidon.microprofile.testing.AddConfigs + * @see io.helidon.microprofile.testing.AddConfigBlock + * @see io.helidon.microprofile.testing.AddConfigSource + * @see io.helidon.microprofile.testing.Configuration + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable(AddConfigs.class) +public @interface AddConfig { + /** + * Configuration property key. + * + * @return key + */ + String key(); + + /** + * Configuration property value. + * + * @return value + */ + String value(); +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigBlock.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigBlock.java new file mode 100644 index 00000000000..97ec4cb06e1 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigBlock.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Add a configuration fragment to the {@link Configuration#useExisting() synthetic test configuration}. + *

+ * This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * + * @see AddConfig + * @see AddConfigs + * @see AddConfigSource + * @see Configuration + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable(AddConfigBlocks.class) +public @interface AddConfigBlock { + /** + * Specifies the configuration format. + *

+ * The default format is 'properties' + * + * @return the supported type + */ + String type() default "properties"; + + /** + * Configuration fragment. + * + * @return fragment + */ + String value(); +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigBlocks.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigBlocks.java new file mode 100644 index 00000000000..07922350837 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigBlocks.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A repeatable container for {@link AddConfigBlock}. + *

+ * This annotation is optional, you can instead repeat {@link AddConfigBlock}. + *

+ * E.g. + *

+ * @AddConfigBlock(type = "yaml", value = """
+ *     foo1:
+ *       bar: "value1"
+ * """)
+ * @AddConfigBlock(type = "yaml", value = """
+ *     foo2:
+ *       bar: "value2"
+ * """)
+ * class MyTest {
+ * }
+ * 
+ * + * @see AddConfig + * @see io.helidon.microprofile.testing.AddConfigBlocks + * @see AddConfigSource + * @see Configuration + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface AddConfigBlocks { + /** + * Get the contained annotations. + * + * @return annotations + */ + AddConfigBlock[] value(); +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigSource.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigSource.java new file mode 100644 index 00000000000..c3b95cfc981 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigSource.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mark a static method that provides a {@link org.eclipse.microprofile.config.spi.ConfigSource ConfigSource} + * to add to the {@link Configuration#useExisting() synthetic test configuration}. + *

+ * E.g. + *

+ * @AddConfigSource
+ * static ConfigSource config() {
+ *     return MpConfigSources.create(Map.of("foo", "bar"));
+ * }
+ * + * @see io.helidon.config.mp.MpConfigSources + * @see AddConfig + * @see AddConfigs + * @see AddConfigBlock + * @see Configuration + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface AddConfigSource { +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigs.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigs.java new file mode 100644 index 00000000000..a0edc93116b --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddConfigs.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A repeatable container for {@link AddConfig}. + *

+ * This annotation is optional, you can instead repeat {@link AddConfig}. + *

+ * E.g. + *

+ * @AddConfig(key="foo", value="1")
+ * @AddConfig(key="bar", value="2")
+ * class MyTest {
+ * }
+ * 
+ * + * @see AddConfig + * @see io.helidon.microprofile.testing.AddConfigs + * @see AddConfigSource + * @see Configuration + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface AddConfigs { + /** + * Get the contained annotations. + * + * @return annotations + */ + AddConfig[] value(); +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddExtension.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddExtension.java new file mode 100644 index 00000000000..271117a0fe1 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddExtension.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.inject.spi.Extension; + +/** + * Add a CDI extension to the container. + *

+ * This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable(AddExtensions.class) +public @interface AddExtension { + /** + * Class of the extension to add. The class must be public. + * + * @return extension class. + */ + Class value(); +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddExtensions.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddExtensions.java new file mode 100644 index 00000000000..87c9c63f55c --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddExtensions.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A repeatable container for {@link AddExtension}. + *

+ * This annotation is optional, you can instead repeat {@link AddExtension}. + *

+ * E.g. + *

+ * @AddExtension(FooExtension.class)
+ * @AddExtension(BarExtension.class)
+ * class MyTest {
+ * }
+ * 
+ */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface AddExtensions { + /** + * Get the contained annotations. + * + * @return annotations + */ + AddExtension[] value(); +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddJaxRs.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddJaxRs.java new file mode 100644 index 00000000000..fd9ab0a1cbe --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AddJaxRs.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.helidon.microprofile.server.JaxRsCdiExtension; +import io.helidon.microprofile.server.ServerCdiExtension; + +import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; +import org.glassfish.jersey.ext.cdi1x.internal.ProcessAllAnnotatedTypes; +import org.glassfish.jersey.weld.se.WeldRequestScope; + +/** + * Add JAX-RS (Jersey) support. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@AddExtension(ProcessAllAnnotatedTypes.class) +@AddExtension(ServerCdiExtension.class) +@AddExtension(JaxRsCdiExtension.class) +@AddExtension(CdiComponentProvider.class) +@AddBean(WeldRequestScope.class) +public @interface AddJaxRs { +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AfterStop.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AfterStop.java new file mode 100644 index 00000000000..259c27094dd --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/AfterStop.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mark a static method to be executed after the container is stopped. + *

+ * E.g. + *

+ * @AfterStop
+ * static void afterStop() {
+ *     // ...
+ * }
+ */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface AfterStop { +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Configuration.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Configuration.java new file mode 100644 index 00000000000..d33947f8b8f --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Configuration.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * General setting for the test configuration. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * + * @see AddConfig + * @see io.helidon.microprofile.testing.AddConfigs + * @see io.helidon.microprofile.testing.AddConfigBlock + * @see io.helidon.microprofile.testing.AddConfigSource + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface Configuration { + /** + * If set to {@code false}, the synthetic test configuration is used. + *

+ * The synthetic test configuration is expressed with the following: + *

    + *
  • {@link #configSources()}
  • + *
  • {@link #profile()}
  • + *
  • {@link AddConfig}
  • + *
  • {@link AddConfigs}
  • + *
  • {@link AddConfigBlock}
  • + *
  • {@link AddConfigSource}
  • + *
+ *

+ * If set to {@code true}, only the existing (or default) MicroProfile configuration is used + * and the annotations listed previously are ignored. + *

+ * You can use {@link org.eclipse.microprofile.config.spi.ConfigProviderResolver ConfigProviderResolver} to define + * the configuration programmatically before the CDI container starts. + * + * @return whether to use existing (or default) configuration + */ + boolean useExisting() default false; + + /** + * Class-path resources to add as config sources to the synthetic test configuration. + * + * @return config sources + */ + String[] configSources() default {}; + + /** + * Configuration profile. + *

+ * The default profile is 'test' + * + * @return profile + */ + String profile() default "test"; +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/DisableDiscovery.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/DisableDiscovery.java new file mode 100644 index 00000000000..59da10af48e --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/DisableDiscovery.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Disables CDI discovery. + *

+ * If discovery is desired, do not annotate test class with this annotation. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + *

+ * When disabling discovery, you are responsible for adding the beans and extensions needed to activate the features you need. + * You can use the following annotations to do that: + *

    + *
  • {@link AddBean} to add CDI beans
  • + *
  • {@link AddExtension} to add CDI extensions
  • + *
  • {@link AddJaxRs} a short-hand to add JAX-RS (Jersey)
  • + *
+ *

+ * See also the following "core" CDI extensions: + *

    + *
  • {@link io.helidon.microprofile.server.ServerCdiExtension ServerCdiExtension} optional if using {@link AddJaxRs}
  • + *
  • {@link io.helidon.microprofile.server.JaxRsCdiExtension JaxRsCdiExtension} optional if using {@link AddJaxRs}
  • + *
  • {@link io.helidon.microprofile.config.ConfigCdiExtension ConfigCdiExtension}
  • + *
+ */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface DisableDiscovery { + /** + * By default, if you annotate a class or a method, discovery gets disabled. + * If you want to override configuration on method to differ from class, you + * can configure the value to {@code false}, effectively enabling discovery. + * + * @return whether to disable discovery ({@code true}), or enable it ({@code false}). If this + * annotation is not present, discovery is enabled + */ + boolean value() default true; +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfig.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfig.java new file mode 100644 index 00000000000..32a6639a600 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfig.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.util.Map; + +import io.helidon.config.mp.MpConfigSources; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; + +/** + * Helidon test configuration. + *

+ * A config delegate that serves one of three config instances: + *

    + *
  • bootstrapping configuration
  • + *
  • synthetic configuration
  • + *
  • original configuration
  • + *
+ *

+ * This mechanism removes the need to define bootstrapping configuration when using {@link Configuration#useExisting()}. + *

+ * This mechanism also provides a way to update the synthetic configuration at a later stage using CDI. + */ +class HelidonTestConfig extends HelidonTestConfigDelegate { + + private final HelidonTestConfigSynthetic syntheticConfig; + private final Config originalConfig; + private volatile Config delegate; + + HelidonTestConfig(HelidonTestInfo testInfo) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + ConfigProviderResolver resolver = ConfigProviderResolver.instance(); + + originalConfig = resolver.getConfig(cl); + + // release original config + resolver.releaseConfig(originalConfig); + + // start with bootstrap config + delegate = resolver.getBuilder() + .withSources(MpConfigSources.create(Map.of( + "mp.initializer.allow", "true", + "mp.initializer.no-warn", "true"))) + .build(); + + syntheticConfig = new HelidonTestConfigSynthetic(testInfo, this::refresh); + + // register delegate + resolver.registerConfig(this, cl); + } + + @Override + Config delegate() { + return delegate; + } + + /** + * Get the synthetic config. + * + * @return synthetic config + */ + HelidonTestConfigSynthetic synthetic() { + return syntheticConfig; + } + + /** + * Resolve the delegate. + */ + void resolve() { + if (syntheticConfig.useExisting()) { + delegate = originalConfig; + } else { + delegate = syntheticConfig; + } + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfigDelegate.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfigDelegate.java new file mode 100644 index 00000000000..82c40af8c3a --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfigDelegate.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import io.helidon.common.GenericType; +import io.helidon.common.LazyValue; +import io.helidon.config.Config; +import io.helidon.config.ConfigMappingException; +import io.helidon.config.ConfigValue; +import io.helidon.config.MissingValueException; +import io.helidon.config.spi.ConfigMapper; +import io.helidon.config.spi.ConfigNode; +import io.helidon.config.spi.ConfigSource; +import io.helidon.config.spi.LazyConfigSource; + +/** + * Config delegate. + *

+ * Also implements a {@link Config Helidon Config} delegate backed by a {@link LazyConfigSource} + * to support "just in time" caching when using {@link io.helidon.config.Config Helidon Config}. + */ +abstract class HelidonTestConfigDelegate implements org.eclipse.microprofile.config.Config, Config { + + private final LazyValue hdelegate = LazyValue.create(this::delegate0); + private final Map> cache = new HashMap<>(); + + /* + * Get the MicroProfile config delegate. + * + * @return delegate + */ + abstract org.eclipse.microprofile.config.Config delegate(); + + @Override + public T getValue(String propertyName, Class propertyType) { + return delegate().getValue(propertyName, propertyType); + } + + @Override + public org.eclipse.microprofile.config.ConfigValue getConfigValue(String propertyName) { + return delegate().getConfigValue(propertyName); + } + + @Override + public List getValues(String propertyName, Class propertyType) { + return delegate().getValues(propertyName, propertyType); + } + + @Override + public Optional getOptionalValue(String propertyName, Class propertyType) { + return delegate().getOptionalValue(propertyName, propertyType); + } + + @Override + public Optional> getOptionalValues(String propertyName, Class propertyType) { + return delegate().getOptionalValues(propertyName, propertyType); + } + + @Override + public Iterable getPropertyNames() { + return delegate().getPropertyNames(); + } + + @Override + public Iterable getConfigSources() { + return delegate().getConfigSources(); + } + + @Override + public Optional> getConverter(Class forType) { + return delegate().getConverter(forType); + } + + @Override + public T unwrap(Class type) { + return delegate().unwrap(type); + } + + @Override + public Instant timestamp() { + return hdelegate.get().timestamp(); + } + + @Override + public Key key() { + return hdelegate.get().key(); + } + + @Override + public Config root() { + return hdelegate.get().root(); + } + + @Override + public Config get(Key key) { + return hdelegate.get().get(key); + } + + @Override + public Config detach() { + return hdelegate.get().detach(); + } + + @Override + public Type type() { + return hdelegate.get().type(); + } + + @Override + public boolean hasValue() { + return hdelegate.get().hasValue(); + } + + @Override + public Stream traverse(Predicate predicate) { + return hdelegate.get().traverse(predicate); + } + + @Override + public T convert(Class type, String value) throws ConfigMappingException { + return hdelegate.get().convert(type, value); + } + + @Override + public ConfigMapper mapper() { + return hdelegate.get().mapper(); + } + + @Override + public ConfigValue as(GenericType genericType) { + return hdelegate.get().as(genericType); + } + + @Override + public ConfigValue as(Class type) { + return hdelegate.get().as(type); + } + + @Override + public ConfigValue as(Function mapper) { + return hdelegate.get().as(mapper); + } + + @Override + public ConfigValue> asList(Class type) throws ConfigMappingException { + return hdelegate.get().asList(type); + } + + @Override + public ConfigValue> asList(Function mapper) throws ConfigMappingException { + return hdelegate.get().asList(mapper); + } + + @Override + public ConfigValue> asNodeList() throws ConfigMappingException { + return hdelegate.get().asNodeList(); + } + + @Override + public ConfigValue> asMap() throws MissingValueException { + return hdelegate.get().asMap(); + } + + /** + * Refresh the cached property names for the current delegate. + */ + void refresh() { + org.eclipse.microprofile.config.Config delegate = delegate(); + if (delegate != null) { + cache.computeIfPresent(delegate, (k, v) -> { + List names = new ArrayList<>(); + for (String name : k.getPropertyNames()) { + names.add(name); + } + return names; + }); + } + } + + private List propertyNames(org.eclipse.microprofile.config.Config config) { + List names = new ArrayList<>(); + for (String name : config.getPropertyNames()) { + names.add(name); + } + return names; + } + + private Config delegate0() { + return Config.just((ConfigSource & LazyConfigSource) key -> { + org.eclipse.microprofile.config.Config delegate = delegate(); + if (delegate != null) { + String value = delegate.getConfigValue(key).getValue(); + if (value != null) { + // simple value + return Optional.of(ConfigNode.ValueNode.create(value)); + } + // complex value + List propertyNames = cache.computeIfAbsent(delegate, this::propertyNames); + ConfigNode.ObjectNode.Builder builder = ConfigNode.ObjectNode.builder(); + boolean hasEntries = false; + for (String name : propertyNames) { + if (name.startsWith(key + ".")) { + String k = name.substring(key.length() + 1); + String v = delegate.getConfigValue(name).getValue(); + builder.addValue(k, v); + hasEntries = true; + } + } + if (hasEntries) { + return Optional.of(builder.build()); + } + } + return Optional.empty(); + }); + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfigSynthetic.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfigSynthetic.java new file mode 100644 index 00000000000..f49219a76ec --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfigSynthetic.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.io.IOException; +import java.io.StringReader; +import java.io.UncheckedIOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import io.helidon.config.mp.MpConfigSources; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigBuilder; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.config.spi.ConfigSource; + +import static io.helidon.microprofile.testing.ReflectionHelper.invoke; +import static io.helidon.microprofile.testing.ReflectionHelper.requireStatic; + +/** + * The synthetic test configuration that is expressed with annotations. + *

+ * The delegate is initialized with the annotations extracted via {@link HelidonTestInfo}. + *

+ * The delegate is re-built when the definitions are updated via the {@code update} methods. + */ +class HelidonTestConfigSynthetic extends HelidonTestConfigDelegate { + + private final Map map = new HashMap<>(); + private final Map> blocks = new HashMap<>(); + private final Set methods = new HashSet<>(); + private final Set resources = new HashSet<>(); + private final HelidonTestInfo testInfo; + private final ReentrantLock lock = new ReentrantLock(); + private final Runnable onUpdate; + private boolean useExisting; + private Config config; + + HelidonTestConfigSynthetic(HelidonTestInfo testInfo, Runnable onUpdate) { + this.testInfo = testInfo; + this.onUpdate = onUpdate; + map.put(ConfigSource.CONFIG_ORDINAL, "1000"); + map.put("server.port", "0"); + map.put("mp.config.profile", "test"); + testInfo.addConfigs().forEach(this::update); + testInfo.addConfigBlocks().forEach(this::update); + testInfo.addConfigSources().forEach(this::update); + testInfo.configuration().ifPresent(this::update); + } + + @Override + Config delegate() { + if (config == null) { + try { + lock.lock(); + if (config == null) { + config = buildConfig(); + onUpdate.run(); + } + } finally { + lock.unlock(); + } + } + return config; + } + + /** + * Update. + * + * @param annotation annotation + */ + void update(Configuration annotation) { + map.put("mp.config.profile", annotation.profile()); + useExisting = annotation.useExisting(); + List sources = List.of(annotation.configSources()); + if (!resources.containsAll(sources)) { + resources.addAll(sources); + config = null; + } + } + + /** + * Update. + * + * @param annotations annotations + */ + void update(AddConfig... annotations) { + for (AddConfig annotation : annotations) { + map.put(annotation.key(), annotation.value()); + } + } + + /** + * Update. + * + * @param annotations annotations + */ + void update(AddConfigBlock... annotations) { + for (AddConfigBlock annotation : annotations) { + if (blocks.computeIfAbsent(annotation.type(), t -> new HashSet<>()) + .add(annotation.value())) { + config = null; + } + } + } + + /** + * Update. + * + * @param method method + */ + void update(Method method) { + if (methods.add(requireStatic(method))) { + config = null; + } + } + + /** + * Get the effective value of {@link Configuration#useExisting()}. + * + * @return useExisting + */ + boolean useExisting() { + return useExisting; + } + + private Config buildConfig() { + List configSources = new ArrayList<>(); + configSources.add(MpConfigSources.create(testInfo.id(), map)); + blocks.forEach((type, values) -> { + for (String value : values) { + configSources.add(MpConfigSources.create(type, new StringReader(value))); + } + }); + for (Method m : methods) { + configSources.add(invoke(ConfigSource.class, requireStatic(m), null)); + } + for (String source : resources) { + String filename = source.trim(); + for (URL url : resources(filename)) { + String type = extension(filename); + configSources.add(MpConfigSources.create(type, url)); + } + } + ConfigBuilder builder = ConfigProviderResolver.instance() + .getBuilder() + .addDefaultSources() + .addDiscoveredSources() + .addDiscoveredConverters(); + configSources.forEach(builder::withSources); + return builder.build(); + } + + private static String extension(String filename) { + int idx = filename.lastIndexOf('.'); + return idx > -1 ? filename.substring(idx + 1) : "properties"; + } + + private static Collection resources(String name) { + try { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Map urls = new HashMap<>(); + cl.getResources(name).asIterator() + .forEachRemaining(u -> urls.put(u.toString(), u)); + return urls.values(); + } catch (IOException e) { + throw new UncheckedIOException(String.format( + "Failed to read '%s' from classpath", name), e); + } + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestContainer.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestContainer.java new file mode 100644 index 00000000000..06abc6fdb4b --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestContainer.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.System.Logger.Level; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; + +import io.helidon.common.testing.virtualthreads.PinningRecorder; + +import jakarta.enterprise.inject.se.SeContainer; +import jakarta.enterprise.inject.se.SeContainerInitializer; + +import static io.helidon.microprofile.testing.ReflectionHelper.requirePublic; + +/** + * CDI container testing facade. + */ +public class HelidonTestContainer { + + /** + * Indicate that the container previously failed to initialize. + */ + public static final class InitializationFailed extends RuntimeException { + private InitializationFailed(RuntimeException error) { + super("Container initialization previously failed", error); + } + } + + private static final System.Logger LOGGER = System.getLogger(HelidonTestContainer.class.getName()); + private static final AtomicInteger NEXT_ID = new AtomicInteger(1); + + private final HelidonTestInfo testInfo; + private final HelidonTestScope testScope; + private final BiFunction, HelidonTestScope, HelidonTestExtension> extensionFactory; + private final AtomicBoolean closed = new AtomicBoolean(false); + private final ReentrantLock lock = new ReentrantLock(); + private final int id = NEXT_ID.getAndIncrement(); + private SeContainer container; + private PinningRecorder pinningRecorder; + private RuntimeException error; + + /** + * Create a new instance. + * + * @param testInfo test info + * @param testScope test scope + * @param extensionFactory extension factory + */ + public HelidonTestContainer(HelidonTestInfo testInfo, + HelidonTestScope testScope, + BiFunction, HelidonTestScope, HelidonTestExtension> extensionFactory) { + + this.testInfo = testInfo; + this.testScope = testScope; + this.extensionFactory = extensionFactory; + } + + /** + * Stop the container. + */ + public void close() { + if (container != null && closed.compareAndSet(false, true)) { + LOGGER.log(Level.DEBUG, "closing container id={0}", id); + container.close(); + if (pinningRecorder != null) { + pinningRecorder.close(); + } + } + } + + /** + * Indicate if the container is closed. + * + * @return {@code true} if closed, {@code false} otherwise + */ + public boolean closed() { + return closed.get(); + } + + /** + * Indicate if the container initializion failed. + * + * @return {@code true} if failed, {@code false} otherwise + */ + public boolean initFailed() { + return error != null; + } + + /** + * Resolve an unqualified bean of the given type. + * + * @param type type + * @param type + * @return resolved instance + * @throws InitializationFailed if the container previusly failed to + * start + */ + @SuppressWarnings("resource") + public T resolveInstance(Class type) throws InitializationFailed { + if (type.isAssignableFrom(SeContainer.class)) { + return type.cast(container()); + } + return container().select(type).get(); + } + + /** + * Test if the given type is supported for injection. + * + * @param type type + * @return {@code true} if supported, {@code false} otherwise + * @throws InitializationFailed if the container previusly failed to + * start + */ + @SuppressWarnings("resource") + public boolean isSupported(Class type) throws InitializationFailed { + if (type.isAssignableFrom(SeContainer.class)) { + return true; + } + return !container().select(type).isUnsatisfied(); + } + + private SeContainer container() { + if (error == null && container == null) { + try { + lock.lock(); + if (error == null && container == null) { + start(); + } + } catch (RuntimeException ex) { + error = ex; + throw ex; + } finally { + lock.unlock(); + } + } + if (error != null) { + throw new InitializationFailed(error); + } + return container; + } + + @SuppressWarnings("unchecked") + private void start() { + LOGGER.log(Level.DEBUG, "starting container\n{0}", this); + if (testInfo.pinningDetection()) { + pinningRecorder = PinningRecorder.create(); + pinningRecorder.record(Duration.ofMillis(testInfo.pinningThreshold())); + } + HelidonTestExtension testExtension = extensionFactory.apply(testInfo, testScope); + SeContainerInitializer initializer = SeContainerInitializer.newInstance(); + if (testInfo.disableDiscovery()) { + initializer.disableDiscovery(); + } + for (AddExtension extension : testInfo.addExtensions()) { + initializer.addExtensions(requirePublic(extension.value())); + } + initializer.addExtensions(testExtension); + container = initializer.initialize(); + } + + @Override + public String toString() { + return new PrettyPrinter() + .object(printer -> printer + .value("id", id) + .object("testInfo", PrettyPrinters.testInfo(testInfo))) + .toString(); + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptor.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptor.java new file mode 100644 index 00000000000..a3974212fc1 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptor.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import jakarta.enterprise.inject.spi.Extension; + +import static io.helidon.common.testing.virtualthreads.PinningRecorder.DEFAULT_THRESHOLD; + +/** + * Describes annotations for a test class or method. + * + * @param element type + */ +public interface HelidonTestDescriptor { + + /** + * Get the annotated element. + * + * @return element + */ + T element(); + + /** + * Get the discovered value of {@code @HelidonTest(resetPerTest = true)}. + * + * @return {@code resetPerTest} value + */ + default boolean resetPerTest() { + return false; + } + + /** + * Get the discovered value of {@code @HelidonTest(pinningDetection = true)}. + * + * @return {@code pinningDetection} value + */ + default boolean pinningDetection() { + return false; + } + + /** + * Get the discovered value of {@code @HelidonTest(pinningThreshold = 50)}. + * + * @return {@code pinningThreshold} value + */ + default long pinningThreshold() { + return DEFAULT_THRESHOLD; + } + + /** + * Get the discovered {@link AddJaxRs} annotation. + * + * @return {@code true} if the annotation is present + */ + boolean addJaxRs(); + + /** + * Get the value of the discovered {@link DisableDiscovery} annotation. + * + * @return {@link DisableDiscovery#value()} or {@code false} if not found + */ + boolean disableDiscovery(); + + /** + * Get the discovered {@link AddExtension} annotations. + * + * @return annotations + */ + List addExtensions(); + + /** + * Get the discovered {@link AddBean} annotations. + * + * @return annotations + */ + List addBeans(); + + /** + * Get the discovered {@link Configuration} annotation. + * + * @return annotation + */ + Optional configuration(); + + /** + * Get the discovered {@link AddConfig} annotations. + * + * @return annotations + */ + List addConfigs(); + + /** + * Get the discovered {@link AddConfigBlock} annotations. + * + * @return annotations + */ + List addConfigBlocks(); + + /** + * Get the discovered {@link AddConfigSource} methods. + * + * @return annotations + */ + List addConfigSources(); + + /** + * Test if the given extension is configured. + * + * @param type extension type + * @return {@code true} if configured, {@code false} otherwise + */ + default boolean containsExtension(Class type) { + return addExtensions().stream() + .map(AddExtension::value) + .anyMatch(Predicate.isEqual(type)); + } + + /** + * Get annotations. + * + * @param aType annotation type + * @param cType annotation container type + * @param function function to inflate from container + * @param annotation type + * @param container type + * @return annotations + */ + Stream annotations(Class aType, + Class cType, + Function function); + + /** + * Get annotations. + * + * @param aType annotation type + * @param annotation type + * @return annotations + */ + Stream annotations(Class aType); + + /** + * Test if an annotation of the given type is found. + * + * @param aType annotation type + * @return {@code true} if found + */ + default boolean containsAnnotation(Class aType) { + return annotations(aType).findFirst().isPresent(); + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptorBase.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptorBase.java new file mode 100644 index 00000000000..78ad190c023 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptorBase.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import io.helidon.common.LazyValue; +import io.helidon.microprofile.testing.ReflectionHelper.Annotated; + +import static io.helidon.common.testing.virtualthreads.PinningRecorder.DEFAULT_THRESHOLD; +import static io.helidon.microprofile.testing.ReflectionHelper.annotated; +import static io.helidon.microprofile.testing.ReflectionHelper.filterAnnotated; +import static io.helidon.microprofile.testing.ReflectionHelper.filterAnnotations; + +/** + * Base implementation. + * + * @param annotated element type + */ +public abstract class HelidonTestDescriptorBase implements HelidonTestDescriptor { + + private final T element; + private final List> annotated; + private final LazyValue resetPerTest = LazyValue.create(this::lookupResetPerTest); + private final LazyValue pinningDetection = LazyValue.create(this::lookupPinningDetection); + private final LazyValue pinningThreshold = LazyValue.create(this::lookupPinningThreshold); + private final LazyValue> addExtensions = LazyValue.create(this::lookupAddExtensions); + private final LazyValue> addBeans = LazyValue.create(this::lookupAddBeans); + private final LazyValue addJaxRs = LazyValue.create(this::lookupAddJaxRs); + private final LazyValue disableDiscovery = LazyValue.create(this::lookupDisableDiscovery); + private final LazyValue> addConfigs = LazyValue.create(this::lookupAddConfigs); + private final LazyValue> addConfigBlocks = LazyValue.create(this::lookupAddConfigBlocks); + private final LazyValue> addConfigSources = LazyValue.create(this::lookupAddConfigSources); + private final LazyValue> configuration = LazyValue.create(this::lookupConfiguration); + + /** + * Create a new instance. + * + * @param element element + */ + protected HelidonTestDescriptorBase(T element) { + this.element = element; + this.annotated = switch (element) { + case Class c -> annotated(c); + case Method m -> annotated(m); + default -> throw new IllegalArgumentException("Unsupported element: " + element); + }; + } + + @Override + public T element() { + return element; + } + + @Override + public boolean resetPerTest() { + return resetPerTest.get(); + } + + @Override + public boolean pinningDetection() { + return pinningDetection.get(); + } + + @Override + public long pinningThreshold() { + return pinningThreshold.get(); + } + + @Override + public List addExtensions() { + return addExtensions.get(); + } + + @Override + public List addBeans() { + return addBeans.get(); + } + + @Override + public boolean addJaxRs() { + return addJaxRs.get(); + } + + @Override + public boolean disableDiscovery() { + return disableDiscovery.get(); + } + + @Override + public Optional configuration() { + return configuration.get(); + } + + @Override + public List addConfigs() { + return addConfigs.get(); + } + + @Override + public List addConfigBlocks() { + return addConfigBlocks.get(); + } + + /** + * Get the discovered {@link AddConfigSource} methods. + * + * @return annotations + */ + public List addConfigSources() { + return addConfigSources.get(); + } + + /** + * Lookup the value of {@code @HelidonTest(resetPerTest = true)}. + * + * @return {@code resetPerTest} value + */ + protected boolean lookupResetPerTest() { + return false; + } + + /** + * Lookup the value of {@code @HelidonTest(pinningDetection = true)}. + * + * @return {@code pinningDetection} value + */ + protected boolean lookupPinningDetection() { + return false; + } + + /** + * Lookup the value of {@code @HelidonTest(pinningThreshold = 50)}. + * + * @return {@code pinningThreshold} value + */ + protected long lookupPinningThreshold() { + return DEFAULT_THRESHOLD; + } + + /** + * Lookup the {@link AddExtension} annotations. + * + * @return annotations + */ + protected List lookupAddExtensions() { + return annotations(AddExtension.class, AddExtensions.class, AddExtensions::value) + .toList(); + } + + /** + * Lookup the {@link AddBean} annotations. + * + * @return annotations + */ + protected List lookupAddBeans() { + return annotations(AddBean.class, AddBeans.class, AddBeans::value) + .toList(); + } + + /** + * Lookup the {@link AddJaxRs} annotation. + * + * @return annotation + */ + protected boolean lookupAddJaxRs() { + return annotations(AddJaxRs.class) + .findFirst() + .isPresent(); + } + + /** + * Lookup the {@link DisableDiscovery} annotation. + * + * @return annotation + */ + protected boolean lookupDisableDiscovery() { + return annotations(DisableDiscovery.class) + .findFirst() + .map(DisableDiscovery::value) + .orElse(false); + } + + /** + * Lookup the {@link Configuration} annotation. + * + * @return annotation + */ + protected Optional lookupConfiguration() { + return annotations(Configuration.class) + .findFirst(); + } + + /** + * Lookup the {@link AddConfig} annotations. + * + * @return annotations + */ + protected List lookupAddConfigs() { + return annotations(AddConfig.class, AddConfigs.class, AddConfigs::value) + .toList(); + } + + /** + * Lookup the {@link AddConfigBlock} annotations. + * + * @return annotations + */ + protected List lookupAddConfigBlocks() { + return annotations(AddConfigBlock.class, AddConfigBlocks.class, AddConfigBlocks::value) + .toList(); + } + + @Override + public Stream annotations(Class aType, + Class cType, + Function function) { + return filterAnnotations(annotated, aType, cType, function); + } + + @Override + public Stream annotations(Class aType) { + return filterAnnotations(annotated, aType); + } + + private List lookupAddConfigSources() { + return filterAnnotated(annotated, AddConfigSource.class) + .map(Annotated::element) + .map(Method.class::cast) + .toList(); + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptorDelegate.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptorDelegate.java new file mode 100644 index 00000000000..66686df2c5b --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptorDelegate.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Descriptor delegate. + * + * @param element type + */ +class HelidonTestDescriptorDelegate implements HelidonTestDescriptor { + + private final HelidonTestDescriptor delegate; + + HelidonTestDescriptorDelegate(HelidonTestDescriptor delegate) { + this.delegate = delegate; + } + + @Override + public T element() { + return delegate.element(); + } + + @Override + public boolean resetPerTest() { + return delegate.resetPerTest(); + } + + @Override + public boolean pinningDetection() { + return delegate.pinningDetection(); + } + + @Override + public long pinningThreshold() { + return delegate.pinningThreshold(); + } + + @Override + public List addExtensions() { + return delegate.addExtensions(); + } + + @Override + public List addBeans() { + return delegate.addBeans(); + } + + @Override + public boolean addJaxRs() { + return delegate.addJaxRs(); + } + + @Override + public boolean disableDiscovery() { + return delegate.disableDiscovery(); + } + + @Override + public Optional configuration() { + return delegate.configuration(); + } + + @Override + public List addConfigs() { + return delegate.addConfigs(); + } + + @Override + public List addConfigBlocks() { + return delegate.addConfigBlocks(); + } + + @Override + public List addConfigSources() { + return delegate.addConfigSources(); + } + + @Override + public Stream annotations(Class aType, + Class cType, + Function function) { + return delegate.annotations(aType, cType, function); + } + + @Override + public Stream annotations(Class aType) { + return delegate.annotations(aType); + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptorImpl.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptorImpl.java new file mode 100644 index 00000000000..3046cbddeb1 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestDescriptorImpl.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.reflect.AnnotatedElement; + +/** + * Default descriptor implementation. + */ +final class HelidonTestDescriptorImpl extends HelidonTestDescriptorBase { + + HelidonTestDescriptorImpl(T element) { + super(element); + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestExtension.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestExtension.java new file mode 100644 index 00000000000..baef3763122 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestExtension.java @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import io.helidon.common.LazyValue; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.Destroyed; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AfterBeanDiscovery; +import jakarta.enterprise.inject.spi.Annotated; +import jakarta.enterprise.inject.spi.AnnotatedConstructor; +import jakarta.enterprise.inject.spi.AnnotatedField; +import jakarta.enterprise.inject.spi.AnnotatedMethod; +import jakarta.enterprise.inject.spi.AnnotatedParameter; +import jakarta.enterprise.inject.spi.AnnotatedType; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.ProcessAnnotatedType; +import jakarta.enterprise.inject.spi.WithAnnotations; +import jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.inject.Singleton; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; + +import static io.helidon.microprofile.testing.ReflectionHelper.annotationHierarchy; +import static io.helidon.microprofile.testing.ReflectionHelper.invoke; +import static io.helidon.microprofile.testing.ReflectionHelper.isOverride; +import static io.helidon.microprofile.testing.ReflectionHelper.requireStatic; +import static io.helidon.microprofile.testing.ReflectionHelper.typeHierarchy; +import static jakarta.interceptor.Interceptor.Priority.PLATFORM_AFTER; +import static jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE; + +/** + * Helidon test CDI extension. + */ +public abstract class HelidonTestExtension implements Extension { + + private static final Map, Annotation> ANNOTATION_LITERALS = Map.of( + ApplicationScoped.class, ApplicationScoped.Literal.INSTANCE, + Singleton.class, ApplicationScoped.Literal.INSTANCE, + RequestScoped.class, RequestScoped.Literal.INSTANCE, + Dependent.class, Dependent.Literal.INSTANCE); + + private static final Set> TYPE_ANNOTATION_TYPES = Set.of( + AddConfig.class, + AddConfigs.class, + AddConfigBlock.class, + Configuration.class); + + private static final Set> PARAMETER_ANNOTATION_TYPES = Set.of( + Socket.class); + + private static final Set> FIELD_ANNOTATION_TYPES = Set.of( + Socket.class); + + private static final Set> METHOD_ANNOTATION_TYPES = Set.of( + AddConfig.class, + AddConfigs.class, + AddConfigBlock.class, + AddConfigSource.class, + AfterStop.class, + Configuration.class); + + private final HelidonTestInfo testInfo; + private final HelidonTestConfig testConfig; + private final HelidonTestScope testScope; + private final Map sockets = new HashMap<>(); + private final List afterStop = new ArrayList<>(); + + /** + * Create a new instance. + * + * @param testInfo test info + * @param testScope test scope + */ + protected HelidonTestExtension(HelidonTestInfo testInfo, HelidonTestScope testScope) { + this.testInfo = testInfo; + this.testConfig = new HelidonTestConfig(testInfo); + this.testScope = testScope; + } + + /** + * Get the annotation types usable on type. + * + * @return annotations + */ + protected Set> typeAnnotationTypes() { + return TYPE_ANNOTATION_TYPES; + } + + /** + * Get the annotation types usable on parameters. + * + * @return annotation types + */ + protected Set> parameterAnnotationTypes() { + return PARAMETER_ANNOTATION_TYPES; + } + + /** + * Get the annotation types usable on fields. + * + * @return annotation types + */ + protected Set> fieldAnnotationTypes() { + return FIELD_ANNOTATION_TYPES; + } + + /** + * Get the annotation types usable on methods. + * + * @return annotation types + */ + protected Set> methodAnnotationTypes() { + return METHOD_ANNOTATION_TYPES; + } + + /** + * Process a type annotation. + * + * @param annotation annotation + */ + protected void processTypeAnnotation(Annotation annotation) { + switch (annotation) { + case Configuration e -> processConfiguration(e); + case AddConfig e -> processAddConfig(e); + case AddConfigs e -> processAddConfig(e.value()); + case AddConfigBlock e -> processAddConfigBlock(e); + case AddConfigBlocks e -> processAddConfigBlock(e.value()); + default -> { + // no-op + } + } + } + + /** + * Process a parameter annotation. + * + * @param annotation annotation + */ + protected void processParameterAnnotation(Annotation annotation) { + if (annotation instanceof Socket s) { + processSocket(s, s.value()); + } + } + + /** + * Process a field annotation. + * + * @param annotation annotation + */ + protected void processFieldAnnotation(Annotation annotation) { + if (annotation instanceof Socket s) { + processSocket(s, s.value()); + } + } + + /** + * Process a static method annotation. + * + * @param annotation annotation + * @param method method + */ + protected void processStaticMethodAnnotation(Annotation annotation, Method method) { + if (Objects.requireNonNull(annotation) instanceof AddConfigSource) { + processAddConfigSource(method); + } else if (annotation instanceof AfterStop) { + processAfterStop(method); + } else { + throw new IllegalStateException(String.format( + "@%s requires method %s to be non static", + method, annotation.annotationType().getSimpleName())); + } + } + + /** + * Process a test method annotation. + * + * @param annotation annotation + * @param method method + */ + protected void processTestMethodAnnotation(Annotation annotation, Method method) { + switch (annotation) { + case Configuration e -> processConfiguration(e); + case AddConfig e -> processAddConfig(e); + case AddConfigs e -> processAddConfig(e.value()); + case AddConfigBlock e -> processAddConfigBlock(e); + case AddConfigBlocks e -> processAddConfigBlock(e.value()); + default -> throw new IllegalStateException(String.format( + "@%s requires method %s to be static", + method, annotation.annotationType().getSimpleName())); + } + } + + /** + * Process a {@link Configuration} annotation. + * + * @param annotation annotation + */ + protected final void processConfiguration(Configuration annotation) { + testConfig.synthetic().update(annotation); + } + + /** + * Process {@link AddConfig Configuration} annotations. + * + * @param annotations annotations + */ + protected final void processAddConfig(AddConfig... annotations) { + testConfig.synthetic().update(annotations); + } + + /** + * Process {@link AddConfig Configuration} annotations. + * + * @param annotations annotations + */ + protected final void processAddConfigBlock(AddConfigBlock... annotations) { + testConfig.synthetic().update(annotations); + } + + /** + * Process a {@link AddConfigSource} method. + * + * @param method method + */ + protected final void processAddConfigSource(Method method) { + testConfig.synthetic().update(method); + } + + /** + * Process a {@link AfterStop} method. + * + * @param method method + */ + protected final void processAfterStop(Method method) { + afterStop.add(requireStatic(method)); + } + + /** + * Process a {@link Socket} annotation. + * + * @param annotation annotation + * @param value value + */ + protected final void processSocket(Annotation annotation, String value) { + sockets.put(annotation, value); + } + + private void processTestClass(@Observes + @WithAnnotations(HelidonTestScoped.class) + ProcessAnnotatedType pat, + BeanManager bm) { + + Set> allTypes = new HashSet<>(); + allTypes.add(pat.getAnnotatedType()); + + // create annotated types for the type hierarchy + for (Class type : typeHierarchy(pat.getAnnotatedType().getJavaClass(), false)) { + allTypes.add(bm.createAnnotatedType(type)); + } + + Method testMethod = testInfo.testMethod().orElse(null); + for (AnnotatedType type : allTypes) { + + // type + processAnnotated(type, typeAnnotationTypes(), this::processTypeAnnotation); + + // constructor parameters + for (AnnotatedConstructor constructor : type.getConstructors()) { + for (AnnotatedParameter parameter : constructor.getParameters()) { + processAnnotated(parameter, fieldAnnotationTypes(), this::processParameterAnnotation); + } + } + + // fields + for (AnnotatedField field : type.getFields()) { + processAnnotated(field, parameterAnnotationTypes(), this::processFieldAnnotation); + } + + // methods + for (AnnotatedMethod method : type.getMethods()) { + processAnnotated(method, methodAnnotationTypes(), a -> { + if (method.isStatic()) { + processStaticMethodAnnotation(a, method.getJavaMember()); + } else { + // test method or super test method + if (testMethod != null && isOverride(method.getJavaMember(), testMethod)) { + processTestMethodAnnotation(a, method.getJavaMember()); + } + } + }); + } + } + } + + private void beforeBeanDiscovery(@Observes BeforeBeanDiscovery event, BeanManager bm) { + // remove bootstrap config + testConfig.resolve(); + + // add the test class + event.addAnnotatedType(testInfo.testClass(), "HelidonTest") + .add(HelidonTestScoped.Literal.INSTANCE); + + for (AddBean addBean : testInfo.addBeans()) { + Class beanClass = addBean.value(); + Class scopeClass = addBean.scope(); + AnnotatedTypeConfigurator configurator = event.addAnnotatedType(beanClass, beanClass.getName()); + + // process scope + if (!scopeClass.equals(Annotation.class)) { + // scope explicitly configured + Annotation scope = ANNOTATION_LITERALS.get(scopeClass); + if (scope == null) { + scope = new AnnotationLiteral<>() { + @Override + public Class annotationType() { + return scopeClass; + } + }; + } + // remove existing scope annotations + configurator.remove(a -> bm.isScope(a.annotationType())); + configurator.add(scope); + } else { + // no scope configured + AnnotatedType annotated = configurator.getAnnotated(); + if (annotated.getAnnotations().stream() + .noneMatch(a -> bm.isScope(a.annotationType()))) { + + // bean class does not have a scope annotation + // default to ApplicationScoped + configurator.add(ApplicationScoped.Literal.INSTANCE); + } + } + } + } + + private void afterBeanDiscovery(@Observes + @Priority(PLATFORM_BEFORE) + AfterBeanDiscovery event, + BeanManager bm) { + + // useExisting may have changed, re-resolve + testConfig.resolve(); + + // test scope support + event.addContext(testScope); + + Class serverClass = serverClass(); + if (serverClass != null && (!testInfo.disableDiscovery() || testInfo.containsExtension(serverClass))) { + + Extension server = bm.getExtension(serverClass); + Client client = ClientBuilder.newClient(); + + // default port + event.addBean() + .addTransitiveTypeClosure(WebTarget.class) + .scope(ApplicationScoped.class) + .createWith(c -> client.target("http://localhost:" + port(server, "@default"))); + + // named ports + sockets.forEach((annotation, value) -> { + + Supplier supplier = () -> "http://localhost:" + port(server, value); + + event.addBean() + .addTransitiveTypeClosure(WebTarget.class) + .scope(ApplicationScoped.class) + .qualifiers(annotation) + .createWith(c -> client.target(supplier.get())); + + // unproxiable types, use dependent backed by lazy values + + LazyValue uri = LazyValue.create(() -> URI.create(supplier.get())); + event.addBean() + .addType(URI.class) + .scope(Dependent.class) + .qualifiers(annotation) + .createWith(c -> uri.get()); + + LazyValue rawUri = LazyValue.create(supplier); + event.addBean() + .addType(String.class) + .scope(Dependent.class) + .qualifiers(annotation) + .createWith(c -> rawUri.get()); + }); + } + } + + private void afterStop(@Observes + @Priority(PLATFORM_AFTER) + @Destroyed(ApplicationScoped.class) + Object ignored) { + + afterStop.forEach(m -> invoke(Void.class, m, null)); + } + + private void processAnnotated(Annotated elt, Set> types, Consumer action) { + for (Annotation a : elt.getAnnotations()) { + if (types.contains(a.annotationType())) { + action.accept(a); + } else { + // meta-annotations + for (Annotation b : annotationHierarchy(a.annotationType())) { + if (types.contains(b.annotationType())) { + action.accept(b); + } + } + } + } + } + + @SuppressWarnings("unchecked") + private static Class serverClass() { + try { + return (Class) Class.forName("io.helidon.microprofile.server.ServerCdiExtension"); + } catch (ClassNotFoundException ignored) { + return null; + } + } + + private static int port(Extension server, String name) { + try { + Method portMethod = server.getClass().getMethod("port", String.class); + return invoke(Integer.class, portMethod, server, name); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestInfo.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestInfo.java new file mode 100644 index 00000000000..8c3b16a241e --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestInfo.java @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.lang.ref.SoftReference; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Stream; + +import static io.helidon.microprofile.testing.Instrumented.isInstrumented; +import static java.util.stream.Collectors.joining; + +/** + * Metadata of the test class or method that triggers the creation of a CDI container. + * + * @param element type + */ +public sealed interface HelidonTestInfo extends HelidonTestDescriptor + permits HelidonTestInfo.ClassInfo, + HelidonTestInfo.MethodInfo { + + /** + * Create a new class info. + * + * @param cls class + * @return ClassInfo + */ + static ClassInfo classInfo(Class cls) { + return classInfo(cls, HelidonTestDescriptorImpl::new); + } + + /** + * Create a new class info. + * + * @param cls class + * @param function descriptor factory + * @return ClassInfo + */ + static ClassInfo classInfo(Class cls, Function, HelidonTestDescriptor>> function) { + Class theClass = isInstrumented(cls) ? cls.getSuperclass() : cls; + return ClassInfo.CACHE.compute(theClass.getName(), (e, r) -> { + if (r == null || r.get() == null) { + return new SoftReference<>(new ClassInfo(function.apply(cls))); + } + return r; + }).get(); + } + + /** + * Create a new class info. + * + * @param desc class descriptor + * @return ClassInfo + */ + static ClassInfo classInfo(HelidonTestDescriptor> desc) { + return ClassInfo.CACHE.compute(desc.element().getName(), (e, r) -> { + if (r == null || r.get() == null) { + return new SoftReference<>(new ClassInfo(desc)); + } + return r; + }).get(); + } + + /** + * Create a new method info. + * + * @param element method + * @param classInfo class info + * @return MethodInfo + */ + static MethodInfo methodInfo(Method element, ClassInfo classInfo) { + return methodInfo(element, classInfo, HelidonTestDescriptorImpl::new); + } + + /** + * Create a new method info. + * + * @param method method + * @param classInfo class info + * @param function descriptor factory + * @return MethodInfo + */ + static MethodInfo methodInfo(Method method, ClassInfo classInfo, Function> function) { + return MethodInfo.CACHE.compute(MethodInfo.cacheKey(method, classInfo), (e, r) -> { + if (r == null || r.get() == null) { + return new SoftReference<>(new MethodInfo(function.apply(method), classInfo)); + } + return r; + }).get(); + } + + /** + * Create a new method info. + * + * @param desc method descriptor + * @param classInfo class info + * @return MethodInfo + */ + static MethodInfo methodInfo(HelidonTestDescriptor desc, ClassInfo classInfo) { + return MethodInfo.CACHE.compute(MethodInfo.cacheKey(desc.element(), classInfo), (e, r) -> { + if (r == null || r.get() == null) { + return new SoftReference<>(new MethodInfo(desc, classInfo)); + } + return r; + }).get(); + } + + /** + * Get the id. + * + * @return id + */ + String id(); + + /** + * Get the test class. + * + * @return test class + */ + Class testClass(); + + /** + * Get the test method. + * + * @return test method + */ + default Optional testMethod() { + return Optional.empty(); + } + + /** + * Get the class info. + * + * @return ClassInfo + */ + ClassInfo classInfo(); + + /** + * Indicate if the container should be reset. + * For a class this is resolved via {@code HelidonTest#resetPerTest()}. + * For a method this is inferred if any of the following annotations is used: + *

+ * + * @return {@code true} if reset is required, {@code false} otherwise + */ + default boolean requiresReset() { + return false; + } + + /** + * Class info. + */ + final class ClassInfo extends HelidonTestDescriptorDelegate> implements HelidonTestInfo> { + private static final Map> CACHE = new ConcurrentHashMap<>(); + + private ClassInfo(HelidonTestDescriptor> descriptor) { + super(descriptor); + } + + @Override + public String id() { + return element().getName(); + } + + @Override + public Class testClass() { + return element(); + } + + @Override + public ClassInfo classInfo() { + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClassInfo that)) { + return false; + } + return Objects.equals(element().getName(), that.element().getName()); + } + + @Override + public int hashCode() { + return element().getName().hashCode(); + } + + @Override + public String toString() { + return new PrettyPrinter() + .object(PrettyPrinters.classInfo(this)) + .toString(); + } + } + + /** + * Method info. + */ + final class MethodInfo implements HelidonTestInfo { + private static final Map> CACHE = new ConcurrentHashMap<>(); + + private final HelidonTestDescriptor descriptor; + private final ClassInfo classInfo; + + private MethodInfo(HelidonTestDescriptor descriptor, ClassInfo classInfo) { + this.descriptor = descriptor; + this.classInfo = classInfo; + } + + private static String cacheKey(Method method, ClassInfo classInfo) { + return classInfo.element().getName() + "#" + + method.getName() + + Arrays.stream(method.getParameterTypes()) + .map(Type::getTypeName) + .collect(joining(",", "(", ")")); + } + + @Override + public String id() { + return classInfo.id() + "#" + element().getName(); + } + + @Override + public Class testClass() { + return classInfo.element(); + } + + @Override + public Optional testMethod() { + return Optional.of(descriptor.element()); + } + + @Override + public ClassInfo classInfo() { + return classInfo; + } + + @Override + public Method element() { + return descriptor.element(); + } + + @Override + public List addExtensions() { + return concat(classInfo.addExtensions(), descriptor.addExtensions()); + } + + @Override + public List addBeans() { + return concat(classInfo.addBeans(), descriptor.addBeans()); + } + + @Override + public boolean addJaxRs() { + return classInfo.addJaxRs() || descriptor.addJaxRs(); + } + + @Override + public boolean disableDiscovery() { + return classInfo.disableDiscovery() || descriptor.disableDiscovery(); + } + + @Override + public Optional configuration() { + return descriptor.configuration(); + } + + @Override + public List addConfigs() { + return concat(classInfo.addConfigs(), descriptor.addConfigs()); + } + + @Override + public List addConfigBlocks() { + return concat(classInfo.addConfigBlocks(), descriptor.addConfigBlocks()); + } + + @Override + public List addConfigSources() { + return classInfo.addConfigSources(); + } + + @Override + public Stream annotations(Class aType, + Class cType, + Function function) { + return Stream.concat( + descriptor.annotations(aType, cType, function), + classInfo.annotations(aType, cType, function)); + } + + @Override + public Stream annotations(Class aType) { + return Stream.concat( + descriptor.annotations(aType), + classInfo.annotations(aType)); + } + + @Override + public boolean requiresReset() { + return classInfo.resetPerTest() + || descriptor.configuration().isPresent() + || descriptor.disableDiscovery() + || descriptor.addJaxRs() + || !descriptor.addBeans().isEmpty() + || !descriptor.addExtensions().isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MethodInfo that)) { + return false; + } + return Objects.equals(element().getName(), that.element().getName()); + } + + @Override + public int hashCode() { + return element().hashCode(); + } + + @Override + public String toString() { + return new PrettyPrinter() + .object(PrettyPrinters.methodInfo(this)) + .toString(); + } + + private static List concat(List l1, List l2) { + return Stream.concat(l1.stream(), l2.stream()).toList(); + } + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestScope.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestScope.java new file mode 100644 index 00000000000..2310da28cc9 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestScope.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import jakarta.enterprise.context.spi.Context; +import jakarta.enterprise.context.spi.Contextual; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.spi.Bean; + +/** + * CDI context that supports {@link HelidonTestScoped}. + */ +public abstract sealed class HelidonTestScope implements Context permits HelidonTestScope.PerThread, + HelidonTestScope.PerContainer { + + /** + * Create a new per-thread scope. + * + * @return HelidonTestScope + */ + public static HelidonTestScope ofThread() { + return new PerThread(); + } + + /** + * Create a new per-container scope. + * + * @return HelidonTestScope + */ + public static HelidonTestScope ofContainer() { + return new PerContainer(); + } + + @Override + public Class getScope() { + return HelidonTestScoped.class; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public T get(Contextual contextual, CreationalContext context) { + return instances().get(contextual, context); + } + + @Override + public T get(Contextual contextual) { + return instances().get(contextual).orElse(null); + } + + /** + * Close the scope. + */ + public void close() { + instances().destroy(); + } + + /** + * Get the instances. + * + * @return instances + */ + abstract Instances instances(); + + /** + * Instances per thread. + */ + static final class PerThread extends HelidonTestScope { + private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(Instances::new); + + @Override + Instances instances() { + return THREAD_LOCAL.get(); + } + + @Override + public void close() { + THREAD_LOCAL.remove(); + super.close(); + } + } + + /** + * Instances per container. + */ + static final class PerContainer extends HelidonTestScope { + private final Instances instances = new Instances(new ConcurrentHashMap<>()); + + @Override + Instances instances() { + return instances; + } + } + + private record Instances(Map, Instance> map) { + + Instances() { + this(new HashMap<>()); + } + + @SuppressWarnings("unchecked") + Instance create(Contextual contextual, CreationalContext context) { + return (Instance) map.computeIfAbsent(contextual, k -> new Instance<>((Bean) k, context)); + } + + T get(Contextual contextual, CreationalContext context) { + return get(contextual).orElseGet(() -> create(contextual, context).it); + } + + @SuppressWarnings("unchecked") + Optional get(Contextual contextual) { + return Optional.ofNullable((Instance) map.get(contextual)).map(Instance::it); + } + + void destroy() { + map.values().forEach(Instance::destroy); + } + } + + private record Instance(Bean bean, CreationalContext context, T it) { + Instance(Bean bean, CreationalContext context) { + this(bean, context, bean.create(context)); + } + + void destroy() { + bean.destroy(it, context); + } + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestScoped.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestScoped.java new file mode 100644 index 00000000000..b4f61e3f0e0 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestScoped.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.io.Serial; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import jakarta.enterprise.context.NormalScope; +import jakarta.enterprise.util.AnnotationLiteral; + +/** + * CDI scope used for the test class. + */ +@NormalScope +@Retention(RetentionPolicy.RUNTIME) +@interface HelidonTestScoped { + + /** + * Annotation literal. + */ + @SuppressWarnings("ALL") + final class Literal extends AnnotationLiteral implements HelidonTestScoped { + + static final Literal INSTANCE = new Literal(); + + @Serial + private static final long serialVersionUID = 1L; + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Instrumented.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Instrumented.java new file mode 100644 index 00000000000..8be50615c95 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Instrumented.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; +import net.bytebuddy.implementation.InvocationHandlerAdapter; +import net.bytebuddy.implementation.attribute.MethodAttributeAppender; +import net.bytebuddy.matcher.ElementMatcher; + +import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith; +import static net.bytebuddy.matcher.ElementMatchers.isEquals; +import static net.bytebuddy.matcher.ElementMatchers.isHashCode; +import static net.bytebuddy.matcher.ElementMatchers.isToString; +import static net.bytebuddy.matcher.ElementMatchers.not; + +/** + * Marker interface for instrumented type. + */ +public interface Instrumented { + + /** + * Instantiate a class without running constructors. + * + * @param type type + * @param type + * @return instance + */ + static T allocateInstance(Class type) { + try { + Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + sun.misc.Unsafe unsafe = (sun.misc.Unsafe) field.get(null); + return type.cast(unsafe.allocateInstance(type)); + } catch (InstantiationException + | IllegalAccessException + | NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + /** + * Test if the given type is instrumented. + * + * @param type type + * @return {@code true} if instrumented, {@code false} otherwise + */ + static boolean isInstrumented(Class type) { + return Set.of(type.getInterfaces()).contains(Instrumented.class); + } + + /** + * Create an instrumented class. + * + * @param type type + * @param annotations annotations to add to the proxy class + * @param methodExcludes method annotations that skip interception + * @param resolver function to resolve the delegate + * @param type + * @return instrumented class + */ + static Class instrument(Class type, + List annotations, + List> methodExcludes, + BiFunction, Method, T> resolver) { + + // always skip delegation for equals, hashCode, toString + // the goal is to instantiate the delegate for "concrete" methods + ElementMatcher.Junction matcher = not(isEquals()) + .and(not(isHashCode())) + .and(not(isToString())); + + // also skip delegation for methods annotated with the given annotations + for (Class exclude : methodExcludes) { + matcher = matcher.and(not(isAnnotatedWith(exclude))); + } + + try (DynamicType.Unloaded unloaded = new ByteBuddy() + // preserve constructors annotations + // must-have for constructor injection + .subclass(type, ConstructorStrategy.Default.IMITATE_SUPER_CLASS.withInheritedAnnotations()) + .implement(Instrumented.class) + // repeat type annotations and add the given annotations + .annotateType(Stream.concat( + Arrays.stream(type.getDeclaredAnnotations()), + annotations.stream()) + .toArray(Annotation[]::new)) + .withHashCodeEquals() + .method(matcher) + .intercept(InvocationHandlerAdapter.of((proxy, method, args) -> { + T instance = resolver.apply(type, method); + if (instance != null) { + try { + method.setAccessible(true); + return method.invoke(instance, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + return null; + })) + // repeat all method annotations on the subclass + .attribute(MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER) + .make()) { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(type, MethodHandles.lookup()); + return unloaded.load(type.getClassLoader(), ClassLoadingStrategy.UsingLookup.of(lookup)) + .getLoaded(); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/PrettyPrinter.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/PrettyPrinter.java new file mode 100644 index 00000000000..98dab5155a5 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/PrettyPrinter.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Pretty printer. + */ +@SuppressWarnings({"UnusedReturnValue", "SameParameterValue"}) +final class PrettyPrinter { + + private final StringBuilder sb = new StringBuilder(); + private int i = 0; + + /** + * Empty consumer. + */ + static final Consumer EMPTY = printer -> { + }; + + /** + * Print a name. + * + * @param name name + * @return this instance + */ + PrettyPrinter name(String name) { + if (name != null && !name.isEmpty()) { + value(name).append(" = "); + } + return this; + } + + /** + * Append a value. + * + * @param value value + * @return this instance + */ + PrettyPrinter value(Object value) { + return append('\n').indent().append(value); + } + + /** + * Append a named value. + * + * @param key key + * @param value value + * @return this instance + */ + PrettyPrinter value(String key, Object value) { + return name(key).append(value); + } + + /** + * Append a named block. + * + * @param key key + * @param block block + * @return this instance + */ + PrettyPrinter block(String key, String block) { + name(key).append("<<"); + block.lines().forEach(this::value); + value(">>"); + return this; + } + + /** + * Append an object structure. + * + * @param action action + * @return this instance + */ + PrettyPrinter object(Consumer action) { + append('{') + .indent(() -> action.accept(this)) + .append('\n').indent().append('}'); + return this; + } + + /** + * Append a named object structure. + * No-op if {@code action} is {@link #EMPTY}. + * + * @param action action + * @return this instance + */ + PrettyPrinter object(String name, Consumer action) { + if (action != EMPTY) { + name(name).object(action); + } + return this; + } + + /** + * Repeat a named object structure for the given list. + * + * @param name name, may be {@code null} + * @param list list + * @param function function + * @param list element type + * @return this instance + */ + PrettyPrinter objects(String name, List list, Function> function) { + if (list != null) { + list.forEach(e -> object(name, function.apply(e))); + } + return this; + } + + /** + * Append a named list structure. + * + * @param name name, may be {@code null} + * @param list list + * @param mapper mapper + * @param list element type + * @return this instance + */ + PrettyPrinter values(String name, List list, Function mapper) { + if (list != null && !list.isEmpty()) { + name(name).append('[') + .indent(() -> list.forEach(e -> value(mapper.apply(e)))) + .append('\n').indent().append(']'); + } + return this; + } + + /** + * Append a named list structure. + * + * @param name name, may be {@code null} + * @param values values + * @return this instance + */ + @SafeVarargs + final PrettyPrinter values(String name, T... values) { + return values(name, Arrays.asList(values), e -> e); + } + + /** + * Delegate to the given action. + * + * @param action action + * @return this instance + */ + PrettyPrinter apply(Consumer action) { + action.accept(this); + return this; + } + + @Override + public String toString() { + return sb.toString(); + } + + private PrettyPrinter append(Object object) { + sb.append(object); + return this; + } + + private PrettyPrinter indent() { + sb.append(" ".repeat(i)); + return this; + } + + private PrettyPrinter indent(Runnable action) { + try { + i++; + action.run(); + return this; + } finally { + i--; + } + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/PrettyPrinters.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/PrettyPrinters.java new file mode 100644 index 00000000000..3f8e18c736f --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/PrettyPrinters.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.reflect.Method; +import java.util.function.Consumer; + +import io.helidon.microprofile.testing.HelidonTestInfo.ClassInfo; +import io.helidon.microprofile.testing.HelidonTestInfo.MethodInfo; + +/** + * Pretty printers. + */ +class PrettyPrinters { + + private PrettyPrinters() { + } + + /** + * Create a {@link HelidonTestInfo} printer. + * + * @param info info + * @return printer consumer + */ + static Consumer testInfo(HelidonTestInfo info) { + return switch (info) { + case ClassInfo classInfo -> classInfo(classInfo); + case MethodInfo methodInfo -> methodInfo(methodInfo); + }; + } + + /** + * Create a {@link MethodInfo} printer. + * + * @param info info + * @return printer consumer + */ + static Consumer methodInfo(MethodInfo info) { + return printer -> printer + .value("class", info.classInfo().element().getName()) + .value("method", info.element().getName()) + .value("requiresReset", info.requiresReset()) + .apply(testDescriptor(info)); + } + + /** + * Create a {@link ClassInfo} printer. + * + * @param info info + * @return printer consumer + */ + static Consumer classInfo(ClassInfo info) { + return printer -> printer + .value("class", info.element().getName()) + .apply(testDescriptor(info)); + } + + /** + * Create a {@link HelidonTestDescriptor} printer. + * + * @param desc descriptor + * @return printer consumer + */ + static Consumer testDescriptor(HelidonTestDescriptor desc) { + return printer -> printer + .value("resetPerTest", desc.resetPerTest()) + .value("pinningDetection", desc.pinningDetection()) + .value("pinningThreshold", desc.pinningThreshold()) + .value("disableDiscovery", desc.disableDiscovery()) + .values("addExtensions", desc.addExtensions(), a -> a.value().getName()) + .values("addBeans", desc.addBeans(), a -> a.value().getName()) + .object("configuration", desc.configuration() + .map(PrettyPrinters::configuration) + .orElse(PrettyPrinter.EMPTY)) + .objects("addConfig", desc.addConfigs(), PrettyPrinters::addConfig) + .objects("addConfigBlock", desc.addConfigBlocks(), PrettyPrinters::addConfigBlock) + .values("addConfigSource", desc.addConfigSources(), Method::getName); + } + + /** + * Create a {@link Configuration} printer. + * + * @param a annotation + * @return printer consumer + */ + static Consumer configuration(Configuration a) { + return printer -> printer + .object("configuration", p -> p + .value("useExisting", a.useExisting()) + .value("profile", a.profile()) + .values("configSources", a.configSources())); + } + + /** + * Create a {@link AddConfig} printer. + * + * @param a annotation + * @return printer consumer + */ + static Consumer addConfig(AddConfig a) { + return printer -> printer + .value("key", a.key()) + .value("value", a.value()); + } + + /** + * Create a {@link AddConfigBlock} printer. + * + * @param a annotation + * @return printer consumer + */ + static Consumer addConfigBlock(AddConfigBlock a) { + return printer -> printer + .value("type", a.type()) + .block("value", a.value()); + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Proxies.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Proxies.java new file mode 100644 index 00000000000..0c10329a373 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Proxies.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.List; +import java.util.function.Function; + +/** + * Proxy helper. + */ +public class Proxies { + + private Proxies() { + // cannot be instantiated + } + + /** + * Mirror an annotation. + * + * @param type target type + * @param annotation source annotation + * @param target type + * @return mirror + */ + public static T mirror(Class type, Annotation annotation) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Object o = Proxy.newProxyInstance(cl, new Class[] {type}, (proxy, method, args) -> { + Method sourceMethod = annotation.getClass().getMethod(method.getName()); + try { + return sourceMethod.invoke(annotation); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + return type.cast(o); + } + + /** + * Instantiate an annotation. + * + * @param type annotation type + * @param function attributes function + * @param annotation type + * @return annotation + */ + public static T annotation(Class type, Function function) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Object o = Proxy.newProxyInstance(cl, List.of(type).toArray(Class[]::new), + (proxy, method, args) -> { + String methodName = method.getName(); + if ("annotationType".equals(methodName)) { + return type; + } + Object value = function.apply(methodName); + return value != null ? value : method.getDefaultValue(); + }); + return type.cast(o); + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/ReflectionHelper.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/ReflectionHelper.java new file mode 100644 index 00000000000..8e08e590344 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/ReflectionHelper.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Reflection helper. + */ +class ReflectionHelper { + + private ReflectionHelper() { + // cannot be instanciated + } + + /** + * Collect all the methods that have the given method signature in its type hierarchy. + * + * @param method method + * @return hierarchy + */ + static List methodHierarchy(Method method) { + return typeHierarchy(method.getDeclaringClass(), true).stream() + .flatMap(t -> Stream.of(t.getDeclaredMethods())) + .filter(override -> isOverride(method, override)) + .toList(); + } + + /** + * Test if the given method overrides another. + * + * @param method base method + * @param override override method + * @return {@code true} if overrides, {@code false otherwise} + */ + static boolean isOverride(Method method, Method override) { + return override.getName().equals(method.getName()) + && override.getReturnType().isAssignableFrom(method.getReturnType()) + && Arrays.equals(override.getParameterTypes(), method.getParameterTypes()); + } + + /** + * Collect all types in the type hiearchy of the given type. + * + * @param type type + * @param all {@code false} to only include ancestors + * @return types + */ + static List> typeHierarchy(Class type, boolean all) { + List> result = new ArrayList<>(); + Deque> stack = new ArrayDeque<>(); + stack.push(type); + while (!stack.isEmpty()) { + Class e = stack.pop(); + if (e.getPackage().getName().startsWith("java.")) { + continue; + } + if (all || !e.equals(type)) { + result.add(e); + } + if (e.getSuperclass() != null) { + stack.push(e.getSuperclass()); + } + List.of(e.getInterfaces()).forEach(stack::push); + } + return result; + } + + /** + * Collect all effective annotations of an annotation class. + * + * @param annotationType annotation type + * @return annotations + */ + static List annotationHierarchy(Class annotationType) { + List result = new ArrayList<>(); + Deque stack = new ArrayDeque<>(); + List.of(annotationType.getAnnotations()).forEach(stack::push); + while (!stack.isEmpty()) { + Annotation e = stack.pop(); + Class type = e.annotationType(); + if (!type.getPackage().getName().startsWith("io.helidon.")) { + continue; + } + result.add(e); + List.of(type.getAnnotations()).forEach(stack::push); + } + return result; + } + + /** + * Annotated element. + * + * @param element element + * @param annotations annotations + */ + record Annotated(AnnotatedElement element, List annotations) { + + private static Annotated create(AnnotatedElement element) { + return new Annotated<>(element, Stream.of(element.getAnnotations()) + .flatMap(a -> Stream.concat(Stream.of(a), annotationHierarchy(a.annotationType()).stream())) + .toList()); + } + } + + /** + * Get all annotations for a method and its hierarchy. + * + * @param method method + * @return annotations + */ + static List> annotated(Method method) { + return annotated(methodHierarchy(method)); + } + + /** + * Get all annotations for a class and its hierarchy. + * + * @param type type + * @return annotations + */ + static List> annotated(Class type) { + return annotated(typeHierarchy(type, true)); + } + + /** + * Get all annotations for the given elements. + * + * @param elements elements + * @return annotations + */ + static List> annotated(List elements) { + return elements.stream().map(Annotated::create).collect(Collectors.toList()); + } + + /** + * Filter annotations of a given type. + * + * @param annotated annotations + * @param aType annotation type + * @param cType container type + * @param function function to inflate from container + * @param annotation type + * @param container type + * @return annotations + */ + static List> filterAnnotated(List> annotated, + Class aType, + Class cType, + Function function) { + + Predicate predicate = a -> a.annotationType().equals(cType) || a.annotationType().equals(aType); + return annotated.stream() + .filter(a -> a.annotations.stream().anyMatch(predicate)) + .map(it -> new Annotated<>(it.element, it.annotations.stream() + .filter(predicate) + .flatMap(at -> { + if (at.annotationType().equals(cType)) { + return Stream.of(function.apply(cType.cast(at))); + } + return Stream.of(aType.cast(at)); + }) + .toList())) + .toList(); + } + + /** + * Filter annotations of a given type. + * + * @param annotated annotations + * @param aType annotation type + * @param container type + * @return annotations + */ + static Stream> filterAnnotated(List> annotated, Class aType) { + Predicate predicate = a -> a.annotationType().equals(aType); + return annotated.stream() + .filter(a -> a.annotations.stream().anyMatch(predicate)) + .map(it -> new Annotated<>(it.element, it.annotations.stream() + .filter(predicate) + .map(aType::cast) + .toList())); + } + + /** + * Filter annotations of a given type. + * + * @param annotated annotations + * @param aType annotation type + * @param cType container type + * @param function function to inflate from container + * @param annotation type + * @param container type + * @return annotations + */ + static Stream filterAnnotations(List> annotated, + Class aType, + Class cType, + Function function) { + + return filterAnnotated(annotated, aType, cType, function).stream() + .flatMap(it -> it.annotations.stream()); + } + + /** + * Filter annotations of a given type. + * + * @param annotations annotations + * @param annotationType annotation type + * @param container type + * @return annotations + */ + static Stream filterAnnotations(List> annotations, Class annotationType) { + return filterAnnotated(annotations, annotationType) + .flatMap(it -> it.annotations.stream()); + } + + /** + * Checks that the given class is public. + * + * @param cls class + * @param class type + * @return Class + * @throws java.lang.IllegalArgumentException if the given class is not public + */ + static Class requirePublic(Class cls) throws IllegalArgumentException { + if (!Modifier.isPublic(cls.getModifiers())) { + throw new IllegalArgumentException(cls.getName() + " is not public"); + } + return cls; + } + + /** + * Checks that the given method is static. + * + * @param method method + * @return Method + * @throws java.lang.IllegalArgumentException if the given class is not public + */ + static Method requireStatic(Method method) throws IllegalArgumentException { + if (!Modifier.isStatic(method.getModifiers())) { + throw new IllegalArgumentException(method + " is not static"); + } + return method; + } + + /** + * Invoke a method. + * + * @param type return type + * @param method method + * @param instance instance + * @param args arguments + * @param return type + * @return invocation result + */ + static T invoke(Class type, Method method, Object instance, Object... args) { + try { + method.setAccessible(true); + Object value = method.invoke(instance, args); + return type.cast(value); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Socket.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Socket.java new file mode 100644 index 00000000000..939354ce058 --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/Socket.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.inject.Qualifier; + +/** + * CDI qualifier to inject a JAX-RS client or URI for a named socket. + *

+ * The supported types are: + *

    + *
  • {@link jakarta.ws.rs.client.WebTarget WebTarget} a JAXRS client target
  • + *
  • {@link java.net.URI URI} a URI
  • + *
  • {@link String} a raw URI
  • + *
+ *

+ * This annotation can be used on constructor parameters, or class fields. + * Test method parameter injection may be supported depending on the test framework integration. + *

+ * Also note that the default socket name is {@code "@default"}. + *

+ * E.g. constructor injection: + *

+ * class MyTest {
+ *     private final WebTarget target;
+ *
+ *     @Inject
+ *     MyTest(@Socket("@default") URI uri) {
+ *         target = ClientBuilder.newClient().target(uri);
+ *     }
+ * }
+ * 
+ *

+ * E.g. field injection: + *

+ * class MyTest {
+ *
+ *     @Inject // optional
+ *     @Socket("@default")
+ *     private WebTarget target;
+ * }
+ * 
+ */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +public @interface Socket { + + /** + * Name of the socket. + * + * @return socket name + */ + String value(); +} diff --git a/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/package-info.java b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/package-info.java new file mode 100644 index 00000000000..4ad81a5fccf --- /dev/null +++ b/microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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. + */ + +/** + * Helidon MicroProfile Testing Support. + */ +package io.helidon.microprofile.testing; diff --git a/microprofile/testing/testing/src/main/java/module-info.java b/microprofile/testing/testing/src/main/java/module-info.java new file mode 100644 index 00000000000..371287a06ac --- /dev/null +++ b/microprofile/testing/testing/src/main/java/module-info.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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. + */ + +/** + * Helidon MicroProfile Testing Support. + */ +@SuppressWarnings("requires-transitive-automatic") +module io.helidon.microprofile.testing { + + requires transitive io.helidon.common.testing.vitualthreads; + requires io.helidon.config.mp; + requires io.helidon.config.yaml.mp; + requires io.helidon.microprofile.cdi; + requires jakarta.inject; + + requires transitive jakarta.cdi; + requires transitive jakarta.ws.rs; + + requires static transitive io.helidon.microprofile.server; + requires static transitive jersey.cdi1x; + requires static transitive jersey.weld2.se; + + requires jersey.client; + requires net.bytebuddy; + requires jdk.unsupported; + + exports io.helidon.microprofile.testing to + io.helidon.microprofile.testing.junit5, + io.helidon.microprofile.testing.testng; +} diff --git a/microprofile/testing/testing/src/test/java/io/helidon/microprofile/testing/HelidonTestConfigTest.java b/microprofile/testing/testing/src/test/java/io/helidon/microprofile/testing/HelidonTestConfigTest.java new file mode 100644 index 00000000000..92117ba0663 --- /dev/null +++ b/microprofile/testing/testing/src/test/java/io/helidon/microprofile/testing/HelidonTestConfigTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing; + +import io.helidon.config.Config; +import io.helidon.config.mp.MpConfig; + +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +/** + * Tests {@link HelidonTestConfig}. + */ +class HelidonTestConfigTest { + + @Test + void testResolve() { + // install the "original" config + ConfigProviderResolver resolver = ConfigProviderResolver.instance(); + resolver.registerConfig(resolver.getBuilder().build(), getClass().getClassLoader()); + + HelidonTestConfig config = new HelidonTestConfig(HelidonTestInfo.classInfo(getClass())); + config.synthetic().update(addConfig("key1", "value1")); + config.synthetic().update(addConfig("key2", "value2")); + + // synthetic config does not resolve + assertThat(config.getOptionalValue("key1", String.class).isPresent(), is(false)); + assertThat(config.getOptionalValue("key2", String.class).isPresent(), is(false)); + + // switch to synthetic + config.resolve(); + + assertThat(config.getValue("key1", String.class), is("value1")); + assertThat(config.getValue("key2", String.class), is("value2")); + + // update synthetic config + assertThat(config.getOptionalValue("complex.key1", String.class).isPresent(), is(false)); + config.synthetic().update(configuration(false, "test-config.yaml")); + assertThat(config.getValue("complex.key1", String.class), is("complex-value1")); + + // useExisting=true + config.synthetic().update(configuration(true)); + + // switch to original + config.resolve(); + + // synthetic config does not resolve + assertThat(config.getOptionalValue("key1", String.class).isPresent(), is(false)); + assertThat(config.getOptionalValue("key2", String.class).isPresent(), is(false)); + assertThat(config.getOptionalValue("complex.key1", String.class).isPresent(), is(false)); + } + + @Test + void testJustInTime() { + HelidonTestConfig config = new HelidonTestConfig(HelidonTestInfo.classInfo(getClass())); + config.resolve(); + + // se config + Config hc = MpConfig.toHelidonConfig(config); + + config.synthetic().update(addConfig("key1", "value1")); + config.synthetic().update(addConfig("key2", "value2")); + assertThat(hc.get("key1").asString().get(), is("value1")); + assertThat(hc.get("key2").asString().get(), is("value2")); + + // update synthetic config + config.synthetic().update(configuration(false, "test-config.yaml")); + assertThat(hc.get("complex.key1").asString().get(), is("complex-value1")); + } + + private static AddConfig addConfig(String key, String value) { + return Proxies.annotation(AddConfig.class, attr -> switch (attr) { + case "key" -> key; + case "value" -> value; + default -> null; + }); + } + + private static Configuration configuration(boolean useExisting, String... configSources) { + return Proxies.annotation(Configuration.class, attr -> switch (attr) { + case "useExisting" -> useExisting; + case "configSources" -> configSources; + default -> null; + }); + } +} diff --git a/tests/integration/jpa/appl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/microprofile/testing/testing/src/test/resources/test-config.yaml similarity index 82% rename from tests/integration/jpa/appl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension rename to microprofile/testing/testing/src/test/resources/test-config.yaml index e9c3dcb5083..f000a4373e5 100644 --- a/tests/integration/jpa/appl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension +++ b/microprofile/testing/testing/src/test/resources/test-config.yaml @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,4 +14,5 @@ # limitations under the License. # -io.helidon.tests.integration.jpa.appl.test.LifeCycleExtension +complex: + key1: "complex-value1" diff --git a/microprofile/testing/testng/pom.xml b/microprofile/testing/testng/pom.xml index 1d52db60dbf..f8c6b33de60 100644 --- a/microprofile/testing/testng/pom.xml +++ b/microprofile/testing/testng/pom.xml @@ -34,13 +34,13 @@ - io.helidon.microprofile.server - helidon-microprofile-server - true + io.helidon.microprofile.testing + helidon-microprofile-testing - io.helidon.common.testing - helidon-common-testing-virtual-threads + io.helidon.microprofile.server + helidon-microprofile-server + provided io.helidon.microprofile.cdi @@ -50,7 +50,7 @@ org.glassfish.jersey.ext.cdi jersey-weld2-se - true + provided io.helidon.jersey @@ -65,6 +65,10 @@ testng provided + + com.google.inject + guice + 5.1.0 + - - \ No newline at end of file + diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddBean.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddBean.java index 220f1c893cb..14ea4f19c30 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddBean.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,37 +17,45 @@ import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import jakarta.enterprise.context.ApplicationScoped; - /** - * Add a bean. - * This is intended for test sources where we do not want to add {@code beans.xml} as this would add - * all test classes as beans. - * The bean will be added by default with {@link jakarta.enterprise.context.ApplicationScoped}. - * The class will be instantiated using CDI and will be available for injection into test classes and other beans. + * Add a CDI bean to the container. + *

* This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + *

+ * The bean scope is defined as follows: + *

    + *
  • If a scope is set with {@link #value()}, it overrides any scope defined on the bean
  • + *
  • Otherwise, the scope defined on the bean is used
  • + *
  • If the bean does not define a scope, {@link jakarta.enterprise.context.ApplicationScoped ApplicationScoped} + * is used
  • + *
+ * @deprecated Use {@link io.helidon.microprofile.testing.AddBean} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Repeatable(AddBeans.class) +@Deprecated(since = "4.2.0") public @interface AddBean { /** - * Class of the bean. - * @return the class of a bean + * The bean class. + * + * @return bean class */ Class value(); /** - * Scope of the bean. - * Only {@link jakarta.inject.Singleton}, {@link jakarta.enterprise.context.ApplicationScoped} - * and {@link jakarta.enterprise.context.RequestScoped} scopes are supported. + * Override the bean scope. * - * @return scope of the bean + * @return scope class */ - Class scope() default ApplicationScoped.class; + Class scope() default Annotation.class; } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddBeans.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddBeans.java index 0de9a00f299..2ab89f1a31d 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddBeans.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddBeans.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,34 @@ package io.helidon.microprofile.testing.testng; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * A repeatable container for {@link AddBean}. - * No need to use this annotation, just repeat {@link AddBean} annotation - * on test class. + *

+ * This annotation is optional, you can instead repeat {@link AddBean}. + *

+ * E.g. + *

+ * @AddBean(FooBean.class)
+ * @AddBean(BarBean.class)
+ * class MyTest {
+ * }
+ * 
+ * @deprecated Use {@link io.helidon.microprofile.testing.AddBeans} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) +@Deprecated(since = "4.2.0") public @interface AddBeans { /** - * Beans to be added. - * @return add bean annotations + * Get the contained annotations. + * + * @return annotations */ AddBean[] value(); } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfig.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfig.java index d1bf335bb5b..0b0cc8e8cc1 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfig.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,27 +16,40 @@ package io.helidon.microprofile.testing.testng; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Add a configuration key/value pair to MicroProfile configuration. + * Add a configuration key/value pair to the {@link Configuration#useExisting() synthetic test configuration}. + *

* This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * + * @see AddConfigs + * @see AddConfigBlock + * @see Configuration + * @deprecated Use {@link io.helidon.microprofile.testing.AddConfig} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Repeatable(AddConfigs.class) +@Deprecated(since = "4.2.0") public @interface AddConfig { /** * Configuration property key. + * * @return key */ String key(); /** * Configuration property value. + * * @return value */ String value(); diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfigBlock.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfigBlock.java index 3ba558d2fc3..fd59c41df54 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfigBlock.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfigBlock.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,28 +23,35 @@ import java.lang.annotation.Target; /** - * Defines the configuration as a String in {@link #value()} for the - * given type. + * Add a configuration fragment to the {@link Configuration#useExisting() synthetic test configuration}. + *

+ * This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * + * @see AddConfig + * @see AddConfigs + * @see Configuration + * @deprecated Use {@link io.helidon.microprofile.testing.AddConfigBlock} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) -@Inherited +@Deprecated(since = "4.2.0") public @interface AddConfigBlock { - /** - * Specifies the format type of the {@link #value()}. - * - * It defaults to 'properties'. + * Specifies the configuration format. + *

+ * The default format is 'properties' * * @return the supported type */ String type() default "properties"; /** - * Configuration value. + * Configuration fragment. * - * @return String with value. + * @return fragment */ String value(); - } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfigs.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfigs.java index 4ff9a87c0ee..6b75dbe7f40 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfigs.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddConfigs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,22 +16,37 @@ package io.helidon.microprofile.testing.testng; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * A repeatable container for {@link AddConfig}. - * No need to use this annotation, just repeat {@link AddConfig} annotation - * on test class. + *

+ * This annotation is optional, you can instead repeat {@link AddConfig}. + *

+ * E.g. + *

+ * @AddConfig(key="foo", value="1")
+ * @AddConfig(key="bar", value="2")
+ * class MyTest {
+ * }
+ * 
+ * + * @see AddConfig + * @see Configuration + * @deprecated Use {@link io.helidon.microprofile.testing.AddConfigs} instead */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) +@Inherited +@Deprecated(since = "4.2.0") public @interface AddConfigs { /** - * Configuration properties to be added. + * Get the contained annotations. * - * @return properties + * @return annotations */ AddConfig[] value(); } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddExtension.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddExtension.java index 85d246eb01b..8c1d8849f87 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddExtension.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.microprofile.testing.testng; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -24,15 +25,22 @@ import jakarta.enterprise.inject.spi.Extension; /** - * Add a CDI extension to the test container. + * Add a CDI extension to the container. + *

* This annotation can be repeated. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * @deprecated Use {@link io.helidon.microprofile.testing.AddExtension} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Repeatable(AddExtensions.class) +@Deprecated(since = "4.2.0") public @interface AddExtension { /** * Class of the extension to add. The class must be public. + * * @return extension class. */ Class value(); diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddExtensions.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddExtensions.java index 02f0eb74d14..f6776846a8f 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddExtensions.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddExtensions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,34 @@ package io.helidon.microprofile.testing.testng; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * A repeatable container for {@link AddExtension}. - * No need to use this annotation, just repeat {@link AddExtension} annotation - * on test class. + *

+ * This annotation is optional, you can instead repeat {@link AddExtension}. + *

+ * E.g. + *

+ * @AddExtension(FooExtension.class)
+ * @AddExtension(BarExtension.class)
+ * class MyTest {
+ * }
+ * 
+ * @deprecated Use {@link io.helidon.microprofile.testing.AddExtensions} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) +@Deprecated(since = "4.2.0") public @interface AddExtensions { /** - * Extensions to be added. - * @return extensions + * Get the contained annotations. + * + * @return annotations */ AddExtension[] value(); } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddJaxRs.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddJaxRs.java index 250d2fb192e..707d5d550f7 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddJaxRs.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/AddJaxRs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,32 @@ package io.helidon.microprofile.testing.testng; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import io.helidon.microprofile.server.JaxRsCdiExtension; +import io.helidon.microprofile.server.ServerCdiExtension; + +import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; +import org.glassfish.jersey.ext.cdi1x.internal.ProcessAllAnnotatedTypes; +import org.glassfish.jersey.weld.se.WeldRequestScope; + /** - * Add JaxRS support for Request-scoped beans. + * Add JAX-RS (Jersey) support. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * @deprecated Use {@link io.helidon.microprofile.testing.AddJaxRs} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ElementType.TYPE, ElementType.METHOD}) +@AddExtension(ProcessAllAnnotatedTypes.class) +@AddExtension(ServerCdiExtension.class) +@AddExtension(JaxRsCdiExtension.class) +@AddExtension(CdiComponentProvider.class) +@AddBean(WeldRequestScope.class) +@Deprecated(since = "4.2.0") public @interface AddJaxRs { } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/ClassContext.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/ClassContext.java new file mode 100644 index 00000000000..84a6879aec7 --- /dev/null +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/ClassContext.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.testng; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.helidon.microprofile.testing.HelidonTestInfo; +import io.helidon.microprofile.testing.HelidonTestInfo.ClassInfo; +import io.helidon.microprofile.testing.HelidonTestInfo.MethodInfo; + +import org.testng.ITestClass; +import org.testng.ITestNGMethod; + +/** + * Class context. + * Supports synchronization and ordering of {@link HelidonTestNgListenerBase}. + */ +class ClassContext { + + private final Semaphore semaphore; + private final HelidonTestNgListenerBase listener; + private final ClassInfo classInfo; + private final List methods; + private final AtomicInteger invocationCount = new AtomicInteger(); + private final AtomicBoolean afterClass = new AtomicBoolean(); + private final Map, List> graph = new ConcurrentHashMap<>(); + private final Map> methodsFutures = new ConcurrentHashMap<>(); + + /** + * Create a new instance. + * + * @param tc test class + * @param semaphore class semaphore + * @param listener listener + */ + ClassContext(ITestClass tc, Semaphore semaphore, HelidonTestNgListenerBase listener) { + this.listener = listener; + this.semaphore = semaphore; + this.classInfo = classInfo(tc.getRealClass()); + this.methods = methodInfos(classInfo, + tc.getTestMethods(), + tc.getBeforeTestMethods(), + tc.getBeforeTestMethods(), + tc.getBeforeClassMethods(), + tc.getAfterClassMethods()); + } + + /** + * Wait for the running methods. + */ + void awaitMethods() { + try { + for (CompletableFuture future : Set.copyOf(methodsFutures.values())) { + future.get(); + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + /** + * Process a before-invocation event. + * + * @param invokedMethod invoked method + * @param testMethod corresponding test method, may be {@code null} + * @see org.testng.IInvokedMethodListener#beforeInvocation(org.testng.IInvokedMethod, org.testng.ITestResult) + * @see org.testng.IConfigurationListener#beforeConfiguration(org.testng.ITestResult, org.testng.ITestNGMethod) + */ + void beforeInvocation(ITestNGMethod invokedMethod, ITestNGMethod testMethod) { + MethodInfo methodInfo = methodInfo(classInfo, realMethod(invokedMethod)); + HelidonTestInfo testInfo = testMethod != null ? methodInfo(classInfo, realMethod(testMethod)) : classInfo; + graph.compute(testInfo, (k, v) -> { + if (v == null) { + v = new ArrayList<>(); + } + v.add(methodInfo); + return v; + }); + listener.onBeforeInvocation(this, methodInfo, testInfo); + if (invokedMethod.isTest()) { + methodsFutures.put(methodInfo, new CompletableFuture<>()); + } + } + + /** + * Process an after-invocation event. + * + * @param invokedMethod invoked method + * @param testMethod corresponding test method, may be {@code null} + * @see org.testng.IInvokedMethodListener#afterInvocation(org.testng.IInvokedMethod, org.testng.ITestResult) + * @see org.testng.IConfigurationListener#onConfigurationSuccess(org.testng.ITestResult, org.testng.ITestNGMethod) + * @see org.testng.IConfigurationListener#onConfigurationFailure(org.testng.ITestResult, org.testng.ITestNGMethod) + */ + void afterInvocation(ITestNGMethod invokedMethod, ITestNGMethod testMethod) { + try { + MethodInfo methodInfo = methodInfo(classInfo, realMethod(invokedMethod)); + HelidonTestInfo testInfo = testMethod != null ? methodInfo(classInfo, realMethod(testMethod)) : classInfo; + List deps = graph.compute(testInfo, (k, v) -> { + if (v == null) { + v = new ArrayList<>(); + } + v.remove(methodInfo); + return v; + }); + boolean last = deps.isEmpty(); + listener.onAfterInvocation(methodInfo, testInfo, last); + CompletableFuture future = methodsFutures.get(methodInfo); + if (future != null) { + future.complete(null); + } + } finally { + afterClass(AtomicInteger::incrementAndGet); + } + } + + /** + * Process a before-class event. + * + * @see org.testng.IClassListener#onBeforeClass(org.testng.ITestClass) + */ + void beforeClass() { + try { + semaphore.acquire(); + listener.onBeforeClass(classInfo); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + /** + * Process an after-class event. + * + * @see org.testng.IClassListener#onAfterClass(org.testng.ITestClass) + */ + void afterClass() { + afterClass(AtomicInteger::get); + } + + @Override + public String toString() { + return "ClassContext{" + + "class=" + classInfo.element().getName() + + ", methods: " + methodNames(methods) + + '}'; + } + + private void afterClass(Function op) { + if (op.apply(invocationCount) == methods.size() + && afterClass.compareAndSet(false, true)) { + try { + listener.onAfterClass(classInfo); + methodsFutures.clear(); + } finally { + semaphore.release(); + } + } + } + + /** + * Get a class info. + * + * @param cls class + * @return ClassInfo + */ + static ClassInfo classInfo(Class cls) { + return HelidonTestInfo.classInfo(cls, HelidonTestDescriptorImpl::new); + } + + /** + * Get a method info. + * + * @param classInfo class info + * @param method method + * @return MethodInfo + */ + static MethodInfo methodInfo(ClassInfo classInfo, Method method) { + return HelidonTestInfo.methodInfo(method, classInfo, HelidonTestDescriptorImpl::new); + } + + private static List methodInfos(ClassInfo classInfo, ITestNGMethod[]... methods) { + return Stream.of(methods) + .flatMap(Stream::of) + .map(m -> methodInfo(classInfo, realMethod(m))) + .toList(); + } + + private static String methodNames(List methodInfos) { + return methodInfos.stream() + .map(MethodInfo::element) + .map(Method::getName) + .collect(Collectors.joining(",", "[", "]")); + } + + private static Method realMethod(ITestNGMethod tm) { + return tm.getConstructorOrMethod().getMethod(); + } +} diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/ClassDecorator.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/ClassDecorator.java new file mode 100644 index 00000000000..4828c74592d --- /dev/null +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/ClassDecorator.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.testng; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.testng.ITestClass; +import org.testng.ITestNGMethod; +import org.testng.internal.ITestClassConfigInfo; +import org.testng.xml.XmlClass; +import org.testng.xml.XmlTest; + +import static io.helidon.microprofile.testing.Instrumented.isInstrumented; + +/** + * A decorator for {@link ITestClass} that is used to customize the value of {@link ITestClass#getName()}. + *

    + *
  • It is used to hide the instrumented name in the test results
  • + *
  • It is installed on every method using {@link ITestNGMethod#setTestClass(ITestClass)}
  • + *
+ * + * @param delegate delegate + */ +record ClassDecorator(ITestClass delegate) implements ITestClass, ITestClassConfigInfo { + + private static final Map CACHE = new ConcurrentHashMap<>(); + + /** + * Decorate the given test class. + * + * @param tc test class + * @return decorated test class + */ + static ITestClass decorate(ITestClass tc) { + if (!(tc instanceof ClassDecorator)) { + return CACHE.computeIfAbsent(tc, ClassDecorator::new); + } + return tc; + } + + @Override + public List getAllBeforeClassMethods() { + if (delegate instanceof ITestClassConfigInfo info) { + return info.getAllBeforeClassMethods().stream() + .peek(m -> m.setTestClass(decorate(m.getTestClass()))) + .toList(); + } + return List.of(); + } + + @Override + public List getInstanceBeforeClassMethods(Object instance) { + if (delegate instanceof ITestClassConfigInfo info) { + return info.getInstanceBeforeClassMethods(instance).stream() + .peek(m -> m.setTestClass(decorate(m.getTestClass()))) + .toList(); + } + return List.of(); + } + + @Override + public String getName() { + Class realClass = getRealClass(); + return isInstrumented(realClass) ? realClass.getSuperclass().getName() : realClass.getName(); + } + + @Override + public Class getRealClass() { + return delegate.getRealClass(); + } + + private ITestNGMethod[] processMethods(ITestNGMethod[] methods) { + for (ITestNGMethod method : methods) { + method.setTestClass(decorate(method.getTestClass())); + } + return methods; + } + + @Override + public ITestNGMethod[] getAfterClassMethods() { + return processMethods(delegate.getAfterClassMethods()); + } + + @Override + public ITestNGMethod[] getAfterGroupsMethods() { + return processMethods(delegate.getAfterGroupsMethods()); + } + + @Override + public ITestNGMethod[] getAfterSuiteMethods() { + return processMethods(delegate.getAfterSuiteMethods()); + } + + @Override + public ITestNGMethod[] getAfterTestConfigurationMethods() { + return processMethods(delegate.getAfterTestConfigurationMethods()); + } + + @Override + public ITestNGMethod[] getAfterTestMethods() { + return processMethods(delegate.getAfterTestMethods()); + } + + @Override + public ITestNGMethod[] getBeforeClassMethods() { + return processMethods(delegate.getBeforeClassMethods()); + } + + @Override + public ITestNGMethod[] getBeforeGroupsMethods() { + return processMethods(delegate.getBeforeGroupsMethods()); + } + + @Override + public ITestNGMethod[] getBeforeSuiteMethods() { + return processMethods(delegate.getBeforeSuiteMethods()); + } + + @Override + public ITestNGMethod[] getBeforeTestConfigurationMethods() { + return processMethods(delegate.getBeforeTestConfigurationMethods()); + } + + @Override + public ITestNGMethod[] getBeforeTestMethods() { + return processMethods(delegate.getBeforeTestMethods()); + } + + @Override + public ITestNGMethod[] getTestMethods() { + return processMethods(delegate.getTestMethods()); + } + + @Override + public void addInstance(Object instance) { + delegate.addInstance(instance); + } + + @Override + public long[] getInstanceHashCodes() { + return delegate.getInstanceHashCodes(); + } + + @Override + public Object[] getInstances(boolean create) { + return delegate.getInstances(create); + } + + @Override + public Object[] getInstances(boolean create, String errorMsgPrefix) { + return delegate.getInstances(create, errorMsgPrefix); + } + + @Override + public String getTestName() { + return delegate.getTestName(); + } + + @Override + public XmlClass getXmlClass() { + return delegate.getXmlClass(); + } + + @Override + public XmlTest getXmlTest() { + return delegate.getXmlTest(); + } +} diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/Configuration.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/Configuration.java index 23a60e70247..16e50b03080 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/Configuration.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/Configuration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,36 +23,55 @@ import java.lang.annotation.Target; /** - * Additional configuration of config itself. + * General setting for the test configuration. + *

+ * If used on a method, the container will be reset regardless of the test lifecycle. + * + * @see AddConfig + * @see AddConfigs + * @see AddConfigBlock + * @deprecated Use {@link io.helidon.microprofile.testing.Configuration} instead */ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) -@Inherited +@Deprecated(since = "4.2.0") public @interface Configuration { /** - * If set to {@code true}, the existing (or default) MicroProfile configuration would be used. - * By default uses a configuration constructed using all {@link AddConfig} - * annotations and {@link #configSources()}. - * When set to false and a {@link org.testng.annotations.BeforeClass} method registers a custom configuration - * with {@link org.eclipse.microprofile.config.spi.ConfigProviderResolver}, the result is undefined, though - * tests have shown that the registered config may be used (as BeforeAll ordering is undefined by - * JUnit, it may be called after our extension) + * If set to {@code false}, the synthetic test configuration is used. + *

+ * The synthetic test configuration is expressed with the following: + *

    + *
  • {@link #configSources()}
  • + *
  • {@link #profile()}
  • + *
  • {@link AddConfig}
  • + *
  • {@link AddConfigs}
  • + *
  • {@link AddConfigBlock}
  • + *
+ *

+ * If set to {@code true}, only the existing (or default) MicroProfile configuration is used + * and the annotations listed previously are ignored. + *

+ * You can use {@link org.eclipse.microprofile.config.spi.ConfigProviderResolver ConfigProviderResolver} to define + * the configuration programmatically before the CDI container starts. * - * @return whether to use existing (or default) configuration, or customized one + * @return whether to use existing (or default) configuration */ boolean useExisting() default false; /** - * Class path properties config sources to add to configuration of this test class or method. + * Class-path resources to add as config sources to the synthetic test configuration. * - * @return config sources to add + * @return config sources */ String[] configSources() default {}; /** * Configuration profile. + *

+ * The default profile is 'test' * - * @return String with default value "test". + * @return profile */ String profile() default "test"; } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/DisableDiscovery.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/DisableDiscovery.java index a4b66811f6e..91148a34ce6 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/DisableDiscovery.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/DisableDiscovery.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,30 +23,39 @@ import java.lang.annotation.Target; /** - * Whether discovery is automated or disabled. If discovery is desired, do not annotate test - * class with this annotation. + * Disables CDI discovery. *

- * When discovery is enabled, the whole classpath is scanned for bean archives (jar files containing - * {@code META-INF/beans.xml}) and all beans and extensions are added automatically. + * If discovery is desired, do not annotate test class with this annotation. *

- * When discovery is disabled, CDI would only contain the CDI implementation itself and beans and extensions added - * through annotations {@link AddBean} and - * {@link AddExtension} - * - * If discovery is disabled on class level and desired on method level, - * the value can be set to {@code false}. + * If used on a method, the container will be reset regardless of the test lifecycle. + *

+ * When disabling discovery, you are responsible for adding the beans and extensions needed to activate the features you need. + * You can use the following annotations to do that: + *

    + *
  • {@link AddBean} to add CDI beans
  • + *
  • {@link AddExtension} to add CDI extensions
  • + *
  • {@link AddJaxRs} a short-hand to add JAX-RS (Jersey)
  • + *
+ *

+ * See also the following "core" CDI extensions: + *

    + *
  • {@link io.helidon.microprofile.server.ServerCdiExtension ServerCdiExtension} optional if using {@link AddJaxRs}
  • + *
  • {@link io.helidon.microprofile.server.JaxRsCdiExtension JaxRsCdiExtension} optional if using {@link AddJaxRs}
  • + *
  • {@link io.helidon.microprofile.config.ConfigCdiExtension ConfigCdiExtension}
  • + *
*/ +@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) -@Inherited +@Deprecated(since = "4.2.0") public @interface DisableDiscovery { /** - * By default if you annotate a class or a method, discovery gets disabled. + * By default, if you annotate a class or a method, discovery gets disabled. * If you want to override configuration on method to differ from class, you * can configure the value to {@code false}, effectively enabling discovery. * * @return whether to disable discovery ({@code true}), or enable it ({@code false}). If this - * annotation is not present, discovery is enabled + * annotation is not present, discovery is enabled */ boolean value() default true; } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTest.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTest.java index 2f3ef14400f..3f810612ac9 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTest.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,30 +24,18 @@ import static io.helidon.common.testing.virtualthreads.PinningRecorder.DEFAULT_THRESHOLD; /** - * An annotation making this test class a CDI bean with support for injection. - *

- * There is no need to provide {@code beans.xml} (actually it is not recommended, as it would combine beans - * from all tests), instead use {@link AddBean}, - * {@link AddExtension}, and {@link AddConfig} - * annotations to control the shape of the container. - *

+ * This extension starts a CDI container and adds the test class as a bean with support for injection. + *

+ * The container is started lazily during test execution to ensure that it is started after all other extensions. + *

+ * The container can be customized with the following annotations: + *

+ *

+ * The configuration can be customized with the following annotations: + *

    + *
  • {@link io.helidon.microprofile.testing.Configuration} global setting for MicroProfile configuration
  • + *
  • {@link io.helidon.microprofile.testing.AddConfig} declarative key/value pair configuration
  • + *
  • {@link io.helidon.microprofile.testing.AddConfigBlock} declarative fragment configuration
  • + *
  • {@link io.helidon.microprofile.testing.AddConfigSource} programmatic configuration
  • + *
+ *

+ * See also {@link io.helidon.microprofile.testing.Socket}, a CDI qualifier to inject JAX-RS client or URI. + *

+ * The container is created per test class by default, unless + * {@link HelidonTest#resetPerTest()} is {@code true}, in + * which case the container is created per test method. + *

+ * The container and the configuration can be customized per method regardless of the value of + * {@link HelidonTest#resetPerTest()}. The container will be reset accordingly. + *

+ * It is not recommended to provide a {@code beans.xml} along the test classes, as it would combine beans from all tests. + * Instead, you should use {@link io.helidon.microprofile.testing.AddBean} to specify the beans per test or method. + * + * @see HelidonTest */ -public class HelidonTestNgListener implements IClassListener, ITestListener { - - private static final Set> TEST_ANNOTATIONS = - Set.of(AddBean.class, AddConfig.class, AddExtension.class, - Configuration.class, AddJaxRs.class, AddConfigBlock.class); - - private static final Map, AnnotationLiteral> BEAN_DEFINING = Map.of( - ApplicationScoped.class, ApplicationScoped.Literal.INSTANCE, - Singleton.class, ApplicationScoped.Literal.INSTANCE, - RequestScoped.class, RequestScoped.Literal.INSTANCE, - Dependent.class, Dependent.Literal.INSTANCE); - - private List classLevelExtensions = new ArrayList<>(); - private List classLevelBeans = new ArrayList<>(); - private ConfigMeta classLevelConfigMeta = new ConfigMeta(); - private boolean classLevelDisableDiscovery = false; - private boolean helidonTest; - private boolean resetPerTest; - - private Class testClass; - private Object testInstance; - private ConfigProviderResolver configProviderResolver; - private Config config; - private SeContainer container; - private PinningRecorder pinningRecorder; - - @Override - public void onBeforeClass(ITestClass iTestClass) { - testClass = iTestClass.getRealClass(); - HelidonTest testAnnot = testClass.getAnnotation(HelidonTest.class); - if (testAnnot == null) { - return; - } - helidonTest = true; - resetPerTest = testAnnot.resetPerTest(); - - List metaAnnotations = extractMetaAnnotations(testClass); - - AddConfig[] configs = getAnnotations(testClass, AddConfig.class, metaAnnotations); +public class HelidonTestNgListener extends HelidonTestNgListenerBase implements ITestListener, + ISuiteListener, + IAlterSuiteListener { - classLevelConfigMeta.addConfig(configs); - classLevelConfigMeta.configuration(getAnnotation(testClass, Configuration.class, metaAnnotations)); - classLevelConfigMeta.addConfigBlock(getAnnotation(testClass, AddConfigBlock.class, metaAnnotations)); - configProviderResolver = ConfigProviderResolver.instance(); + private static final Logger LOGGER = System.getLogger(HelidonTestNgListener.class.getName()); - AddExtension[] extensions = getAnnotations(testClass, AddExtension.class, metaAnnotations); - classLevelExtensions.addAll(Arrays.asList(extensions)); - - AddBean[] beans = getAnnotations(testClass, AddBean.class, metaAnnotations); - classLevelBeans.addAll(Arrays.asList(beans)); - - if (testAnnot.pinningDetection()) { - pinningRecorder = PinningRecorder.create(); - pinningRecorder.record(Duration.ofMillis(testAnnot.pinningThreshold())); - } - - DisableDiscovery discovery = getAnnotation(testClass, DisableDiscovery.class, metaAnnotations); - if (discovery != null) { - classLevelDisableDiscovery = discovery.value(); - } - - if (resetPerTest) { - validatePerTest(); - return; - } - validatePerClass(); - - // add beans when using JaxRS - AddJaxRs addJaxRsAnnotation = getAnnotation(testClass, AddJaxRs.class, metaAnnotations); - if (addJaxRsAnnotation != null){ - classLevelExtensions.add(ProcessAllAnnotatedTypesLiteral.INSTANCE); - classLevelExtensions.add(ServerCdiExtensionLiteral.INSTANCE); - classLevelExtensions.add(JaxRsCdiExtensionLiteral.INSTANCE); - classLevelExtensions.add(CdiComponentProviderLiteral.INSTANCE); - classLevelBeans.add(WeldRequestScopeLiteral.INSTANCE); - } - - configure(classLevelConfigMeta); - - Object[] testInstances = iTestClass.getInstances(false); - if (testInstances != null && testInstances.length > 0) { - testInstance = testInstances[0]; - } + private static final List TYPE_ANNOTATIONS = List.of( + Proxies.annotation(Guice.class, attr -> { + if (attr.equals("moduleFactory")) { + return HelidonTestNgModuleFactory.class; + } + return null; + })); - if (!classLevelConfigMeta.useExisting) { - startContainer(classLevelBeans, classLevelExtensions, classLevelDisableDiscovery); - } - } + private static final List> METHOD_EXCLUDES = List.of( + BeforeTest.class, + BeforeSuite.class); + private final Semaphore semaphore = new Semaphore(1); + private volatile HelidonTestContainer container; @Override - public void onAfterClass(ITestClass testClass) { - if (!helidonTest) { - return; - } - - if (!resetPerTest) { - releaseConfig(); - stopContainer(); - } - if (pinningRecorder != null) { - pinningRecorder.close(); - pinningRecorder = null; + public void alter(List suites) { + for (XmlSuite suite : suites) { + for (XmlTest test : suite.getTests()) { + Set xmlClasses = new HashSet<>(test.getClasses()); + for (XmlPackage xmlPackage : test.getXmlPackages()) { + xmlClasses.addAll(xmlPackage.getXmlClasses()); + } + for (XmlClass xmlClass : xmlClasses) { + ClassInfo classInfo = classInfo(xmlClass.getSupportClass()); + if (classInfo.containsAnnotation(HelidonTest.class)) { + Class testClass = classInfo.element(); + if (Modifier.isAbstract(testClass.getModifiers())) { + continue; + } + if (Modifier.isFinal(testClass.getModifiers())) { + LOGGER.log(Level.WARNING, "Cannot instrument final class: {0}", testClass.getName()); + continue; + } + // Instrument the test class + // Add a @Guice annotation to install HelidonTestNgModuleFactory + // Use a proxy to start the container lazily + xmlClass.setClass(instrument(testClass, TYPE_ANNOTATIONS, METHOD_EXCLUDES, this::resolve)); + } + } + } } } @Override - public void onTestStart(ITestResult result) { - if (!helidonTest) { - return; - } - - if (resetPerTest) { - Method method = result.getMethod().getConstructorOrMethod().getMethod(); - AddConfig[] configs = method.getAnnotationsByType(AddConfig.class); - ConfigMeta methodLevelConfigMeta = classLevelConfigMeta.nextMethod(); - methodLevelConfigMeta.addConfig(configs); - methodLevelConfigMeta.configuration(method.getAnnotation(Configuration.class)); - methodLevelConfigMeta.addConfigBlock(method.getAnnotation(AddConfigBlock.class)); - - configure(methodLevelConfigMeta); - - List methodLevelExtensions = new ArrayList<>(classLevelExtensions); - List methodLevelBeans = new ArrayList<>(classLevelBeans); - boolean methodLevelDisableDiscovery = classLevelDisableDiscovery; - - AddExtension[] extensions = method.getAnnotationsByType(AddExtension.class); - methodLevelExtensions.addAll(Arrays.asList(extensions)); - - AddBean[] beans = method.getAnnotationsByType(AddBean.class); - methodLevelBeans.addAll(Arrays.asList(beans)); - - DisableDiscovery discovery = method.getAnnotation(DisableDiscovery.class); - if (discovery != null) { - methodLevelDisableDiscovery = discovery.value(); + public void onStart(ISuite suite) { + for (ITestNGMethod tm : suite.getAllMethods()) { + if (isInstrumented(tm.getTestClass().getRealClass())) { + // replace the built-in ITestClass with a decorator + // to hide the instrumented class name in the test results + tm.setTestClass(ClassDecorator.decorate(tm.getTestClass())); } - - startContainer(methodLevelBeans, methodLevelExtensions, methodLevelDisableDiscovery); } } @Override - public void onTestFailure(ITestResult iTestResult) { - if (!helidonTest) { - return; - } - - if (resetPerTest) { - releaseConfig(); - stopContainer(); - } + boolean filterClass(Class cls) { + return isInstrumented(cls); } @Override - public void onTestSuccess(ITestResult iTestResult) { - if (!helidonTest) { - return; - } - - if (resetPerTest) { - releaseConfig(); - stopContainer(); - } - } - - private void validatePerClass() { - validateMethods(testClass.getMethods()); - validateMethods(testClass.getDeclaredMethods()); - validateConstructors(testClass.getDeclaredConstructors()); - AddJaxRs addJaxRsAnnotation = testClass.getAnnotation(AddJaxRs.class); - if (addJaxRsAnnotation != null){ - if (testClass.getAnnotation(DisableDiscovery.class) == null){ - throw new RuntimeException("@AddJaxRs annotation should be used only with @DisableDiscovery annotation."); + void onBeforeInvocation(ClassContext classContext, MethodInfo methodInfo, HelidonTestInfo testInfo) { + LOGGER.log(Level.DEBUG, "onBeforeInvocation: {0}", testInfo.id()); + try { + if (testInfo.requiresReset()) { + semaphore.acquire(); + classContext.awaitMethods(); + closeContainer(testInfo); + initContainer(testInfo); + } else { + semaphore.acquire(); + initContainer(testInfo.classInfo()); + semaphore.release(); } + } catch (InterruptedException e) { + throw new RuntimeException(e); } } - private void validatePerTest() { - Constructor[] constructors = testClass.getConstructors(); - if (constructors.length > 1) { - throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," - + " the class must have only a single no-arg constructor"); - } - if (constructors.length == 1) { - Constructor c = constructors[0]; - if (c.getParameterCount() > 0) { - throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," - + " the class must have a no-arg constructor"); + @Override + void onAfterInvocation(MethodInfo methodInfo, HelidonTestInfo testInfo, boolean last) { + LOGGER.log(Level.DEBUG, "onAfterInvocation: {0}", methodInfo.id()); + if (last) { + if (testInfo.requiresReset()) { + closeContainer(testInfo); + semaphore.release(); } } - validateFields(testClass.getFields()); - validateFields(testClass.getDeclaredFields()); } - private void configure(ConfigMeta configMeta) { - if (config != null) { - configProviderResolver.releaseConfig(config); - } - if (!configMeta.useExisting) { - // only create a custom configuration if not provided by test method/class - // prepare configuration - ConfigBuilder builder = configProviderResolver.getBuilder(); - - configMeta.additionalSources.forEach(it -> { - String fileName = it.trim(); - int idx = fileName.lastIndexOf('.'); - String type = idx > -1 ? fileName.substring(idx + 1) : "properties"; - try { - Enumeration urls = Thread.currentThread().getContextClassLoader().getResources(fileName); - urls.asIterator().forEachRemaining(url -> builder.withSources(MpConfigSources.create(type, url))); - } catch (IOException e) { - throw new IllegalStateException("Failed to read \"" + fileName + "\" from classpath", e); - } - }); - if (configMeta.type != null && configMeta.block != null) { - builder.withSources(MpConfigSources.create(configMeta.type, new StringReader(configMeta.block))); - } - config = builder - .withSources(MpConfigSources.create(configMeta.additionalKeys)) - .addDefaultSources() - .addDiscoveredSources() - .addDiscoveredConverters() - .build(); - configProviderResolver.registerConfig(config, Thread.currentThread().getContextClassLoader()); - } + @Override + void onBeforeClass(ClassInfo classInfo) { + LOGGER.log(Level.DEBUG, "onBeforeClass: {0}", classInfo.id()); } - private void releaseConfig() { - if (configProviderResolver != null && config != null) { - configProviderResolver.releaseConfig(config); - config = null; - - classLevelExtensions = new ArrayList<>(); - classLevelBeans = new ArrayList<>(); - classLevelConfigMeta = new ConfigMeta(); - classLevelDisableDiscovery = false; - configProviderResolver = ConfigProviderResolver.instance(); - } + @Override + void onAfterClass(ClassInfo classInfo) { + LOGGER.log(Level.DEBUG, "onAfterClass: {0}", classInfo.id()); + closeContainer(classInfo); + semaphore.drainPermits(); + semaphore.release(); } - @SuppressWarnings("unchecked") - private void startContainer(List beanAnnotations, - List extensionAnnotations, - boolean disableDiscovery) { - - SeContainerInitializer initializer = SeContainerInitializer.newInstance(); - - if (disableDiscovery) { - initializer.disableDiscovery(); - } - - initializer.addExtensions( - new TestInstanceExtension(testInstance, testClass), - new AddBeansExtension(beanAnnotations)); - - for (AddExtension addExtension : extensionAnnotations) { - Class extensionClass = addExtension.value(); - if (Modifier.isPublic(extensionClass.getModifiers())) { - initializer.addExtensions(addExtension.value()); - } else { - throw new IllegalArgumentException("Extension classes must be public, but " + extensionClass - .getName() + " is not"); - } + private void initContainer(HelidonTestInfo testInfo) { + if (container == null) { + LOGGER.log(Level.DEBUG, "initContainer: {0}", testInfo.id()); + HelidonTestScope testScope = HelidonTestScope.ofContainer(); + container = new HelidonTestContainer(testInfo, testScope, HelidonTestExtensionImpl::new); } - - container = initializer.initialize(); - container.select(testClass).get(); } - private void stopContainer() { + private void closeContainer(HelidonTestInfo testInfo) { if (container != null) { + LOGGER.log(Level.DEBUG, "closeContainer: {0}", testInfo.id()); container.close(); container = null; } } - private List extractMetaAnnotations(Class testClass) { - Annotation[] testAnnotations = testClass.getAnnotations(); - for (Annotation testAnnotation : testAnnotations) { - List annotations = List.of(testAnnotation.annotationType().getAnnotations()); - List> annotationsClass = annotations.stream() - .map(Annotation::annotationType).collect(Collectors.toList()); - if (!Collections.disjoint(TEST_ANNOTATIONS, annotationsClass)) { - // Contains at least one of HELIDON_TEST_ANNOTATIONS - return annotations; - } + private T resolve(Class type, Method method) { + if (container == null) { + throw new IllegalStateException("Container not set"); } - return List.of(); + return container.resolveInstance(type); } - - private List annotationsByType(Class annotClass, List metaAnnotations) { - List byType = new ArrayList<>(); - for (Annotation annotation : metaAnnotations) { - if (annotation.annotationType() == annotClass) { - byType.add((T) annotation); - } - } - return byType; - } - - private T getAnnotation(Class testClass, Class annotClass, - List metaAnnotations) { - T annotation = testClass.getAnnotation(annotClass); - if (annotation == null) { - List byType = annotationsByType(annotClass, metaAnnotations); - if (!byType.isEmpty()) { - annotation = byType.get(0); - } - } - return annotation; - } - - @SuppressWarnings("unchecked") - private T[] getAnnotations(Class testClass, Class annotClass, - List metaAnnotations) { - // inherited does not help, as it only returns annot from superclass if - // child has none - T[] directAnnotations = testClass.getAnnotationsByType(annotClass); - - List allAnnotations = new ArrayList<>(List.of(directAnnotations)); - // Include meta annotations - allAnnotations.addAll(annotationsByType(annotClass, metaAnnotations)); - - Class superClass = testClass.getSuperclass(); - while (superClass != null) { - directAnnotations = superClass.getAnnotationsByType(annotClass); - allAnnotations.addAll(List.of(directAnnotations)); - superClass = superClass.getSuperclass(); - } - - Object result = Array.newInstance(annotClass, allAnnotations.size()); - for (int i = 0; i < allAnnotations.size(); i++) { - Array.set(result, i, allAnnotations.get(i)); - } - - return (T[]) result; - } - - private static void validateMethods(Method[] methods) { - for (Method method : methods) { - if (method.getAnnotation(Test.class) != null) { - // a test method - if (hasAnnotation(method, TEST_ANNOTATIONS)) { - throw new RuntimeException("When a class is annotated with @HelidonTest, " - + "there is a single CDI container used to invoke all " - + "test methods on the class. Method " + method - + " has an annotation that modifies container behavior."); - } - } - } - } - - private static void validateConstructors(Constructor[] constructors) { - for (Constructor constructor : constructors) { - if (constructor.getAnnotation(Inject.class) != null) { - if (hasAnnotation(constructor, TEST_ANNOTATIONS)) { - throw new RuntimeException("When a class is annotated with @HelidonTest, " - + "there is a single CDI container used to invoke all " - + "test methods on the class. Do not use @Inject annotation" - + "over constructor. Use it on each field."); - } - } - } - } - - private static void validateFields(Field[] fields) { - for (Field field : fields) { - if (field.getAnnotation(Inject.class) != null) { - throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," - + " injection into fields or constructor is not supported, as each" - + " test method uses a different CDI container. Field " + field - + " is annotated with @Inject"); - } - } - } - - private static boolean hasAnnotation(AnnotatedElement element, Set> annotations) { - for (Class aClass : annotations) { - if (element.getAnnotation(aClass) != null) { - return true; - } - } - return false; - } - - @SuppressWarnings("CdiManagedBeanInconsistencyInspection") - private record TestInstanceExtension(Object testInstance, Class testClass) implements Extension { - - void registerTestClass(@Observes BeforeBeanDiscovery event) { - event.addAnnotatedType(testClass, "testng-" + testClass.getName()) - .add(SingletonLiteral.INSTANCE); - } - - @SuppressWarnings("unchecked") - void registerTestInstances(@Observes ProcessInjectionTarget pit) { - if (pit.getAnnotatedType().getJavaClass().equals(testClass)) { - pit.setInjectionTarget(new TestInjectionTarget<>(pit.getInjectionTarget(), (T) testInstance)); - } - } - } - - @SuppressWarnings("CdiManagedBeanInconsistencyInspection") - private record AddBeansExtension(List addBeans) implements Extension { - - void registerOtherBeans(@Observes AfterBeanDiscovery event) { - event.addBean() - .addType(jakarta.ws.rs.client.WebTarget.class) - .scope(ApplicationScoped.class) - .produceWith(context -> ClientBuilder.newClient().target(clientUri())); - } - - void registerAddedBeans(@Observes BeforeBeanDiscovery event) { - for (AddBean beanDef : addBeans) { - Class beanType = beanDef.value(); - Class scopeType = beanDef.scope(); - - AnnotationLiteral scope = BEAN_DEFINING.get(scopeType); - if (scope == null) { - throw new IllegalStateException( - "Only on of " + BEAN_DEFINING.keySet() + " scopes are allowed in tests. Scope " - + scopeType.getName() + " is not allowed for bean " + beanDef.value().getName()); - } - - AnnotatedTypeConfigurator configurator = event.addAnnotatedType(beanType, "testng-" + beanType.getName()); - if (!hasAnnotation(beanType, BEAN_DEFINING.keySet())) { - configurator.add(scope); - } - } - } - - @SuppressWarnings("unchecked") - static String clientUri() { - try { - Class extClass = Class.forName("io.helidon.microprofile.server.ServerCdiExtension"); - Extension extension = CDI.current().getBeanManager().getExtension((Class) extClass); - Method m = extension.getClass().getMethod("port"); - int port = (int) m.invoke(extension); - return "http://localhost:" + port; - } catch (ReflectiveOperationException e) { - return "http://localhost:7001"; - } - } - } - - record TestInjectionTarget(InjectionTarget delegate, T testInstance) - implements InjectionTarget { - - @Override - public void dispose(T i) { - } - - @Override - public Set getInjectionPoints() { - return delegate.getInjectionPoints(); - } - - @Override - public void inject(T testInstance, CreationalContext cc) { - delegate.inject(testInstance, cc); - } - - @Override - public void postConstruct(T testInstance) { - delegate.postConstruct(testInstance); - } - - @Override - public void preDestroy(T testInstance) { - delegate.preDestroy(testInstance); - } - - @Override - public T produce(CreationalContext cc) { - return testInstance; - } - } - - private static final class ConfigMeta { - private final Map additionalKeys = new HashMap<>(); - private final List additionalSources = new ArrayList<>(); - private String type; - private String block; - private boolean useExisting; - private String profile; - - ConfigMeta() { - // to allow SeContainerInitializer (forbidden by default because of native image) - additionalKeys.put("mp.initializer.allow", "true"); - additionalKeys.put("mp.initializer.no-warn", "true"); - // to run on random port - additionalKeys.put("server.port", "0"); - // higher ordinal then all the defaults, system props and environment variables - additionalKeys.putIfAbsent(ConfigSource.CONFIG_ORDINAL, "1000"); - // profile - additionalKeys.put("mp.config.profile", "test"); - } - - void addConfig(AddConfig[] configs) { - for (AddConfig config : configs) { - additionalKeys.put(config.key(), config.value()); - } - } - - void configuration(Configuration config) { - if (config == null) { - return; - } - useExisting = config.useExisting(); - profile = config.profile(); - additionalSources.addAll(List.of(config.configSources())); - //set additional key for profile - additionalKeys.put("mp.config.profile", profile); - } - - void addConfigBlock(AddConfigBlock config) { - if (config == null) { - return; - } - this.type = config.type(); - this.block = config.value(); - } - - ConfigMeta nextMethod() { - ConfigMeta methodMeta = new ConfigMeta(); - - methodMeta.additionalKeys.putAll(this.additionalKeys); - methodMeta.additionalSources.addAll(this.additionalSources); - methodMeta.useExisting = this.useExisting; - methodMeta.profile = this.profile; - - return methodMeta; - } - } - - @SuppressWarnings("ClassExplicitlyAnnotation") - private static final class WeldRequestScopeLiteral extends AnnotationLiteral implements AddBean { - - static final WeldRequestScopeLiteral INSTANCE = new WeldRequestScopeLiteral(); - - @Override - public Class value() { - return org.glassfish.jersey.weld.se.WeldRequestScope.class; - } - - @Override - public Class scope() { - return RequestScoped.class; - } - } - - - @SuppressWarnings("ClassExplicitlyAnnotation") - private static final class ProcessAllAnnotatedTypesLiteral extends AnnotationLiteral implements AddExtension { - - static final ProcessAllAnnotatedTypesLiteral INSTANCE = new ProcessAllAnnotatedTypesLiteral(); - - @Override - public Class value() { - return org.glassfish.jersey.ext.cdi1x.internal.ProcessAllAnnotatedTypes.class; - } - } - - @SuppressWarnings("ClassExplicitlyAnnotation") - private static final class ServerCdiExtensionLiteral extends AnnotationLiteral implements AddExtension { - - static final ServerCdiExtensionLiteral INSTANCE = new ServerCdiExtensionLiteral(); - - @Override - public Class value() { - return ServerCdiExtension.class; - } - } - - @SuppressWarnings("ClassExplicitlyAnnotation") - private static final class JaxRsCdiExtensionLiteral extends AnnotationLiteral implements AddExtension { - - static final JaxRsCdiExtensionLiteral INSTANCE = new JaxRsCdiExtensionLiteral(); - - @Override - public Class value() { - return JaxRsCdiExtension.class; - } - } - - @SuppressWarnings("ClassExplicitlyAnnotation") - private static final class CdiComponentProviderLiteral extends AnnotationLiteral implements AddExtension { - - static final CdiComponentProviderLiteral INSTANCE = new CdiComponentProviderLiteral(); - - @Override - public Class value() { - return CdiComponentProvider.class; - } - } - - @SuppressWarnings("ClassExplicitlyAnnotation") - private static final class SingletonLiteral extends AnnotationLiteral implements Singleton { - static final SingletonLiteral INSTANCE = new SingletonLiteral(); - } - } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListenerBase.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListenerBase.java new file mode 100644 index 00000000000..039d3044c8c --- /dev/null +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListenerBase.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.testng; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; + +import io.helidon.microprofile.testing.HelidonTestInfo; +import io.helidon.microprofile.testing.HelidonTestInfo.ClassInfo; +import io.helidon.microprofile.testing.HelidonTestInfo.MethodInfo; + +import org.testng.IClassListener; +import org.testng.IConfigurationListener; +import org.testng.IInvokedMethod; +import org.testng.IInvokedMethodListener; +import org.testng.ITestClass; +import org.testng.ITestNGMethod; +import org.testng.ITestResult; +import org.testng.xml.XmlTest; + +/** + * Base listener. + * Implements the following features: + *

    + *
  • Single instrumented test class running at a time
  • + *
  • {@link #onAfterClass(ClassInfo)} invoked last
  • + *
+ */ +abstract class HelidonTestNgListenerBase implements IInvokedMethodListener, + IConfigurationListener, + IClassListener { + + private static final Map> CONTEXTS = new ConcurrentHashMap<>(); + private static final Semaphore SEMAPHORE = new Semaphore(1); + + /** + * Filter the given class. + * + * @param cls class + * @return {@code true} if should be processed, {@code false} otherwise + */ + abstract boolean filterClass(Class cls); + + /** + * Before invocation. + * + * @param classContext class context + * @param methodInfo invoked method info + * @param testInfo test info + */ + abstract void onBeforeInvocation(ClassContext classContext, MethodInfo methodInfo, HelidonTestInfo testInfo); + + /** + * After invocation. + * + * @param methodInfo invoked method info + * @param testInfo test info + * @param last {@code true} if this is the last invocation of {@code testInfo}, {@code false} otherwise + */ + abstract void onAfterInvocation(MethodInfo methodInfo, HelidonTestInfo testInfo, boolean last); + + /** + * Before class. + * + * @param classInfo class info + */ + abstract void onBeforeClass(ClassInfo classInfo); + + /** + * After class. + * + * @param classInfo class info + */ + abstract void onAfterClass(ClassInfo classInfo); + + @Override + public void onConfigurationFailure(ITestResult tr, ITestNGMethod tm) { + if (filterClass(realClass(tr))) { + ITestNGMethod im = tr.getMethod(); + classContext(im.getTestClass()).afterInvocation(im, tm); + } + } + + @Override + public void onConfigurationSuccess(ITestResult tr, ITestNGMethod tm) { + if (filterClass(realClass(tr))) { + ITestNGMethod im = tr.getMethod(); + classContext(im.getTestClass()).afterInvocation(im, tm); + } + } + + @Override + public void beforeConfiguration(ITestResult tr, ITestNGMethod tm) { + if (filterClass(realClass(tr))) { + ITestNGMethod im = tr.getMethod(); + classContext(im.getTestClass()).beforeInvocation(im, tm); + } + } + + @Override + public void beforeInvocation(IInvokedMethod im, ITestResult tr) { + if (filterClass(realClass(tr)) && im.isTestMethod()) { + ITestNGMethod tm = im.getTestMethod(); + classContext(tm.getTestClass()).beforeInvocation(tm, tm); + } + } + + @Override + public void afterInvocation(IInvokedMethod im, ITestResult tr) { + if (filterClass(realClass(tr)) && im.isTestMethod()) { + ITestNGMethod tm = im.getTestMethod(); + classContext(tm.getTestClass()).afterInvocation(tm, tm); + } + } + + @Override + public void onBeforeClass(ITestClass tc) { + Class cls = tc.getRealClass(); + if (filterClass(cls)) { + classContext(tc).beforeClass(); + } + } + + @Override + public void onAfterClass(ITestClass tc) { + if (filterClass(tc.getRealClass())) { + classContext(tc).afterClass(); + } + } + + private ClassContext classContext(ITestClass tc) { + return CONTEXTS.computeIfAbsent(tc.getXmlTest(), k -> new ConcurrentHashMap<>()) + .computeIfAbsent(tc, k -> new ClassContext(k, SEMAPHORE, this)); + } + + private static Class realClass(ITestResult tr) { + return tr.getTestClass().getRealClass(); + } +} diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgModuleFactory.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgModuleFactory.java new file mode 100644 index 00000000000..f8fddcf0142 --- /dev/null +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgModuleFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.testng; + +import io.helidon.microprofile.testing.Instrumented; + +import com.google.inject.AbstractModule; +import com.google.inject.Module; +import org.testng.IModuleFactory; +import org.testng.ITestContext; + +/** + * A simple Guice module factory that instantiates the instrumented test class. + */ +public class HelidonTestNgModuleFactory implements IModuleFactory { + @Override + public Module createModule(ITestContext context, Class testClass) { + return new ModuleImpl<>(testClass); + } + + private static class ModuleImpl extends AbstractModule { + private final Class testClass; + private final T testInstance; + + ModuleImpl(Class testClass) { + this.testClass = testClass; + this.testInstance = Instrumented.allocateInstance(testClass); + } + + @Override + protected void configure() { + bind(testClass).toProvider(() -> testInstance); + } + } +} diff --git a/microprofile/testing/testng/src/main/java/module-info.java b/microprofile/testing/testng/src/main/java/module-info.java index 02ddeb59bf3..36c90895bbc 100644 --- a/microprofile/testing/testng/src/main/java/module-info.java +++ b/microprofile/testing/testng/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,12 @@ */ module io.helidon.microprofile.testing.testng { - requires io.helidon.config.mp; - requires io.helidon.config.yaml.mp; - requires io.helidon.common.testing.vitualthreads; - requires io.helidon.microprofile.cdi; - requires jakarta.cdi; - requires jakarta.inject; - requires jakarta.ws.rs; - requires microprofile.config.api; - requires org.testng; + requires io.helidon.microprofile.testing; - requires static io.helidon.microprofile.server; - requires static jersey.cdi1x; - requires static jersey.weld2.se; + requires org.testng; + requires com.google.guice; exports io.helidon.microprofile.testing.testng; provides org.testng.ITestNGListener with io.helidon.microprofile.testing.testng.HelidonTestNgListener; - -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/pom.xml b/microprofile/tests/testing/junit5/pom.xml index 96223fb1873..b53c5519344 100644 --- a/microprofile/tests/testing/junit5/pom.xml +++ b/microprofile/tests/testing/junit5/pom.xml @@ -28,10 +28,9 @@ helidon-microprofile-tests-testing-junit5 Helidon Microprofile Tests Junit5 unit tests - - Test for JUnit5 integration to prevent cyclic dependendcies, - so the module can be used in MP config implementation - + + true + @@ -51,7 +50,7 @@ org.hamcrest - hamcrest-core + hamcrest-all test @@ -59,6 +58,11 @@ helidon-microprofile-testing-mocking test + + io.helidon.logging + helidon-logging-jul + test + org.mockito mockito-core @@ -69,6 +73,22 @@ junit-platform-testkit test - + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + ${project.build.testOutputDirectory}/logging.properties + + + ${redirectTestOutputToFile} + + + + diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/EnabledIfParameter.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/EnabledIfParameter.java new file mode 100644 index 00000000000..39f9b3221d6 --- /dev/null +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/EnabledIfParameter.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Enable tests based on parameters. + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(EnabledIfParameterCondition.class) +@interface EnabledIfParameter { + /** + * Parameter key. + * + * @return key + */ + String key(); + + /** + * Parameter value. + * + * @return value + */ + String value(); +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/EnabledIfParameterCondition.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/EnabledIfParameterCondition.java new file mode 100644 index 00000000000..58a97ad050d --- /dev/null +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/EnabledIfParameterCondition.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.junit5; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; + +/** + * Condition implementation of {@link EnabledIfParameter}. + */ +final class EnabledIfParameterCondition implements ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return context.getElement() + .flatMap(e -> Optional.ofNullable(e.getAnnotation(EnabledIfParameter.class))) + .map(a -> context.getConfigurationParameter(a.key()) + .map(v -> v.equals(a.value()) ? + enabled("parameter match: %s".formatted(a.key())) : + disabled("parameter mismatch: %s!=%s".formatted(a.value(), v))) + .orElse(disabled("parameter not found: %s".formatted(a.key())))) + .orElse(enabled("@EnabledIfParameter not present")); + } +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAddBean.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAddBean.java index e9f52b74423..29e116d518d 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAddBean.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAddBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,4 +53,4 @@ String configured() { return configured; } } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAddConfigSource.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAddConfigSource.java new file mode 100644 index 00000000000..05636523970 --- /dev/null +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAddConfigSource.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.junit5; + +import java.util.Map; + +import jakarta.inject.Inject; + +import io.helidon.config.mp.MpConfigSources; +import io.helidon.microprofile.testing.AddConfigSource; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +class TestAddConfigSource { + + @AddConfigSource + static ConfigSource config() { + return MpConfigSources.create(Map.of( + "some.key", "some.value", + "another.key", "another.value")); + } + + @Inject + @ConfigProperty(name = "some.key") + private String someKey; + + @Inject + @ConfigProperty(name = "another.key") + private String anotherKey; + + @Test + void testValue() { + assertThat(someKey, is("some.value")); + assertThat(anotherKey, is("another.value")); + } +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAlternativeObserver.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAlternativeObserver.java new file mode 100644 index 00000000000..ef18d18bbf1 --- /dev/null +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAlternativeObserver.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.junit5; + +import io.helidon.microprofile.testing.junit5.AddBean; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Alternative; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@AddBean(TestAlternativeObserver.AlternativeObserver.class) +@AddBean(TestAlternativeObserver.AlternativeObserver.class) +@HelidonTest +class TestAlternativeObserver { + + @Inject + Observer observer; + + @Test + void doTest() { + assertThat(observer.started, is(true)); + } + + @Singleton + static class Observer { + + boolean started = false; + + void onStart(@Observes @Initialized(ApplicationScoped.class) Object ignore) { + started = true; + } + } + + @Priority(0) + @Alternative + @Singleton + static class AlternativeObserver extends Observer { + } +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestConstructorInjection.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestConstructorInjection.java index 612c07a064f..7be8db2123a 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestConstructorInjection.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestConstructorInjection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,4 +54,4 @@ public int currentPort() { return 423; } } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestCustomConfig.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestCustomConfig.java index 636e6e13bcc..60ceb80f4b6 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestCustomConfig.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestCustomConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,4 +53,4 @@ void testIt() { assertThat(shouldExist, is(PROPERTY_VALUE)); assertThat(anotherValue, is("test-custom-config-second-value")); } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestCustomExtension.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestCustomExtension.java index 1236569b873..1207b7f49b0 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestCustomExtension.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestCustomExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,4 +43,4 @@ void observer(@Observes @Initialized(ApplicationScoped.class) final Object event called = true; } } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestDefaults.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestDefaults.java index 5495d5aa1b3..e7a3ea3c20e 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestDefaults.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,4 +66,4 @@ void testLifecycleMethodsCalled() { assertThat("Before all should have been called", beforeAllCalled, is(true)); assertThat("Before each should have been called", beforeEachCalled, is(true)); } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestMetaAnnotation.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestMetaAnnotation.java index 5720506120f..e441778690e 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestMetaAnnotation.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestMetaAnnotation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,11 @@ package io.helidon.microprofile.tests.testing.junit5; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import jakarta.inject.Inject; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import jakarta.inject.Inject; + import io.helidon.microprofile.testing.junit5.AddBean; import io.helidon.microprofile.testing.junit5.AddConfig; import io.helidon.microprofile.testing.junit5.AddConfigBlock; @@ -33,9 +30,10 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + @TestMetaAnnotation.MetaAnnotation -// @HelidonTest is still mandatory in the test and it has no effect in the MetaAnnotation -@HelidonTest class TestMetaAnnotation { @Inject @@ -78,8 +76,9 @@ void testIt() { """) @AddConfig(key = "second-key", value = "test-custom-config-second-value") @Configuration(configSources = {"testConfigSources.properties", "testConfigSources.yaml"}) + @HelidonTest @Retention(RetentionPolicy.RUNTIME) - static @interface MetaAnnotation { + @interface MetaAnnotation { } static class MyBean { diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestMockBeanAnswer.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestMockBeanAnswer.java index 1a5874f323e..2994a13829c 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestMockBeanAnswer.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestMockBeanAnswer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -31,7 +30,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Answers; -import org.mockito.MockSettings; import org.mockito.Mockito; @HelidonTest diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestNamedWebTarget.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestNamedWebTarget.java index 075603de259..5af1f594f88 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestNamedWebTarget.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestNamedWebTarget.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,4 +82,4 @@ public String getNamed() { return "Hello from Named Resource"; } } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestNoDiscovery.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestNoDiscovery.java index f8d90eab0f5..36a0542106e 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestNoDiscovery.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestNoDiscovery.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,4 +56,4 @@ void testIt() { public static class MyBean { } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassConcurrentExecution.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassConcurrentExecution.java new file mode 100644 index 00000000000..26ee1707c83 --- /dev/null +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassConcurrentExecution.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.junit5; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; + +import io.helidon.microprofile.testing.junit5.AddBean; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@HelidonTest +@AddBean(TestPerClassConcurrentExecution.State.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestPerClassConcurrentExecution { + + @Inject + State state; + + @Test + @Execution(ExecutionMode.CONCURRENT) + void testA() throws InterruptedException { + state.add("a"); + state.countDownA(); + state.awaitB(); + assertThat(state.get(), is(Set.of("a", "b"))); + } + + @Test + @Execution(ExecutionMode.CONCURRENT) + void testB() throws InterruptedException { + state.awaitA(); + assertThat(state.get(), is(Set.of("a"))); + state.add("b"); + state.countDownB(); + } + + @ApplicationScoped + static class State { + + final Set events = ConcurrentHashMap.newKeySet(); + final CountDownLatch latchA = new CountDownLatch(1); + final CountDownLatch latchB = new CountDownLatch(1); + + void add(String event) { + events.add(event); + } + + Set get() { + return events; + } + + void countDownA() { + latchA.countDown(); + } + + void awaitA() throws InterruptedException { + latchA.await(); + } + + void countDownB() { + latchB.countDown(); + } + + void awaitB() throws InterruptedException { + latchB.await(); + } + } +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassCtorInjection.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassCtorInjection.java new file mode 100644 index 00000000000..e5ba170be14 --- /dev/null +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassCtorInjection.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.junit5; + +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import jakarta.inject.Inject; +import jakarta.ws.rs.client.WebTarget; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestPerClassCtorInjection { + + private final WebTarget target; + + @Inject + TestPerClassCtorInjection(WebTarget target) { + this.target = target; + } + + @Test + void testCtorInjection() { + assertThat(target, is(not(nullValue()))); + } +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassImplicitReset.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassImplicitReset.java new file mode 100644 index 00000000000..5f5cfd7b4d1 --- /dev/null +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassImplicitReset.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.junit5; + +import io.helidon.microprofile.testing.junit5.AddBean; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@HelidonTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestPerClassImplicitReset { + + @Inject + CounterBean counterBean; + + @Test + @AddBean(CounterBean.class) + void firstTest() { + assertThat(counterBean.value++, is(0)); + } + + @Test + @AddBean(CounterBean.class) + void secondTest() { + assertThat(counterBean.value++, is(0)); + } + + @ApplicationScoped + static class CounterBean { + int value = 0; + } +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassMethodOrder.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassMethodOrder.java new file mode 100644 index 00000000000..9fce4d28aa0 --- /dev/null +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPerClassMethodOrder.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.junit5; + +import java.util.ArrayList; +import java.util.List; + +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@HelidonTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class TestPerClassMethodOrder { + + private final List list = new ArrayList<>(); + + @Test + @Order(1) + void firstTest() { + list.add("firstTest"); + assertThat(list, is(List.of("firstTest"))); + } + + @Test + @Order(2) + void secondTest() { + list.add("secondTest"); + assertThat(list, is(List.of("firstTest", "secondTest"))); + } + + @Test + @Order(3) + void thirdTest() { + list.add("thirdTest"); + assertThat(list, is(List.of("firstTest", "secondTest", "thirdTest"))); + } +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPinnedThread.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPinnedThread.java index 398bd0c4424..76ad4a8ae9a 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPinnedThread.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestPinnedThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,11 +40,13 @@ import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +@SuppressWarnings("ALL") class TestPinnedThread { @Test void engineTest() { Events events = EngineTestKit.engine("junit-jupiter") + .configurationParameter("TestPinnedThread", "true") .selectors( selectClass(PinningTestCase.class), selectClass(PinningExtraThreadTestCase.class), @@ -64,7 +66,7 @@ void engineTest() { ); } - private Condition failedWithPinningException(String expectedPinningMethodName) { + private Condition failedWithPinningException(String expectedPinningMethodName) { return finishedWithFailure( instanceOf(PinningAssertionError.class), message(m -> m.startsWith("Pinned virtual threads were detected")) @@ -73,7 +75,7 @@ private Condition failedWithPinningExce s -> s .anyMatch(e -> e.getMethodName() .equals(expectedPinningMethodName))), - "Method with pinning is missing from stack strace.") + "Method with pinning is missing from stack strace.") ); } @@ -81,6 +83,7 @@ private Condition displayClass(Class clazz) { return displayName(Arrays.stream(clazz.getName().split("\\.")).toList().getLast()); } + @EnabledIfParameter(key = "TestPinnedThread", value = "true") @HelidonTest(pinningDetection = true) @AddBean(PinningTestCase.TestResource.class) static class PinningTestCase { @@ -108,6 +111,7 @@ void pinningTest(WebTarget target) { } } + @EnabledIfParameter(key = "TestPinnedThread", value = "true") @HelidonTest(pinningDetection = true) static class PinningExtraThreadTestCase { @@ -125,6 +129,7 @@ void pinningTest() throws InterruptedException { } } + @EnabledIfParameter(key = "TestPinnedThread", value = "true") @HelidonTest(pinningDetection = false) static class PinningDisabledExtraThreadTestCase { @@ -142,6 +147,7 @@ void pinningTest() throws InterruptedException { } } + @EnabledIfParameter(key = "TestPinnedThread", value = "true") @HelidonTest(pinningDetection = true) static class NoPinningTestCase { @@ -168,6 +174,7 @@ void pinningTest(WebTarget target) { } } + @EnabledIfParameter(key = "TestPinnedThread", value = "true") @HelidonTest(pinningDetection = true) static class NoPinningExtraThreadTestCase { diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestReqScopeDisabledDiscovery.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestReqScopeDisabledDiscovery.java index b8831785ba8..fa6846847da 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestReqScopeDisabledDiscovery.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestReqScopeDisabledDiscovery.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,8 +28,6 @@ @HelidonTest @DisableDiscovery - -// JAX-RS Request scope @AddJaxRs @AddBean(TestReqScopeDisabledDiscovery.MyController.class) class TestReqScopeDisabledDiscovery { @@ -52,4 +50,4 @@ public Response get() { return Response.ok("Hallo!").build(); } } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestWebTarget.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestWebTarget.java index 7b8070ff0af..6785d4a30fd 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestWebTarget.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestWebTarget.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,4 +65,4 @@ public String getIt() { return "Hello from ResourceClass"; } } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestWebTargetPerMethod.java b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestWebTargetPerMethod.java index dc1e11baf73..a1ae8fdace9 100644 --- a/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestWebTargetPerMethod.java +++ b/microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestWebTargetPerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,4 +69,4 @@ public String getIt() { return "Hello from ResourceClass"; } } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/junit5/src/test/resources/junit-platform.properties b/microprofile/tests/testing/junit5/src/test/resources/junit-platform.properties new file mode 100644 index 00000000000..99794e3ba19 --- /dev/null +++ b/microprofile/tests/testing/junit5/src/test/resources/junit-platform.properties @@ -0,0 +1,16 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# +junit.jupiter.execution.parallel.enabled=true diff --git a/tests/integration/jpa/simple/src/test/resources/logging.properties b/microprofile/tests/testing/junit5/src/test/resources/logging.properties similarity index 64% rename from tests/integration/jpa/simple/src/test/resources/logging.properties rename to microprofile/tests/testing/junit5/src/test/resources/logging.properties index e5f909036bd..7454cdab319 100644 --- a/tests/integration/jpa/simple/src/test/resources/logging.properties +++ b/microprofile/tests/testing/junit5/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2023 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,15 +14,8 @@ # limitations under the License. # -# Example Logging Configuration File -# For more information see $JAVA_HOME/jre/lib/logging.properties -# Send messages to the console - handlers=io.helidon.logging.jul.HelidonConsoleHandler -java.util.logging.SimpleFormatter.format=%4$s %3$s: %5$s%6$s%n - -.level=WARNING +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %5$s%6$s%n -io.helidon.level=INFO -org.hibernate.level=INFO -com.zaxxer.level=INFO +.level=SEVERE +io.helidon.microprofile.testing.level=FINEST diff --git a/microprofile/tests/testing/testng/pom.xml b/microprofile/tests/testing/testng/pom.xml index 7e8f9022f5a..e491d7cd13a 100644 --- a/microprofile/tests/testing/testng/pom.xml +++ b/microprofile/tests/testing/testng/pom.xml @@ -15,8 +15,8 @@ limitations under the License. --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 io.helidon.microprofile.tests.testing @@ -27,9 +27,9 @@ helidon-microprofile-tests-testing-testng Helidon Microprofile Tests TestNG unit tests - - Test for TestNG integration to prevent cyclic dependencies, so the module can be used in MP config implementation - + + true + @@ -48,13 +48,13 @@ test - org.hamcrest - hamcrest-core + io.helidon.microprofile.testing + helidon-microprofile-testing-mocking test - io.helidon.microprofile.testing - helidon-microprofile-testing-mocking + io.helidon.logging + helidon-logging-jul test @@ -62,10 +62,19 @@ mockito-core test + + org.slf4j + slf4j-jdk14 + test + + + org.hamcrest + hamcrest-all + test + - org.apache.maven.plugins @@ -74,6 +83,12 @@ test-suite.xml + + + ${project.build.testOutputDirectory}/logging.properties + + + ${redirectTestOutputToFile} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/EnabledIfParameter.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/EnabledIfParameter.java new file mode 100644 index 00000000000..9555baf1414 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/EnabledIfParameter.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Enable tests based on parameters. + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@interface EnabledIfParameter { + /** + * Parameter key. + * + * @return key + */ + String key(); + + /** + * Parameter value. + * + * @return value + */ + String value(); +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/EnabledIfParameterListener.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/EnabledIfParameterListener.java new file mode 100644 index 00000000000..f57ba042a56 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/EnabledIfParameterListener.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import java.util.List; +import java.util.ListIterator; + +import org.testng.IAlterSuiteListener; +import org.testng.xml.XmlClass; +import org.testng.xml.XmlPackage; +import org.testng.xml.XmlSuite; +import org.testng.xml.XmlTest; + +/** + * An {@link IAlterSuiteListener} implementation that supports {@link EnabledIfParameter}. + */ +public final class EnabledIfParameterListener implements IAlterSuiteListener { + + @Override + public void alter(List suites) { + for (XmlSuite suite : suites) { + ListIterator testsIt = suite.getTests().listIterator(); + while (testsIt.hasNext()) { + XmlTest xmlTest = testsIt.next(); + ListIterator classesIt = xmlTest.getXmlClasses().listIterator(); + while (classesIt.hasNext()) { + XmlClass xmlClass = classesIt.next(); + if (isDisabled(xmlClass, xmlTest)) { + // remove test class + classesIt.remove(); + removeIfEmpty(classesIt, testsIt); + } + } + ListIterator packagesIt = xmlTest.getXmlPackages().listIterator(); + while (packagesIt.hasNext()) { + XmlPackage xmlPackage = packagesIt.next(); + ListIterator pkgClassesIt = xmlPackage.getXmlClasses().listIterator(); + while (pkgClassesIt.hasNext()) { + XmlClass xmlClass = pkgClassesIt.next(); + if (isDisabled(xmlClass, xmlTest)) { + // remove test class + pkgClassesIt.remove(); + removeIfEmpty(pkgClassesIt, packagesIt); + removeIfEmpty(pkgClassesIt, testsIt); + } + } + } + } + } + } + + private static boolean isDisabled(XmlClass xmlClass, XmlTest xmlTest) { + Class testClass = xmlClass.getSupportClass(); + EnabledIfParameter annotation = testClass.getAnnotation(EnabledIfParameter.class); + return annotation != null && !annotation.value().equals(xmlTest.getParameter(annotation.key())); + } + + private static void removeIfEmpty(ListIterator childIt, ListIterator parentIt) { + if (!childIt.hasPrevious() && !childIt.hasNext()) { + parentIt.remove(); + } + } +} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/MPTest.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/ParallelMethods.java similarity index 72% rename from tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/MPTest.java rename to microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/ParallelMethods.java index 16cb4c98184..6250c9410d8 100644 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/MPTest.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/ParallelMethods.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.tests.integration.jpa.appl; +package io.helidon.microprofile.tests.testing.testng; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -21,10 +21,9 @@ import java.lang.annotation.Target; /** - * Test method annotation. - * This is security enhancement which restricts methods invoked by Dispatcher only to those with this annotation. + * Marks the methods of a class to be executed with {@link org.testng.xml.XmlSuite.ParallelMode#METHODS}. */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface MPTest { +@Target(ElementType.TYPE) +@interface ParallelMethods { } diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/ParallelizerListenerImpl.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/ParallelizerListenerImpl.java new file mode 100644 index 00000000000..52bff791497 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/ParallelizerListenerImpl.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import java.lang.reflect.Method; +import java.util.List; + +import io.helidon.microprofile.testing.testng.HelidonTest; + +import org.testng.IAlterSuiteListener; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.testng.xml.XmlClass; +import org.testng.xml.XmlPackage; +import org.testng.xml.XmlSuite; +import org.testng.xml.XmlTest; + +/** + * An {@link IAlterSuiteListener} implementation that customizes the parallel mode. + */ +public class ParallelizerListenerImpl implements IAlterSuiteListener { + + @Override + public void alter(List suites) { + for (XmlSuite suite : suites) { + suite.setParallel(XmlSuite.ParallelMode.METHODS); + // one thread per method + for (XmlTest test : suite.getTests()) { + int methodsCount = 0; + for (XmlClass xmlClass : test.getXmlClasses()) { + methodsCount += methodsCount(xmlClass); + } + for (XmlPackage xmlPackage : test.getXmlPackages()) { + for (XmlClass xmlClass : xmlPackage.getXmlClasses()) { + methodsCount += methodsCount(xmlClass); + } + } + test.setThreadCount(methodsCount); + } + } + } + + private int methodsCount(XmlClass xmlClass) { + int count = 0; + Class testClass = xmlClass.getSupportClass(); + if (testClass.isAnnotationPresent(HelidonTest.class)) { + for (Method method : testClass.getDeclaredMethods()) { + if (method.isAnnotationPresent(Test.class) + || method.isAnnotationPresent(BeforeClass.class) + || method.isAnnotationPresent(AfterClass.class)) { + count++; + } + } + } + return count; + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddBean.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddBean.java index 9febd386196..3d5441c933f 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddBean.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigBlockProperties.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigBlockProperties.java index e7bbbf90373..532e345824c 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigBlockProperties.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigBlockProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ some.key1=some.value1 some.key2=some.value2 """) -class TestAddConfigBlockProperties { +public class TestAddConfigBlockProperties { @Inject @ConfigProperty(name = "some.key1") diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigBlockYaml.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigBlockYaml.java index a23bd9e529c..dc34e0c4695 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigBlockYaml.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigBlockYaml.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ another2: key: "another2.value" """) -class TestAddConfigBlockYaml { +public class TestAddConfigBlockYaml { @Inject @ConfigProperty(name = "another1.key") diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigSource.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigSource.java new file mode 100644 index 00000000000..6fd2fc3e37c --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAddConfigSource.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import java.util.Map; + +import io.helidon.config.mp.MpConfigSources; +import io.helidon.microprofile.testing.AddConfigSource; +import io.helidon.microprofile.testing.testng.HelidonTest; + +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.testng.annotations.Test; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +public class TestAddConfigSource { + + @AddConfigSource + static ConfigSource config() { + return MpConfigSources.create(Map.of( + "some.key", "some.value", + "another.key", "another.value")); + } + + @Inject + @ConfigProperty(name = "some.key") + private String someKey; + + @Inject + @ConfigProperty(name = "another.key") + private String anotherKey; + + @Test + void testValue() { + assertThat(someKey, is("some.value")); + assertThat(anotherKey, is("another.value")); + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAlternativeObserver.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAlternativeObserver.java new file mode 100644 index 00000000000..0ac37614e8d --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestAlternativeObserver.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import io.helidon.microprofile.testing.testng.AddBean; +import io.helidon.microprofile.testing.testng.HelidonTest; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Alternative; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@AddBean(TestAlternativeObserver.AlternativeObserver.class) +@AddBean(TestAlternativeObserver.AlternativeObserver.class) +@HelidonTest +public class TestAlternativeObserver { + + @Inject + Observer observer; + + @Test + void doTest() { + assertThat(observer.started, is(true)); + } + + @Singleton + static class Observer { + + boolean started = false; + + void onStart(@Observes @Initialized(ApplicationScoped.class) Object ignore) { + started = true; + } + } + + @Priority(0) + @Alternative + @Singleton + static class AlternativeObserver extends Observer { + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestConstructorInjection.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestConstructorInjection.java new file mode 100644 index 00000000000..ad8de46cb25 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestConstructorInjection.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import io.helidon.microprofile.testing.testng.AddBean; +import io.helidon.microprofile.testing.testng.DisableDiscovery; +import io.helidon.microprofile.testing.testng.HelidonTest; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.testng.annotations.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test when discovery is disabled. + */ +@HelidonTest +@DisableDiscovery +@AddBean(TestConstructorInjection.MyBean.class) +public class TestConstructorInjection { + private final int currentPort; + + @Inject + public TestConstructorInjection(@Named("port") int currentPort) { + this.currentPort = currentPort; + } + + @Test + void testIt() { + assertThat(currentPort, is(423)); + } + + public static class MyBean { + @Produces + @Named("port") + public int currentPort() { + return 423; + } + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestCustomConfig.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestCustomConfig.java index b64b5b55bc7..c575d77dc7b 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestCustomConfig.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestCustomConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,4 +53,4 @@ void testIt() { assertThat(shouldExist, is(PROPERTY_VALUE)); assertThat(anotherValue, is("test-custom-config-second-value")); } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestDefaults.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestDefaults.java index a3d84cfe1b8..b08cc375a06 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestDefaults.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import jakarta.inject.Inject; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static org.hamcrest.CoreMatchers.is; @@ -41,17 +41,17 @@ public class TestDefaults { @ConfigProperty(name = PROPERTY_NAME, defaultValue = DEFAULT_VALUE) private String shouldNotExist; - private static boolean beforeAllCalled; - private boolean beforeEachCalled; + private boolean beforeClassCalled; + private boolean beforeMethodCalled; @BeforeClass - static void initClass() { - beforeAllCalled = true; + void beforeClass() { + beforeClassCalled = true; } - @BeforeTest - void beforeEach() { - beforeEachCalled = true; + @BeforeMethod + void beforeMethod() { + beforeMethodCalled = true; } @Test @@ -62,8 +62,7 @@ void testIt() { @Test void testLifecycleMethodsCalled() { - // this is to validate we can still use the usual junit methods - assertThat("Before all should have been called", beforeAllCalled, is(true)); - assertThat("Before each should have been called", beforeEachCalled, is(true)); + assertThat(beforeClassCalled, is(true)); + assertThat(beforeMethodCalled, is(true)); } -} \ No newline at end of file +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMetaAnnotation.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMetaAnnotation.java index 85b6d8c4d31..aeab8b7e4d6 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMetaAnnotation.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMetaAnnotation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ @TestMetaAnnotation.MetaAnnotation // @HelidonTest is still mandatory in the test and it has no effect in the MetaAnnotation @HelidonTest -class TestMetaAnnotation { +public class TestMetaAnnotation { @Inject MyBean bean; diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanArgumentMatcher.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanArgumentMatcher.java index bac3c2484fd..f4d1c1be695 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanArgumentMatcher.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanArgumentMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ @HelidonTest @AddBean(TestMockBeanArgumentMatcher.Resource.class) @AddBean(TestMockBeanArgumentMatcher.Service.class) -class TestMockBeanArgumentMatcher { +public class TestMockBeanArgumentMatcher { @MockBean private Service service; diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanField.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanField.java index 2f63646acce..0888383cd6b 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanField.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanField.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ @HelidonTest @AddBean(TestMockBeanField.Resource.class) @AddBean(TestMockBeanField.Service.class) -class TestMockBeanField { +public class TestMockBeanField { @Inject @MockBean diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanParameter.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanParameter.java new file mode 100644 index 00000000000..b5d1fe05b81 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestMockBeanParameter.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import io.helidon.microprofile.testing.testng.AddBean; +import io.helidon.microprofile.testing.testng.HelidonTest; +import io.helidon.microprofile.testing.mocking.MockBean; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.WebTarget; +import org.mockito.Answers; +import org.mockito.MockSettings; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +@AddBean(TestMockBeanParameter.Resource.class) +@AddBean(TestMockBeanParameter.Service.class) +public class TestMockBeanParameter { + + @Inject + @MockBean + private Service service; + + @Inject + private WebTarget target; + + @Test + void injectionTest() { + String response = target.path("/test").request().get(String.class); + // Defaults to specified in @Produces + assertThat(response, is("")); + Mockito.when(service.test()).thenReturn("Mocked"); + response = target.path("/test").request().get(String.class); + assertThat(response, is("Mocked")); + } + + @Produces + MockSettings mockSettings() { + return Mockito.withSettings().defaultAnswer(Answers.RETURNS_DEFAULTS); + } + + @Path("/test") + public static class Resource { + + @Inject + private Service service; + + @GET + public String test() { + return service.test(); + } + } + + static class Service { + + String test() { + return "Not Mocked"; + } + + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestNGHelper.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestNGHelper.java new file mode 100644 index 00000000000..53575f02898 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestNGHelper.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import java.util.List; +import java.util.Map; + +import org.testng.IAlterSuiteListener; +import org.testng.TestListenerAdapter; +import org.testng.TestNG; +import org.testng.xml.XmlSuite; + +/** + * {@link TestNG} helper. + */ +class TestNGHelper { + + private TestNGHelper() { + } + + /** + * Run a test programmatically. + * + * @param name test name + * @param testClasses test classes + * @return TestListenerAdapter + */ + static TestListenerAdapter run(String name, Class... testClasses) { + TestNG testng = new TestNG(false); + testng.setDefaultTestName(name); + testng.setTestClasses(testClasses); + TestListenerAdapter tla = new TestListenerAdapter(); + testng.addListener(tla); + testng.addListener(new SuiteParameterAdapter(Map.of(name, "true"))); + testng.setListenersToSkipFromBeingWiredInViaServiceLoaders(ParallelizerListenerImpl.class.getName()); + testng.setVerbose(0); + testng.run(); + return tla; + } + + private record SuiteParameterAdapter(Map parameters) implements IAlterSuiteListener { + @Override + public void alter(List suites) { + for (XmlSuite suite : suites) { + suite.getParameters().putAll(parameters); + } + } + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestNamedWebTarget.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestNamedWebTarget.java new file mode 100644 index 00000000000..3266b549c00 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestNamedWebTarget.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import io.helidon.microprofile.testing.Socket; +import io.helidon.microprofile.testing.testng.AddBean; +import io.helidon.microprofile.testing.testng.AddConfig; +import io.helidon.microprofile.testing.testng.HelidonTest; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.WebTarget; +import org.testng.annotations.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +@AddBean(TestNamedWebTarget.ResourceClass.class) +@AddConfig(key = "server.sockets.0.port", value = "0") +@AddConfig(key = "server.sockets.0.name", value = "named") +public class TestNamedWebTarget { + + @Inject + private WebTarget target; + + @Inject + @Socket("named") + private WebTarget namedTarget; + + @Test + void testTargetsAreDifferent(){ + //Should be different + assertThat(target.getUri(), not(namedTarget.getUri())); + } + + @Test + void testRegularTarget() { + assertThat(target, notNullValue()); + String response = target.path("/test") + .request() + .get(String.class); + assertThat(response, is("Hello from ResourceClass")); + } + + @Test + void testNamedSocketTarget() { + assertThat(namedTarget, notNullValue()); + String response = namedTarget.path("/test/named") + .request() + .get(String.class); + assertThat(response, is("Hello from Named Resource")); + } + + @Path("/test") + public static class ResourceClass { + @GET + public String getIt() { + return "Hello from ResourceClass"; + } + + @GET + @Path("named") + public String getNamed() { + return "Hello from Named Resource"; + } + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestParallelMethods.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestParallelMethods.java new file mode 100644 index 00000000000..55da0dd2ed1 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestParallelMethods.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; + +import io.helidon.microprofile.testing.testng.AddBean; +import io.helidon.microprofile.testing.testng.HelidonTest; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@HelidonTest +@AddBean(TestParallelMethods.State.class) +@ParallelMethods +public class TestParallelMethods { + + @Inject + State state; + + @Test + void testA() throws InterruptedException { + state.add("a"); + state.countDownA(); + state.awaitB(); + assertThat(state.get(), is(Set.of("a", "b"))); + } + + @Test + void testB() throws InterruptedException { + state.awaitA(); + assertThat(state.get(), is(Set.of("a"))); + state.add("b"); + state.countDownB(); + } + + @ApplicationScoped + static class State { + + final Set events = ConcurrentHashMap.newKeySet(); + final CountDownLatch latchA = new CountDownLatch(1); + final CountDownLatch latchB = new CountDownLatch(1); + + void add(String event) { + events.add(event); + } + + Set get() { + return events; + } + + void countDownA() { + latchA.countDown(); + } + + void awaitA() throws InterruptedException { + latchA.await(); + } + + void countDownB() { + latchB.countDown(); + } + + void awaitB() throws InterruptedException { + latchB.await(); + } + } + +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerClassImplicitReset.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerClassImplicitReset.java new file mode 100644 index 00000000000..f292d23ee17 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerClassImplicitReset.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import io.helidon.microprofile.testing.testng.AddBean; +import io.helidon.microprofile.testing.testng.HelidonTest; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@HelidonTest +public class TestPerClassImplicitReset { + + @Inject + CounterBean counterBean; + + @Test + @AddBean(CounterBean.class) + void firstTest() { + assertThat(counterBean.value++, is(0)); + } + + @Test + @AddBean(CounterBean.class) + void secondTest() { + assertThat(counterBean.value++, is(0)); + } + + @ApplicationScoped + static class CounterBean { + int value = 0; + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerClassMethodOrder.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerClassMethodOrder.java new file mode 100644 index 00000000000..d046d37b833 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerClassMethodOrder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import org.testng.TestListenerAdapter; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class TestPerClassMethodOrder { + + @Test + void testListener() { + TestListenerAdapter tla = TestNGHelper.run("TestPerClassMethodOrder", TestPerClassMethodOrderExtra.class); + assertThat(tla.getPassedTests().size(), is(3)); + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerClassMethodOrderExtra.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerClassMethodOrderExtra.java new file mode 100644 index 00000000000..f55381ab9c0 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerClassMethodOrderExtra.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import java.util.ArrayList; +import java.util.List; + +import io.helidon.microprofile.testing.testng.HelidonTest; + +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +/** + * Test executed programmatically from {@link TestPerClassMethodOrder}. + */ +@EnabledIfParameter(key = "TestPerClassMethodOrder", value = "true") +@HelidonTest +public class TestPerClassMethodOrderExtra { + + private final List list = new ArrayList<>(); + + @Test(priority = 1) + void firstTest() { + list.add("firstTest"); + assertThat(list, is(List.of("firstTest"))); + } + + @Test(priority = 2) + void secondTest() { + list.add("secondTest"); + assertThat(list, is(List.of("firstTest", "secondTest"))); + } + + @Test(priority = 3) + void thirdTest() { + list.add("thirdTest"); + assertThat(list, is(List.of("firstTest", "secondTest", "thirdTest"))); + } +} diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerMethod.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerMethod.java index cc19145b992..298bc89b8e2 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerMethod.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,12 +24,12 @@ import io.helidon.microprofile.testing.testng.AddBean; import io.helidon.microprofile.testing.testng.AddConfig; import io.helidon.microprofile.testing.testng.AddExtension; +import io.helidon.microprofile.testing.testng.DisableDiscovery; import io.helidon.microprofile.testing.testng.HelidonTest; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.Initialized; import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.se.SeContainer; import jakarta.enterprise.inject.spi.CDI; import jakarta.enterprise.inject.spi.Extension; import jakarta.inject.Inject; @@ -43,12 +43,23 @@ @HelidonTest(resetPerTest = true) public class TestPerMethod { - private final static List ALL_CONTAINERS = new LinkedList<>(); + private final static List> ALL_CONTAINERS = new LinkedList<>(); + + @Test + void testWithParameter() { + ALL_CONTAINERS.add(CDI.current()); + } + + @Test + @DisableDiscovery + void testWithParameterNoDiscovery() { + ALL_CONTAINERS.add(CDI.current()); + } @Test @AddConfig(key = "key-1", value = "value-1") @AddBean(MyBean.class) - public void testWithAdditionalConfig() { + void testWithAdditionalConfig() { String configured = CDI.current() .select(MyBean.class) .get() @@ -59,15 +70,15 @@ public void testWithAdditionalConfig() { @Test @AddExtension(MyExtension.class) - public void testCustomExtension() { + void testCustomExtension() { assertThat("Extension should have been called, as it observes application scope", MyExtension.called, is(true)); } @AfterClass - static void validateContainerInstances() { + void validateContainerInstances() { Set used = new HashSet<>(); try { - for (SeContainer container : ALL_CONTAINERS) { + for (CDI container : ALL_CONTAINERS) { if (!used.add(System.identityHashCode(container))) { Assert.fail("Container instance used twice: " + container); } @@ -77,7 +88,7 @@ static void validateContainerInstances() { } } - public static class MyBean { + static class MyBean { private final String configured; @Inject diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPinnedThread.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPinnedThread.java index 8fdd6bda3bc..380c368f68a 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPinnedThread.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPinnedThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,33 +19,37 @@ import java.util.Arrays; import io.helidon.common.testing.virtualthreads.PinningAssertionError; -import io.helidon.microprofile.tests.testing.testng.programmatic.PinningExtraThreadTest; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; -import org.testng.Assert; +import org.testng.ITestResult; import org.testng.TestListenerAdapter; -import org.testng.TestNG; import org.testng.annotations.Test; -import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsCollectionContaining.hasItem; public class TestPinnedThread { - private static final String EXPECTED_PINNING_METHOD_NAME = "lambda$testPinningExtraThread$0"; + private static final String EXPECTED_PINNING_METHOD_NAME = "lambda$testPinned$0"; @Test void testListener() { - TestNG testng = new TestNG(); - testng.setTestClasses(new Class[] {PinningExtraThreadTest.class}); - TestListenerAdapter tla = new TestListenerAdapter(); - testng.addListener(tla); - PinningAssertionError pinningAssertionError = Assert.expectThrows(PinningAssertionError.class, testng::run); - assertThat(pinningAssertionError.getMessage(), startsWith("Pinned virtual threads were detected:")); - assertThat("Method with pinning is missing from stack strace.", Arrays.asList(pinningAssertionError.getStackTrace()), - hasItem(new StackTraceElementMatcher(EXPECTED_PINNING_METHOD_NAME))); + TestListenerAdapter tla = TestNGHelper.run("TestPinnedThread", TestPinnedThreadExtra.class); + Throwable ex = tla.getFailedTests().stream() + .findFirst() + .map(ITestResult::getThrowable) + .orElse(null); + assertThat(ex, is(not(nullValue()))); + assertThat(ex, is(instanceOf(PinningAssertionError.class))); + assertThat(ex.getMessage(), startsWith("Pinned virtual threads were detected:")); + assertThat("Method with pinning is missing from stack strace.", Arrays.asList(ex.getStackTrace()), + hasItem(new StackTraceElementMatcher(EXPECTED_PINNING_METHOD_NAME))); } private static class StackTraceElementMatcher extends BaseMatcher { @@ -70,7 +74,6 @@ public void describeMismatch(Object o, Description description) { @Override public void describeTo(Description description) { - } } } diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/programmatic/PinningExtraThreadTest.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPinnedThreadExtra.java similarity index 75% rename from microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/programmatic/PinningExtraThreadTest.java rename to microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPinnedThreadExtra.java index 924106a43ff..b013aea96da 100644 --- a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/programmatic/PinningExtraThreadTest.java +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestPinnedThreadExtra.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,21 @@ * limitations under the License. */ -package io.helidon.microprofile.tests.testing.testng.programmatic; +package io.helidon.microprofile.tests.testing.testng; import io.helidon.microprofile.testing.testng.HelidonTest; import org.testng.annotations.Test; /** - * Test executed programmatically from TestPinnedThread Test. + * Test executed programmatically from {@link TestPinnedThread}. */ +@EnabledIfParameter(key = "TestPinnedThread", value = "true") @HelidonTest(pinningDetection = true) -public class PinningExtraThreadTest { +public class TestPinnedThreadExtra { @Test - void testPinningExtraThread() throws InterruptedException { + void testPinned() throws InterruptedException { Thread.ofVirtual().start(() -> { synchronized (this) { try { diff --git a/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestWebTarget.java b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestWebTarget.java new file mode 100644 index 00000000000..f9172683320 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/java/io/helidon/microprofile/tests/testing/testng/TestWebTarget.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.tests.testing.testng; + +import io.helidon.microprofile.server.JaxRsCdiExtension; +import io.helidon.microprofile.server.ServerCdiExtension; +import io.helidon.microprofile.testing.testng.AddBean; +import io.helidon.microprofile.testing.testng.AddExtension; +import io.helidon.microprofile.testing.testng.DisableDiscovery; +import io.helidon.microprofile.testing.testng.HelidonTest; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.WebTarget; +import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; +import org.testng.annotations.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest(resetPerTest = true) +@DisableDiscovery +@AddExtension(ServerCdiExtension.class) +@AddExtension(JaxRsCdiExtension.class) +@AddExtension(CdiComponentProvider.class) +@AddBean(TestWebTarget.ResourceClass.class) +public class TestWebTarget { + + @Inject + private WebTarget target; + + @Test + void testFirst() { + assertThat(target, notNullValue()); + String response = target.path("/test") + .request() + .get(String.class); + assertThat(response, is("Hello from ResourceClass")); + } + + @Test + void testSecond() { + assertThat(target, notNullValue()); + String response = target.path("/test") + .request() + .get(String.class); + assertThat(response, is("Hello from ResourceClass")); + } + + @Path("/test") + public static class ResourceClass { + @GET + public String getIt() { + return "Hello from ResourceClass"; + } + } +} diff --git a/microprofile/tests/testing/testng/src/test/resources/META-INF/services/org.testng.ITestNGListener b/microprofile/tests/testing/testng/src/test/resources/META-INF/services/org.testng.ITestNGListener new file mode 100644 index 00000000000..936dc33f8a0 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/resources/META-INF/services/org.testng.ITestNGListener @@ -0,0 +1,2 @@ +io.helidon.microprofile.tests.testing.testng.ParallelizerListenerImpl +io.helidon.microprofile.tests.testing.testng.EnabledIfParameterListener diff --git a/microprofile/tests/testing/testng/src/test/resources/logging.properties b/microprofile/tests/testing/testng/src/test/resources/logging.properties new file mode 100644 index 00000000000..54c3f39b0e1 --- /dev/null +++ b/microprofile/tests/testing/testng/src/test/resources/logging.properties @@ -0,0 +1,21 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# + +handlers=io.helidon.logging.jul.HelidonConsoleHandler +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s !thread! %5$s%6$s%n + +.level=SEVERE +io.helidon.microprofile.testing.level=FINEST diff --git a/microprofile/tests/testing/testng/test-suite.xml b/microprofile/tests/testing/testng/test-suite.xml index a60e4f2eed7..02a2e6b2974 100644 --- a/microprofile/tests/testing/testng/test-suite.xml +++ b/microprofile/tests/testing/testng/test-suite.xml @@ -1,7 +1,7 @@ - - + - \ No newline at end of file + diff --git a/service/tests/inject/pom.xml b/service/tests/inject/pom.xml index fe35f268209..f781f58ca59 100644 --- a/service/tests/inject/pom.xml +++ b/service/tests/inject/pom.xml @@ -1,7 +1,7 @@ - - - 4.0.0 - - - io.helidon.applications - helidon-mp - 4.2.0-SNAPSHOT - ../../../../applications/mp/pom.xml - - - io.helidon.tests.integration.jpa - helidon-tests-integration-jpa-appl - Helidon Tests Integration JPA MP Application - - - io.helidon.microprofile.cdi.Main - org.h2.tools.Server - 0.34.1 - - - - - io.helidon.tests.integration.jpa - helidon-tests-integration-jpa-model - ${project.version} - - - io.helidon.microprofile.server - helidon-microprofile-server - - - io.helidon.config - helidon-config-yaml - - - io.helidon.microprofile.config - helidon-microprofile-config - - - io.helidon.integrations.cdi - helidon-integrations-cdi-hibernate - compile - - - io.helidon.integrations.cdi - helidon-integrations-cdi-jta-weld - compile - - - io.helidon.integrations.cdi - helidon-integrations-cdi-jta - compile - - - io.helidon.integrations.cdi - helidon-integrations-cdi-delegates - runtime - - - io.helidon.integrations.cdi - helidon-integrations-cdi-datasource-hikaricp - runtime - - - io.helidon.integrations.cdi - helidon-integrations-cdi-jpa - runtime - - - jakarta.persistence - jakarta.persistence-api - compile - - - jakarta.transaction - jakarta.transaction-api - compile - - - jakarta.json - jakarta.json-api - compile - - - io.smallrye - jandex - runtime - - - org.slf4j - slf4j-jdk14 - runtime - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - io.helidon.logging - helidon-logging-jul - test - - - - - - - ${basedir}/src/main/resources/META-INF - META-INF - - microprofile-config.properties - persistence.xml - - true - - - ${basedir}/src/main/resources - - META-INF/microprofile-config.properties - META-INF/persistence.xml - - - - - - - io.fabric8 - docker-maven-plugin - ${version.plugin.docker-maven-plugin} - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - io.smallrye - jandex-maven-plugin - - - org.hibernate.orm.tooling - hibernate-enhance-maven-plugin - - - - true - true - true - - - enhance - - - - - - org.apache.maven.plugins - maven-jar-plugin - ${version.plugin.jar} - - - - true - libs - ${mainClass} - false - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - org.apache.maven.plugins - maven-failsafe-plugin - - methods - 10 - - true - ${db.user} - ${db.password} - ${db.url} - - - - - test - integration-test - - integration-test - verify - - - - io.helidon.tests.integration.jpa.appl.test.*IT - - - - - - - - - - - - - !native-image - - - java-image - - - - org.codehaus.mojo - exec-maven-plugin - - - app - pre-integration-test - - exec - - - java - - -classpath - - ${mainClass} - - true - - - - - - - - - - - native-image - - - native-image - - - - org.codehaus.mojo - exec-maven-plugin - - - app - pre-integration-test - - exec - - - ${project.build.directory}/${project.artifactId} - true - - - - - - - - - docker - - - - io.fabric8 - docker-maven-plugin - - - - start - package - - start - - - - stop - post-integration-test - - stop - - - - - - - - - - - h2 - - - !db - - - - 9092 - localhost - test - sa - - jdbc:h2:mem:test;INIT=SET TRACE_LEVEL_FILE=4 - org.hibernate.dialect.H2Dialect - org.h2.jdbcx.JdbcDataSource - - - - io.helidon.integrations.db - h2 - - - com.h2database - h2 - runtime - - - - - mysql - - - db - mysql - - - - 3306 - 127.0.0.1 - pokemon - useSSL=false&allowPublicKeyRetrieval=true - user - password - root - jdbc:mysql://${db.host}:${db.port}/${db.database}?${db.args} - org.hibernate.dialect.MySQL8Dialect - com.mysql.cj.jdbc.MysqlDataSource - com.mysql.cj.jdbc.Driver - - - - io.helidon.integrations.db - helidon-integrations-db-mysql - ${project.version} - - - com.mysql - mysql-connector-j - runtime - - - - - - io.fabric8 - docker-maven-plugin - - - - mysql:8 - mysql - - - ${db.user} - ${db.password} - ${db.roootpw} - ${db.database} - - ${db.host} - - ${db.host}:${db.port}:3306 - - - - - MySQL init process done. Ready for start up. - - - - - true - false - - - - - - - pgsql - - - db - pgsql - - - - 5432 - 127.0.0.1 - pokemon - user - password - jdbc:postgresql://${db.host}:${db.port}/${db.database} - org.hibernate.dialect.PostgreSQLDialect - org.postgresql.ds.PGSimpleDataSource - org.postgresql.Driver - - - - io.helidon.integrations.db - helidon-integrations-db-pgsql - ${project.version} - - - org.postgresql - postgresql - runtime - - - - - - io.fabric8 - docker-maven-plugin - - - - postgres - postgres - - - ${db.user} - ${db.password} - ${db.database} - - ${db.host} - - ${db.host}:${db.port}:5432 - - - - 127.0.0.1 - - ${db.port} - - - - - - - - true - false - - - - - - - - diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DbUtils.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DbUtils.java deleted file mode 100644 index 440d44dfb80..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DbUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.util.List; - -import jakarta.persistence.EntityManager; - -import io.helidon.tests.integration.jpa.model.Pokemon; - -/** - * Database utilities. - */ -public class DbUtils { - - private DbUtils() { - throw new UnsupportedOperationException("Instances of DbUtils class are not allowed"); - } - - /** - * Flush Entity manager and clear caches. - * - * @param em Entity manager instance - */ - public static void cleanEm(final EntityManager em) { - em.flush(); - em.clear(); - em.getEntityManagerFactory().getCache().evictAll(); - } - - /** - * Find pokemon by name from pokemon List. - * - * @param pokemons List to search - * @param name name of pokemon - * @return found pokemon or null when no such pokemon exists - */ - public static Pokemon findPokemonByName(List pokemons, String name) { - if (pokemons != null && !pokemons.isEmpty()) { - for (Pokemon pokemon : pokemons) { - if (pokemon.getName().equals(name)) { - return pokemon; - } - } - } - return null; - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DeleteIT.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DeleteIT.java deleted file mode 100644 index bfcd0e58c03..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DeleteIT.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.util.List; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Root; - -import io.helidon.tests.integration.jpa.dao.Create; -import io.helidon.tests.integration.jpa.dao.Delete; -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Stadium; -import io.helidon.tests.integration.jpa.model.Trainer; - -/** - * Verify delete operations of ORM (server side). - */ -@ApplicationScoped -public class DeleteIT { - - @PersistenceContext(unitName = "test") - private EntityManager em; - - /** - * Initialize test suite. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult setup(TestResult result) { - Create.dbInsertMisty(em); - Create.dbInsertViridian(em); - return result; - } - - /** - * Clean up test suite. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult destroy(TestResult result) { - Delete.dbDeleteMisty(em); - Delete.dbDeleteViridian(em); - return result; - } - - /** - * Delete pokemon: release Misty's Staryu. - * Modification is done using entity instance. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testDeleteEntity(TestResult result) { - Pokemon staryu = em.createQuery( - "SELECT p FROM Pokemon p WHERE p.name = :name", Pokemon.class) - .setParameter("name", "Staryu") - .getSingleResult(); - int id = staryu.getId(); - em.remove(staryu); - DbUtils.cleanEm(em); - Pokemon dbStaryu = em.find(Pokemon.class, id); - result.assertNull(dbStaryu); - return result; - } - - /** - * Delete pokemon: release Misty's Psyduck. - * Modification is done using JPQL. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testDeleteJPQL(TestResult result) { - int deleted = em.createQuery( - "DELETE FROM Pokemon p WHERE p.name = :name") - .setParameter("name", "Psyduck") - .executeUpdate(); - result.assertEquals(1, deleted); - DbUtils.cleanEm(em); - List pokemons = em.createQuery( - "SELECT p FROM Pokemon p WHERE p.name=:name", Pokemon.class) - .setParameter("name", "Psyduck") - .getResultList(); - result.assertTrue(pokemons.isEmpty()); - return result; - } - - /** - * Delete pokemon: release Misty's Corsola. - * Modification is done using CriteriaDelete. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testDeleteCriteria(TestResult result) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaDelete cu = cb.createCriteriaDelete(Pokemon.class); - Root pokemonRoot = cu.from(Pokemon.class); - cu.where(cb.equal(pokemonRoot.get("name"), "Corsola")); - int deleted = em.createQuery(cu).executeUpdate(); - result.assertEquals(1, deleted); - DbUtils.cleanEm(em); - cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(Pokemon.class); - pokemonRoot = cq.from(Pokemon.class); - cq.select(pokemonRoot) - .where(cb.equal(pokemonRoot.get("name"), "Corsola")); - List pokemons = em.createQuery(cq).getResultList(); - result.assertTrue(pokemons.isEmpty()); - return result; - } - - /** - * Delete Viridian City. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testDeleteViridianCity(TestResult result) { - City city = em.createQuery( - "SELECT c FROM City c WHERE c.name = :name", City.class) - .setParameter("name", "Viridian City") - .getSingleResult(); - Stadium stadium = city.getStadium(); - Trainer trainer = stadium.getTrainer(); - List pokemons = trainer.getPokemons(); - em.remove(city); - em.remove(trainer); - pokemons.forEach(poklemon -> em.remove(poklemon)); - DbUtils.cleanEm(em); - List cities = em.createQuery( - "SELECT c FROM City c WHERE c.name = :name", City.class) - .setParameter("name", "Viridian City") - .getResultList(); - result.assertTrue(cities.isEmpty()); - return result; - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/Dispatcher.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/Dispatcher.java deleted file mode 100644 index 579c1ef829e..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/Dispatcher.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.lang.System.Logger.Level; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -/** - * Test name and class dispatcher. - */ -@ApplicationScoped -public class Dispatcher { - - private static final System.Logger LOGGER = System.getLogger(Dispatcher.class.getName()); - - /** - * Test invocation handler. - */ - static final class Handle { - - private final Object instance; - private final Method method; - private final TestResult result; - - /** - * Creates an instance of test invocation handler. - * - * @param instance test class instance - * @param method method handler to invoke - * @param result test execution result - */ - Handle(final Object instance, final Method method, final TestResult result) { - if (result == null) { - this.result = new TestResult(); - } else { - this.result = result; - } - this.instance = instance; - this.method = method; - } - - /** - * Invoke test. - * - * @return test execution result - */ - TestResult invoke() { - try { - if (method.getAnnotation(MPTest.class) != null) { - try { - return (TestResult) method.invoke(instance, result); - } catch (InvocationTargetException ie) { - Throwable cause = ie.getCause(); - return result.throwed(cause != null ? cause : ie); - } - } else { - return result.fail("Method is missing MPTest annotation"); - } - } catch (IllegalAccessException | IllegalArgumentException ex) { - result.throwed(ex); - } - return result; - } - - /** - * Get result of test execution. - * - * @return result of test execution - */ - TestResult result() { - return result; - } - - } - - @Inject - private InsertIT insertIt; - - @Inject - private UpdateIT updateIt; - - @Inject - private DeleteIT deleteIt; - - @Inject - private QueryIT queryIt; - - private static Handle createHandle(final Class testClass, final Object instance, final String methodName, final TestResult result) { - try { - return new Handle( - instance, - testClass.getDeclaredMethod(methodName, TestResult.class), - result); - } catch (NoSuchMethodException ex) { - result.throwed(ex); - } - return null; - } - - private Handle getHandle(final String name) { - final int nameLen = name.length(); - final int serpPos = name.indexOf('.'); - final TestResult result = new TestResult(); - result.name(name); - if (serpPos < 0 || (serpPos + 1) >= nameLen) { - result.fail("Invalid test identifier: " + name); - return null; - } - final String className = name.substring(0, serpPos); - final String methodName = name.substring(serpPos + 1, nameLen); - if (null == className) { - result.fail("Unknown test class: " + className); - } else switch (className) { - case "InsertIT": - return createHandle(InsertIT.class, insertIt, methodName, result); - case "UpdateIT": - return createHandle(UpdateIT.class, updateIt, methodName, result); - case "DeleteIT": - return createHandle(DeleteIT.class, deleteIt, methodName, result); - case "QueryIT": - return createHandle(QueryIT.class, queryIt, methodName, result); - default: - result.fail("Unknown test class: " + className); - } - return null; - } - - /** - * Run test identified by it's name ({@code.}). - * - * @param name name of the test - * @return test execution result - */ - public TestResult runTest(final String name) { - Handle handle = getHandle(name); - if (handle == null) { - try { - return handle.result().fail("Missing method handle."); - } catch (Exception ex) { - LOGGER.log(Level.WARNING, () -> String.format("Test %s execution throwed an exception: %s", - name, ex.getMessage())); - throw ex; - } - } - return handle.invoke(); - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/ExitThread.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/ExitThread.java deleted file mode 100644 index 2f662f42bfc..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/ExitThread.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.lang.System.Logger.Level; - -/** - * Exits JPA MP application after short delay. - */ -public class ExitThread implements Runnable { - - private static final System.Logger LOGGER = System.getLogger(ExitThread.class.getName()); - - /** - * Starts application exit thread. - */ - public static final void start() { - new Thread(new ExitThread()).start(); - } - - /** - * Wait few seconds and terminate Java VM. - */ - @Override - public void run() { - try { - Thread.sleep(3000); - } catch (InterruptedException ie) { - LOGGER.log(Level.WARNING, () -> String.format("Thread was interrupted: %s", ie.getMessage()), ie); - } finally { - System.exit(0); - } - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/InsertIT.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/InsertIT.java deleted file mode 100644 index c3c737bb968..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/InsertIT.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; - -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Stadium; -import io.helidon.tests.integration.jpa.model.Trainer; -import io.helidon.tests.integration.jpa.model.Type; - -/** - * Verify create/insert operations of ORM. - */ -@ApplicationScoped -public class InsertIT { - - private static final Set DELETE_POKEMONS = new HashSet<>(); - private static final Set DELETE_TRAINERS = new HashSet<>(); - private static final Set DELETE_STADIUMS = new HashSet<>(); - private static final Set DELETE_TOWNS = new HashSet<>(); - - @PersistenceContext(unitName = "test") - private EntityManager em; - - /** - * Clean up test suite. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult destroy(TestResult result) { - // testInsertType cleanup - em.createQuery("DELETE FROM Type t WHERE t.id = :id") - .setParameter("id", 20) - .executeUpdate(); - // Towns cleanup - DELETE_TOWNS.forEach((id) -> { - em.createQuery("DELETE FROM City c WHERE c.id = :id") - .setParameter("id", id) - .executeUpdate(); - }); - // Stadiums cleanup - DELETE_STADIUMS.forEach((id) -> { - em.createQuery("DELETE FROM Stadium s WHERE s.id = :id") - .setParameter("id", id) - .executeUpdate(); - }); - // Pokemons cleanup - DELETE_POKEMONS.forEach((id) -> { - em.createQuery("DELETE FROM Pokemon p WHERE p.id = :id") - .setParameter("id", id) - .executeUpdate(); - }); - // Trainers cleanup - DELETE_TRAINERS.forEach((id) -> { - em.createQuery("DELETE FROM Trainer t WHERE t.id = :id") - .setParameter("id", id) - .executeUpdate(); - }); - return result; - } - - /** - * Verify simple create operation (persist) on a single database row. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testInsertType(TestResult result) { - Type type = new Type(20, "TestType"); - em.persist(type); - em.flush(); - Type dbType = em.find(Type.class, 20); - result.assertEquals(type, dbType); - return result; - } - - /** - * Verify complex create operation (persist) on a full ORM model (Gary Oak and his 6 pokemons). - * Relations are not marked for cascade persist operation so every entity instance has to be persisted separately. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testInsertTrainerWithPokemons(TestResult result) { - final Pokemon[] pokemons = new Pokemon[6]; - Type normal = em.find(Type.class, 1); - Type flying = em.find(Type.class, 3); - Type poison = em.find(Type.class, 4); - Type fire = em.find(Type.class, 10); - Type water = em.find(Type.class, 11); - Type electric = em.find(Type.class, 13); - final Trainer trainer = new Trainer("Gary Oak", 10); - pokemons[0] = new Pokemon(trainer, "Krabby", 236, Arrays.asList(water)); - pokemons[1] = new Pokemon(trainer, "Nidoran", 251, Arrays.asList(poison)); - pokemons[2] = new Pokemon(trainer, "Eevee", 115, Arrays.asList(normal)); - pokemons[3] = new Pokemon(trainer, "Electivire", 648, Arrays.asList(electric)); - pokemons[4] = new Pokemon(trainer, "Dodrio", 346, Arrays.asList(normal, flying)); - pokemons[5] = new Pokemon(trainer, "Magmar", 648, Arrays.asList(fire)); - em.persist(trainer); - for (Pokemon pokemon : pokemons) { - em.persist(pokemon); - } - DbUtils.cleanEm(em); - Pokemon dbKrabby = em.find(Pokemon.class, pokemons[0].getId()); - Pokemon dbNidoran = em.find(Pokemon.class, pokemons[1].getId()); - Pokemon dbEvee = em.find(Pokemon.class, pokemons[2].getId()); - Pokemon dbElectivire = em.find(Pokemon.class, pokemons[3].getId()); - Pokemon dbDodrio = em.find(Pokemon.class, pokemons[4].getId()); - Pokemon dbMagmar = em.find(Pokemon.class, pokemons[5].getId()); - Trainer dbTrainer = dbKrabby.getTrainer(); - result.assertEquals(pokemons[0], dbKrabby); - result.assertEquals(pokemons[1], dbNidoran); - result.assertEquals(pokemons[2], dbEvee); - result.assertEquals(pokemons[3], dbElectivire); - result.assertEquals(pokemons[4], dbDodrio); - result.assertEquals(pokemons[5], dbMagmar); - result.assertEquals(trainer, dbTrainer); - for (Pokemon pokemon : pokemons) { - DELETE_POKEMONS.add(pokemon.getId()); - } - DELETE_TRAINERS.add(dbTrainer.getId()); - return result; - } - - /** - * Verify complex create operation (persist) on a full ORM model (Lt. Surge in Vermilion City). - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testInsertTownWithStadium(TestResult result) { - final Trainer[] trainers = new Trainer[1]; - final Pokemon[] pokemons = new Pokemon[6]; - final Stadium[] stadiums = new Stadium[1]; - final City[] cities = new City[1]; - Type steel = em.find(Type.class, 9); - Type electric = em.find(Type.class, 13); - trainers[0] = new Trainer("Lt. Surge", 28); - pokemons[0] = new Pokemon(trainers[0], "Raichu", 1521, Arrays.asList(electric)); - pokemons[1] = new Pokemon(trainers[0], "Manectric", 1589, Arrays.asList(electric)); - pokemons[2] = new Pokemon(trainers[0], "Magnezone", 1853, Arrays.asList(electric)); - pokemons[3] = new Pokemon(trainers[0], "Electrode", 1237, Arrays.asList(electric)); - pokemons[4] = new Pokemon(trainers[0], "Pachirisu", 942, Arrays.asList(electric)); - pokemons[5] = new Pokemon(trainers[0], "Electivire", 1931, Arrays.asList(electric)); - stadiums[0] = new Stadium("Vermilion Gym", trainers[0]); - cities[0] = new City("Vermilion City", "Mina", stadiums[0]); - em.persist(trainers[0]); - em.persist(pokemons[0]); - em.persist(pokemons[1]); - em.persist(pokemons[2]); - em.persist(pokemons[3]); - em.persist(pokemons[4]); - em.persist(pokemons[5]); - //em.persist(stadiums[0]); - em.persist(cities[0]); - em.flush(); - DbUtils.cleanEm(em); - City dbCity = em.find(City.class, cities[0].getId()); - Stadium dbStadium = dbCity.getStadium(); - Trainer dbTrainer = dbStadium.getTrainer(); - List dbPokemons = dbTrainer.getPokemons(); - Set pokemonSet = new HashSet<>(pokemons.length); - pokemonSet.addAll(Arrays.asList(pokemons)); - dbPokemons.forEach((dbPokemon) -> { - result.assertTrue(pokemonSet.remove(dbPokemon)); - }); - result.assertTrue(pokemonSet.isEmpty()); - result.assertEquals(trainers[0], dbTrainer); - result.assertEquals(stadiums[0], dbStadium); - result.assertEquals(cities[0], dbCity); - for (Pokemon pokemon : pokemons) { - DELETE_POKEMONS.add(pokemon.getId()); - } - DELETE_TRAINERS.add(dbTrainer.getId()); - DELETE_STADIUMS.add(dbStadium.getId()); - DELETE_TOWNS.add(dbCity.getId()); - return result; - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/JdbcApiIT.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/JdbcApiIT.java deleted file mode 100644 index 09f54b863fc..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/JdbcApiIT.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.System.Logger.Level; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.Properties; - -import jakarta.enterprise.context.ApplicationScoped; - -/** - * Test JDBC API without JPA. - */ -@ApplicationScoped -public class JdbcApiIT { - - private static final System.Logger LOGGER = System.getLogger(JdbcApiIT.class.getName()); - - /* Database connection. */ - private static Connection conn = null; - - /** - * Initialize database connection. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult setup(TestResult result) { - Properties config = new Properties(); - try (InputStream is = JdbcApiIT.class.getResourceAsStream("/META-INF/microprofile-config.properties")) { - config.load(is); - } catch (IOException ex) { - LOGGER.log(Level.ERROR, () -> String.format("Could not load configuration properties: %s", ex.getMessage())); - } - final String dbUser = config.getProperty("javax.sql.DataSource.test.dataSource.user"); - final String dbPassword = config.getProperty("javax.sql.DataSource.test.dataSource.password"); - final String dbUrl = config.getProperty("javax.sql.DataSource.test.dataSource.url"); - if (dbUser == null) { - throw new IllegalStateException("Database user name was not set, check javax.sql.DataSource.test.dataSource.user property! "); - } - if (dbPassword == null) { - throw new IllegalStateException("Database user password was not set, check javax.sql.DataSource.test.dataSource.password property!"); - } - if (dbUrl == null) { - throw new IllegalStateException("Database URL was not set, check javax.sql.DataSource.test.dataSource.url property!"); - } - try { - conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword); - } catch (SQLException ex) { - LOGGER.log(Level.INFO, () -> String.format("Could not open database connection: %s", ex.getMessage())); - conn = null; - } - return result; - } - - /** - * Close database connection. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult destroy(TestResult result) { - if (conn != null) { - Utils.closeConnection(conn); - } - return result; - } - - - /** - * Test simple ping query. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult ping(TestResult result) throws SQLException { - if (conn == null) { - return result.fail("No database connection is available!"); - } - try (Statement stmt = conn.createStatement()) { - try (ResultSet rs = stmt.executeQuery("SELECT 1")) { - result.assertTrue(rs.next(), "Is 1st result available?"); - Integer value = rs.getInt(1); - result.assertNotNull(value); - result.assertEquals(1, value); - } - } catch (SQLException e) { - LOGGER.log(Level.WARNING, () -> String.format("Simple ping query failed: ", e.getMessage())); - result.throwed(e); - } - return result; - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/JdbcTestResource.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/JdbcTestResource.java deleted file mode 100644 index a1ef4925878..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/JdbcTestResource.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import jakarta.enterprise.context.RequestScoped; -import jakarta.inject.Inject; -import jakarta.json.JsonObject; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; - -/** - * REST Resource for JDBC test application. - */ -@Path("/testJdbc") -@RequestScoped -public class JdbcTestResource { - - @Inject - private JdbcApiIT jdbcApiIt; - - private Dispatcher.Handle getHandle(final String name) { - final int nameLen = name.length(); - final int serpPos = name.indexOf('.'); - final TestResult result = new TestResult(); - result.name(name); - if (serpPos < 0 || (serpPos + 1) >= nameLen) { - result.fail("Invalid test identifier: " + name); - return null; - } - final String className = name.substring(0, serpPos); - final String methodName = name.substring(serpPos + 1, nameLen); - if ("JdbcApiIT".equals(className)) { - try { - return new Dispatcher.Handle( - jdbcApiIt, - JdbcApiIT.class.getDeclaredMethod(methodName, TestResult.class), - result); - } catch (NoSuchMethodException ex) { - result.throwed(ex); - } - } else { - result.fail("Unknown test class: " + className); - } - return null; - } - - /** - * Run test identified by it's name ({@code.}). - * - * @param name name of the test - * @return test execution result - */ - private TestResult runTest(final String name) { - Dispatcher.Handle handle = getHandle(name); - if (handle == null) { - return handle.result().fail("Missing method handle."); - } - return handle.invoke(); - } - - /** - * Test setup invocation. - * - * @return test result - */ - @GET - @Path("/setup") - @Produces(MediaType.APPLICATION_JSON) - public JsonObject setup() { - return runTest("JdbcApiIT.setup").build(); - } - - /** - * Test cleanup invocation. - * - * @return test result - */ - @GET - @Path("/destroy") - @Produces(MediaType.APPLICATION_JSON) - public JsonObject destroy() { - return runTest("JdbcApiIT.destroy").build(); - } - - - /** - * Test invocation. - * - * @param name test name - * @return test result - */ - @GET - @Path("/test/{name}") - @Produces(MediaType.APPLICATION_JSON) - public JsonObject test(@PathParam("name") String name) { - return runTest(name).build(); - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/JpaTestResource.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/JpaTestResource.java deleted file mode 100644 index 35588dea83e..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/JpaTestResource.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.lang.System.Logger.Level; - -import jakarta.enterprise.context.RequestScoped; -import jakarta.inject.Inject; -import jakarta.json.JsonObject; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.transaction.Transactional; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; - -import io.helidon.tests.integration.jpa.dao.Create; - -/** - * REST Resource for JPA test application. - */ -@Path("/test") -@RequestScoped -public class JpaTestResource { - - private static final System.Logger LOGGER = System.getLogger(JpaTestResource.class.getName()); - - @PersistenceContext(unitName = "test") - private EntityManager em; - - @Inject - Dispatcher dispatcher; - - /** - * Resource status check - * - * @return status message - */ - @GET - @Path("/status") - public String status() { - return "JPA test application is alive."; - } - - /** - * Test initialization. - * - * @return initialization result - */ - @GET - @Path("/init") - @Produces(MediaType.APPLICATION_JSON) - @Transactional - public JsonObject init() { - final TestResult result = new TestResult(); - result.name("Initialization"); - try { - Create.dbInsertTypes(em); - result.message("Database was initialized"); - } catch (Throwable t) { - LOGGER.log(Level.ERROR, () -> String.format("Pokemon types initialization failed: %s", t.getMessage()), t); - result.throwed(t); - } - return result.build(); - } - - /** - * Test whether MP application can access JPA Entity Beans. - * - * @return test result - */ - @GET - @Path("/beans") - @Produces(MediaType.APPLICATION_JSON) - public JsonObject beans() { - final TestResult result = new TestResult(); - result.name("Beans check"); - try { - Class typeClass = Class.forName("io.helidon.tests.integration.jpa.model.Type"); - Class pokemonClass = Class.forName("io.helidon.tests.integration.jpa.model.Pokemon"); - Class trainerClass = Class.forName("io.helidon.tests.integration.jpa.model.Trainer"); - result.assertNotNull(typeClass); - result.assertNotNull(pokemonClass); - result.assertNotNull(trainerClass); - } catch (Throwable t) { - LOGGER.log(Level.ERROR, () -> String.format("JPA Entity beans check failed: %s", t.getMessage()), t); - result.throwed(t); - } - return result.build(); - } - - /** - * Test invocation. - * - * @param name test name - * @return test result - */ - @GET - @Path("/test/{name}") - @Produces(MediaType.APPLICATION_JSON) - @Transactional - public JsonObject test(@PathParam("name") String name) { - TestResult result = dispatcher.runTest(name); - return result.build(); - } - - /** - * Terminate JPA MP application. - * - * @return shutdown message - */ - @GET - @Path("/exit") - public String exit() { - ExitThread.start(); - return "JPA MP application shutting down."; - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/QueryIT.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/QueryIT.java deleted file mode 100644 index f2ff1cd061c..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/QueryIT.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.util.List; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Root; - -import io.helidon.tests.integration.jpa.dao.Create; -import io.helidon.tests.integration.jpa.dao.Delete; -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Trainer; - -/** - * Verify query operations of ORM (server side). - */ -@ApplicationScoped -public class QueryIT { - - private static int ASH_ID; - - @PersistenceContext(unitName = "test") - private EntityManager em; - - /** - * Initialize test suite. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult setup(TestResult result) { - ASH_ID = Create.dbInsertAsh(em); - Create.dbInsertCeladon(em); - return result; - } - - /** - * Clean up test suite. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult destroy(TestResult result) { - Delete.dbDeleteAsh(em); - Delete.dbDeleteCeladon(em); - return result; - } - - /** - * Find trainer Ash and his pokemons. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testFind(TestResult result) { - Trainer ash = em.find(Trainer.class, ASH_ID); - List pokemons = ash.getPokemons(); - result.assertNotNull(ash); - result.assertFalse(pokemons.isEmpty()); - return result; - } - - /** - * Query trainer Ash and his pokemons using JPQL. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testQueryJPQL(TestResult result) { - Trainer ash = em.createQuery( - "SELECT t FROM Trainer t JOIN FETCH t.pokemons p WHERE t.id = :id", Trainer.class) - .setParameter("id", ASH_ID) - .getSingleResult(); - List pokemons = ash.getPokemons(); - result.assertNotNull(ash); - result.assertFalse(pokemons.isEmpty()); - return result; - } - - /** - * Query trainer Ash and his pokemons using CriteriaQuery. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testQueryCriteria(TestResult result) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(Trainer.class); - Root trainerRoot = cq.from(Trainer.class); - cq.select(trainerRoot) - .where(cb.equal(trainerRoot.get("id"), ASH_ID)); - Trainer ash = em.createQuery(cq).getSingleResult(); - List pokemons = ash.getPokemons(); - result.assertNotNull(ash); - result.assertFalse(pokemons.isEmpty()); - return result; - } - - /** - * Query Celadon city using JPQL. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testQueryCeladonJPQL(TestResult result) { - City city = em.createQuery( - "SELECT c FROM City c " - + "JOIN FETCH c.stadium s " - + "JOIN FETCH s.trainer t " - + "WHERE c.name = :name", City.class) - .setParameter("name", "Celadon City") - .getSingleResult(); - result.assertEquals(city.getName(), "Celadon City"); - result.assertEquals(city.getStadium().getName(), "Celadon Gym"); - result.assertEquals(city.getStadium().getTrainer().getName(), "Erika"); - return result; - } - - /** - * Query Celadon city using CriteriaQuery. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testQueryCeladonCriteria(TestResult result) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(City.class); - Root cityRoot = cq.from(City.class); - cityRoot - .fetch("stadium") - .fetch("trainer"); - cq.select(cityRoot) - .where(cb.equal(cityRoot.get("name"), "Celadon City")); - City city = em.createQuery(cq).getSingleResult(); - result.assertEquals(city.getName(), "Celadon City"); - result.assertEquals(city.getStadium().getName(), "Celadon Gym"); - result.assertEquals(city.getStadium().getTrainer().getName(), "Erika"); - return result; - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/TestResult.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/TestResult.java deleted file mode 100644 index 058db7f7554..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/TestResult.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import jakarta.json.Json; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; - -/** - * Test execution result. - */ -public class TestResult { - - private boolean failed; - - private final JsonObjectBuilder ob; - - private final JsonArrayBuilder msg; - - private JsonObjectBuilder ex; - - /** - * Creates an instance of test execution result. - */ - public TestResult() { - failed = false; - ob = Json.createObjectBuilder(); - msg = Json.createArrayBuilder(); - ex = null; - } - - /** - * Add test name. - * - * @param name name of the test - */ - public void name(final String name) { - ob.add("name", name); - } - - /** - * Get execution status (failed or not failed). - * - * @return execution status - */ - public boolean failed() { - return failed; - } - - /** - * Build test execution result JSON object. - * - * @return test execution result JSON object - */ - public JsonObject build() { - ob.add("status", failed ? "FAILURE" : "SUCCESS"); - ob.add("messages", msg.build()); - if (ex != null) { - ob.add("exception", ex.build()); - } - return ob.build(); - } - /** - * Add test execution activity message. - * - * @param message test execution activity message - * @return this instance - */ - public TestResult message(final String message) { - msg.add(message); - return this; - } - - /** - * Mark executed test as failed and store message related to failure. - * - * @param message message related to failure - * @return this instance - */ - public TestResult fail(final String message) { - // Log 1st failure as an error. - if (!failed) { - ob.add("error", message); - } - failed = true; - msg.add(message); - return this; - } - - /** - * Store an exception thrown during test execution. - * - * @param t exception thrown during test execution - * @return this instance - */ - public TestResult throwed(final Throwable t) { - // Log 1st failure as an error. - if (!failed) { - ob.add("error", t.getMessage()); - } - failed = true; - if (ex == null) { - JsonArrayBuilder trace = Json.createArrayBuilder(); - ex = Json.createObjectBuilder(); - ex.add("message", t.getMessage()); - StackTraceElement[] st = t.getStackTrace(); - for (StackTraceElement ste : st) { - trace.add(ste.toString()); - } - ex.add("trace", trace.build()); - } - return this; - } - - /** - * Test result check: equals - * - * @param expected expected value - * @param actual actual value to be checked - */ - public void assertEquals(Object expected, Object actual) { - boolean addError = false; - StringBuilder sb = new StringBuilder(); - sb.append("Expected: "); - sb.append(expected != null ? expected.toString() : ""); - sb.append(" Actual: "); - sb.append(actual != null ? actual.toString() : ""); - sb.append(" :: "); - if (expected == actual || (expected != null && expected.equals(actual))) { - sb.append("EQUAL"); - } else { - if (!failed) { - addError = true; - } - failed = true; - sb.append("NOT EQUAL"); - } - String message = sb.toString(); - msg.add(message); - if (addError) { - ob.add("error", message); - } - } - - /** - * Test result check: boolean true - * - * @param value actual value to be checked - */ - public void assertTrue(Boolean value) { - StringBuilder sb = new StringBuilder(); - sb.append("Expected: true"); - sb.append(" Actual: "); - sb.append(Boolean.toString(value)); - sb.append(" :: "); - sb.append(value ? "EQUAL" : "NOT EQUAL"); - String message = sb.toString(); - msg.add(message); - if (!value) { - ob.add("error", message); - failed = true; - } - } - - /** - * Test result check: boolean true - * - * @param value actual value to be checked - */ - public void assertTrue(Boolean value, String header) { - StringBuilder sb = new StringBuilder(); - sb.append(header); - sb.append(" Expected: true"); - sb.append(" Actual: "); - sb.append(Boolean.toString(value)); - sb.append(" :: "); - sb.append(value ? "EQUAL" : "NOT EQUAL"); - String message = sb.toString(); - msg.add(message); - if (!value) { - ob.add("error", message); - failed = true; - } - } - - /** - * Test result check: boolean false - * - * @param value actual value to be checked - */ - public void assertFalse(Boolean value) { - StringBuilder sb = new StringBuilder(); - sb.append("Expected: false"); - sb.append(" Actual: "); - sb.append(Boolean.toString(value)); - sb.append(" :: "); - sb.append(value ? "NOT EQUAL" : "EQUAL"); - String message = sb.toString(); - msg.add(message); - if (value) { - ob.add("error", message); - failed = true; - } - } - - /** - * Test result check: null value - * - * @param value actual value to be checked - */ - public void assertNull(Object value) { - StringBuilder sb = new StringBuilder(); - sb.append("Expected: null"); - sb.append(" Actual: "); - sb.append(value == null ? "null" : "not null"); - sb.append(" :: "); - sb.append(value == null ? "EQUAL" : "NOT EQUAL"); - String message = sb.toString(); - msg.add(message); - if (value != null ) { - ob.add("error", message); - failed = true; - } - } - - /** - * Test result check: non null value - * - * @param value actual value to be checked - */ - public void assertNotNull(Object value) { - StringBuilder sb = new StringBuilder(); - sb.append("Expected: not null"); - sb.append(" Actual: "); - sb.append(value == null ? "null" : "not null"); - sb.append(" :: "); - sb.append(value == null ? "NOT EQUAL" : "EQUAL"); - String message = sb.toString(); - msg.add(message); - if (value == null ) { - ob.add("error", message); - failed = true; - } - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/UpdateIT.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/UpdateIT.java deleted file mode 100644 index 96cec511ad8..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/UpdateIT.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2020, 2022 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.CriteriaUpdate; -import jakarta.persistence.criteria.Root; - -import io.helidon.tests.integration.jpa.dao.Create; -import io.helidon.tests.integration.jpa.dao.Delete; -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Stadium; -import io.helidon.tests.integration.jpa.model.Trainer; - -/** - * Verify update operations of ORM (server side). - */ -@ApplicationScoped -public class UpdateIT { - - @PersistenceContext(unitName = "test") - private EntityManager em; - - - /** - * Initialize test suite. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult setup(TestResult result) { - Create.dbInsertBrock(em); - Create.dbInsertSaffron(em); - return result; - } - - /** - * Clean up test suite. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult destroy(TestResult result) { - Delete.dbDeleteBrock(em); - Delete.dbDeleteSaffron(em); - return result; - } - - /** - * Update pokemon: evolve Broke's Geodude into Graveler. - * Modification is done using entity instance. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testUpdateEntity(TestResult result) { - Pokemon[] pokemons = new Pokemon[1]; - pokemons[0] = em.createQuery( - "SELECT p FROM Pokemon p WHERE p.name = :name", Pokemon.class) - .setParameter("name", "Geodude") - .getSingleResult(); - pokemons[0].getTypes().size(); - pokemons[0].setName("Graveler"); - pokemons[0].setCp(527); - em.persist(pokemons[0]); - DbUtils.cleanEm(em); - Pokemon dbGraveler = em.find(Pokemon.class, pokemons[0].getId()); - result.assertEquals(pokemons[0], dbGraveler); - return result; - } - - /** - * Update pokemon: evolve Broke's Slowpoke into Slowbro. - * Modification is done using JPQL. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testUpdateJPQL(TestResult result) { - int updated = em.createQuery( - "UPDATE Pokemon p SET p.name = :newName, p.cp = :newCp WHERE p.name = :name") - .setParameter("newName", "Slowbro") - .setParameter("newCp", 647) - .setParameter("name", "Slowpoke") - .executeUpdate(); - result.assertEquals(1, updated); - DbUtils.cleanEm(em); - Pokemon dbWartortle = em.createQuery( - "SELECT p FROM Pokemon p WHERE p.name=:name", Pokemon.class) - .setParameter("name", "Slowbro") - .getSingleResult(); - result.assertEquals(647, dbWartortle.getCp()); - return result; - } - - /** - * Update pokemon: evolve Broke's Teddiursa into Ursaring. - * Modification is done using CriteriaUpdate. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testUpdateCriteria(TestResult result) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaUpdate cu = cb.createCriteriaUpdate(Pokemon.class); - Root pokemonRoot = cu.from(Pokemon.class); - cu.where(cb.equal(pokemonRoot.get("name"), "Teddiursa")) - .set("name", "Ursaring") - .set("cp", 1568); - int updated = em.createQuery(cu).executeUpdate(); - result.assertEquals(1, updated); - DbUtils.cleanEm(em); - cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(Pokemon.class); - pokemonRoot = cq.from(Pokemon.class); - cq.select(pokemonRoot) - .where(cb.equal(pokemonRoot.get("name"), "Ursaring")); - Pokemon dbUrsaring = em.createQuery(cq).getSingleResult(); - result.assertEquals(1568, dbUrsaring.getCp()); - return result; - } - - /** - * Update Saffron City data structure. - * Replace stadium trainer with new guy who will get all pokemons from previous trainer. - * Also Alakazam evolves to Mega Alakazam at the same time. - * - * @param result test execution result - * @return test execution result - */ - @MPTest - public TestResult testUpdateSaffron(TestResult result) { - City[] cities = new City[1]; - Set pokemonNames = new HashSet<>(6); - cities[0] = em.createQuery( - "SELECT c FROM City c WHERE c.name = :name", City.class) - .setParameter("name", "Saffron City") - .getSingleResult(); - Stadium stadium = cities[0].getStadium(); - Trainer sabrina = stadium.getTrainer(); - Trainer janine = new Trainer("Janine", 24); - stadium.setTrainer(janine); - List pokemons = sabrina.getPokemons(); - janine.setPokemons(pokemons); - sabrina.setPokemons(Collections.EMPTY_LIST); - em.remove(sabrina); - em.persist(janine); - for (Pokemon pokemon : pokemons) { - pokemon.setTrainer(janine); - pokemonNames.add(pokemon.getName()); - em.persist(pokemon); - } - em.persist(stadium); - Pokemon alkazam = DbUtils.findPokemonByName(pokemons, "Alakazam"); - // Update pokemon by query - em.createQuery( - "UPDATE Pokemon p SET p.name = :newName, p.cp = :newCp WHERE p.id = :id") - .setParameter("newName", "Mega Alakazam") - .setParameter("newCp", 4348) - .setParameter("id", alkazam.getId()) - .executeUpdate(); - pokemonNames.remove("Alakazam"); - pokemonNames.add("Mega Alakazam"); - DbUtils.cleanEm(em); - City city = em.find(City.class, cities[0].getId()); - stadium = city.getStadium(); - Trainer trainer = stadium.getTrainer(); - pokemons = trainer.getPokemons(); - result.assertEquals(trainer.getName(), "Janine"); - for (Pokemon pokemon : pokemons) { - result.assertTrue(pokemonNames.remove(pokemon.getName()), "Pokemon " + pokemon.getName() + " is missing"); - } - return result; - } - -} diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/Utils.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/Utils.java deleted file mode 100644 index 68f2b93366c..00000000000 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/Utils.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl; - -import java.lang.System.Logger.Level; -import java.sql.Connection; -import java.sql.SQLException; - -/** - * Test utilities. - */ -public class Utils { - - private static final System.Logger LOGGER = System.getLogger(Utils.class.getName()); - - private Utils() { - throw new IllegalStateException("No instances of this class are allowed!"); - } - - /** - * Close database connection. - * - * @param connection database connection - */ - public static void closeConnection(final Connection connection) { - try { - connection.close(); - } catch (SQLException ex) { - LOGGER.log(Level.WARNING, () -> String.format("Could not close database connection: %s", ex.getMessage())); - } - } - -} diff --git a/tests/integration/jpa/appl/src/main/resources/META-INF/beans.xml b/tests/integration/jpa/appl/src/main/resources/META-INF/beans.xml deleted file mode 100644 index e1f8b242429..00000000000 --- a/tests/integration/jpa/appl/src/main/resources/META-INF/beans.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - diff --git a/tests/integration/jpa/appl/src/main/resources/logging.properties b/tests/integration/jpa/appl/src/main/resources/logging.properties deleted file mode 100644 index 2d270fc6875..00000000000 --- a/tests/integration/jpa/appl/src/main/resources/logging.properties +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) 2020, 2023 Oracle and/or its affiliates. -# -# 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 -# -# http://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. -# - -# Example Logging Configuration File -# For more information see $JAVA_HOME/jre/lib/logging.properties -# Send messages to the console - -handlers=io.helidon.logging.jul.HelidonConsoleHandler -# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread -java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n -# Global logging level. Can be overridden by specific loggers -.level=WARNING -io.helidon.level=INFO -io.helidon.config.level=INFO -io.helidon.webserver.level=WARNING diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/ClientUtils.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/ClientUtils.java deleted file mode 100644 index d1b34f728c7..00000000000 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/ClientUtils.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl.test; - -import java.lang.System.Logger.Level; - -import jakarta.json.stream.JsonParsingException; -import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.WebTarget; -import jakarta.ws.rs.core.Response; - -/** - * REST client utilities for remote test calls. - */ -public class ClientUtils { - - private static final System.Logger LOGGER = System.getLogger(ClientUtils.class.getName()); - - private ClientUtils() { - throw new UnsupportedOperationException("Instances of ClientUtils class are not allowed"); - } - - private static final Client CLIENT = ClientBuilder.newClient(); - // FIXME: Use random port. - private static final WebTarget TARGET = CLIENT.target("http://localhost:7001/test"); - private static final WebTarget TARGET_JDBC = CLIENT.target("http://localhost:7001/testJdbc"); - - /** - * Call remote test on MP server using REST interface. - * - * @param path test path (URL suffix {@code .}) - */ - public static void callTest(final String path) { - WebTarget status = TARGET.path(path); - Response response = status.request().get(); - String responseStr = response.readEntity(String.class); - try { - Validate.check(responseStr); - } catch (JsonParsingException t) { - LOGGER.log(Level.ERROR, () -> String.format("Response is not JSON: %s, message: %s", t.getMessage(), responseStr), t); - } - } - - /** - * Call remote test on MP server using REST interface. - * - * @param path test path (URL suffix {@code .}) - */ - public static void callJdbcTest(final String path) { - WebTarget status = TARGET_JDBC.path(path); - Response response = status.request().get(); - String responseStr = response.readEntity(String.class); - try { - Validate.check(responseStr); - } catch (JsonParsingException t) { - LOGGER.log(Level.ERROR, () -> String.format("Response is not JSON: %s, message: %s", t.getMessage(), responseStr), t); - } - } - -} diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/DeleteIT.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/DeleteIT.java deleted file mode 100644 index 435610423f9..00000000000 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/DeleteIT.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl.test; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * Verify delete operations of ORM (client side). - */ -public class DeleteIT { - - @BeforeAll - public static void setup() { - ClientUtils.callTest("/test/DeleteIT.setup"); - } - - @AfterAll - public static void destroy() { - ClientUtils.callTest("/test/DeleteIT.destroy"); - } - - /** - * Delete pokemon: release Misty's Staryu. - * Modification is done using entity instance. - */ - @Test - public void testDeleteEntity() { - ClientUtils.callTest("/test/DeleteIT.testDeleteEntity"); - } - - /** - * Delete pokemon: release Misty's Psyduck. - * Modification is done using JPQL. - */ - @Test - public void testDeleteJPQL() { - ClientUtils.callTest("/test/DeleteIT.testDeleteJPQL"); - - } - - /** - * Delete pokemon: release Misty's Corsola. - * Modification is done using CriteriaUpdate. - */ - @Test - public void testDeleteCriteria() { - ClientUtils.callTest("/test/DeleteIT.testDeleteCriteria"); - } - - /** - * Delete Viridian City. - */ - @Test - public void testDeleteViridianCity() { - ClientUtils.callTest("/test/DeleteIT.testDeleteViridianCity"); - } - -} diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/InsertIT.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/InsertIT.java deleted file mode 100644 index 723cc796e8f..00000000000 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/InsertIT.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl.test; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -/** - * Verify update operations of ORM (clienbt side). - */ -public class InsertIT { - - @AfterAll - public static void destroy() { - ClientUtils.callTest("/test/InsertIT.destroy"); - } - - /** - * Verify simple create operation (persist) on a single database row. - */ - @Test - public void testInsertType() { - ClientUtils.callTest("/test/InsertIT.testInsertType"); - } - - /** - * Verify complex create operation (persist) on a full ORM model (Gary Oak and his 6 pokemons). - * Relations are not marked for cascade persist operation so every entity instance has to be persisted separately. - */ - @Test - public void testInsertTrainerWithPokemons() { - ClientUtils.callTest("/test/InsertIT.testInsertTrainerWithPokemons"); - } - - /** - * Verify complex create operation (persist) on a full ORM model (Lt. Surge in Vermilion City). - */ - @Test - public void testInsertTownWithStadium() { - ClientUtils.callTest("/test/InsertIT.testInsertTownWithStadium"); - } - -} diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/LifeCycleExtension.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/LifeCycleExtension.java deleted file mode 100644 index ad37279eab2..00000000000 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/LifeCycleExtension.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl.test; - -import java.lang.System.Logger.Level; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - -import io.helidon.logging.common.LogConfig; -import io.helidon.tests.integration.jpa.appl.Utils; - -import jakarta.json.stream.JsonParsingException; -import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.WebTarget; -import jakarta.ws.rs.core.Response; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; - -/** - * Test Life Cycle. - * Contains setup and cleanup methods for the tests. - */ -public class LifeCycleExtension implements BeforeAllCallback, ExtensionContext.Store.CloseableResource { - - private static final System.Logger LOGGER = System.getLogger(LifeCycleExtension.class.getName()); - - private static final String STORE_KEY = LifeCycleExtension.class.getName(); - - /* Startup timeout in seconds for both database and web server. */ - private static final int TIMEOUT = 60; - - /* Thread sleep time in miliseconds while waiting for database or appserver to come up. */ - private static final int SLEEP_MILIS = 250; - - private static final Client CLIENT = ClientBuilder.newClient(); - - private static final WebTarget TARGET = CLIENT.target("http://localhost:7001/test"); - - /** - * Test setup. - * - * @param ec current extension context - * @throws Exception when test setup fails - */ - @Override - public void beforeAll(ExtensionContext ec) throws Exception { - final Object resource = ec.getRoot().getStore(GLOBAL).get(STORE_KEY); - if (resource == null) { - LogConfig.configureRuntime(); - LOGGER.log(Level.TRACE, "Running beforeAll lifecycle method for the first time, invoking setup()"); - ec.getRoot().getStore(GLOBAL).put(STORE_KEY, this); - setup(); - } else { - LOGGER.log(Level.TRACE, "Running beforeAll lifecycle method next time, skipping setup()"); - } - } - - /** - * Setup JPA application tests. - */ - private void setup() { - LOGGER.log(Level.DEBUG, "Running JPA application test setup()"); - waitForDatabase(); - waitForServer(); - ClientUtils.callJdbcTest("/setup"); - ClientUtils.callJdbcTest("/test/JdbcApiIT.ping"); - init(); - testBeans(); - } - - /** - * Cleanup JPA application tests. - */ - @Override - public void close() throws Throwable { - LOGGER.log(Level.DEBUG, "Running JPA application test close()"); - shutdown(); - } - - @SuppressWarnings("SleepWhileInLoop") - public static void waitForDatabase() { - final String dbUser = System.getProperty("db.user"); - final String dbPassword = System.getProperty("db.password"); - final String dbUrl = System.getProperty("db.url"); - boolean connected = false; - if (dbUser == null) { - throw new IllegalStateException("Database user name was not set, check db.user property!"); - } - if (dbPassword == null) { - throw new IllegalStateException("Database user password was not set, check db.password property!"); - } - if (dbUrl == null) { - throw new IllegalStateException("Database URL was not set, check db.url property"); - } - long endTm = 1000 * TIMEOUT + System.currentTimeMillis(); - while (true) { - try { - Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword); - connected = true; - Utils.closeConnection(conn); - return; - } catch (SQLException ex) { - LOGGER.log(Level.DEBUG, () -> String.format("Connection check: %s", ex.getMessage())); - if (System.currentTimeMillis() > endTm) { - throw new IllegalStateException(String.format("Database is not ready within %d seconds", TIMEOUT)); - } - try { - Thread.sleep(SLEEP_MILIS); - } catch (InterruptedException ie) { - LOGGER.log(Level.WARNING, () -> String.format("Thread was interrupted: %s", ie.getMessage()), ie); - } - } - } - } - - @SuppressWarnings("SleepWhileInLoop") - public static void waitForServer() { - WebTarget status = TARGET.path("/status"); - - long tmEnd = System.currentTimeMillis() + (TIMEOUT * 1000); - boolean retry = true; - while (retry) { - try { - Response response = status.request().get(); - retry = false; - } catch (Exception ex) { - LOGGER.log(Level.DEBUG, () -> String.format("Connection check: %s", ex.getMessage())); - if (System.currentTimeMillis() > tmEnd) { - throw new IllegalStateException(String.format("Appserver is not ready within %d seconds", TIMEOUT)); - } - try { - Thread.sleep(SLEEP_MILIS); - } catch (InterruptedException ie) { - LOGGER.log(Level.WARNING, () -> String.format("Thread was interrupted: %s", ie.getMessage()), ie); - } - } - } - } - - public void init() { - WebTarget status = TARGET.path("/init"); - Response response = status.request().get(); - String responseStr = response.readEntity(String.class); - try { - Validate.check(responseStr); - } catch (JsonParsingException t) { - LOGGER.log(Level.ERROR, () -> String.format("Response is not JSON: %s, message: %s", t.getMessage(), responseStr), t); - } - } - - public void testBeans() { - WebTarget status = TARGET.path("/beans"); - Response response = status.request().get(); - String responseStr = response.readEntity(String.class); - try { - Validate.check(responseStr); - } catch (JsonParsingException t) { - LOGGER.log(Level.ERROR, () -> String.format("Response is not JSON: %s, message: %s", t.getMessage(), responseStr), t); - } - } - - public void shutdown() { - WebTarget exit = TARGET.path("/exit"); - Response response = exit.request().get(); - LOGGER.log(Level.INFO, () -> String.format("Status: %s", response.readEntity(String.class))); - } - -} diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/QueryIT.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/QueryIT.java deleted file mode 100644 index 8fd849b466a..00000000000 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/QueryIT.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl.test; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * Verify query operations of ORM (client side). - */ -public class QueryIT { - - @BeforeAll - public static void setup() { - ClientUtils.callTest("/test/QueryIT.setup"); - } - - @AfterAll - public static void destroy() { - ClientUtils.callTest("/test/QueryIT.destroy"); - } - - /** - * Find trainer Ash and his pokemons. - */ - @Test - public void testDeleteEntity() { - ClientUtils.callTest("/test/QueryIT.testFind"); - } - - /** - * Query trainer Ash and his pokemons using JPQL. - */ - @Test - public void testDeleteJPQL() { - ClientUtils.callTest("/test/QueryIT.testQueryJPQL"); - - } - - /** - * Query trainer Ash and his pokemons using CriteriaQuery. - */ - @Test - public void testDeleteCriteria() { - ClientUtils.callTest("/test/QueryIT.testQueryCriteria"); - } - - /** - * Query Celadon city using JPQL. - */ - @Test - public void testQueryCeladonJPQL() { - ClientUtils.callTest("/test/QueryIT.testQueryCeladonJPQL"); - } - - /** - * Query Celadon city using CriteriaQuery. - */ - @Test - public void testQueryCeladonCriteria() { - ClientUtils.callTest("/test/QueryIT.testQueryCeladonCriteria"); - } - -} diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/UpdateIT.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/UpdateIT.java deleted file mode 100644 index 6389cc63214..00000000000 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/UpdateIT.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl.test; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * Verify update operations of ORM (client side). - */ -public class UpdateIT { - - @BeforeAll - public static void setup() { - ClientUtils.callTest("/test/UpdateIT.setup"); - } - - @AfterAll - public static void destroy() { - ClientUtils.callTest("/test/UpdateIT.destroy"); - } - - /** - * Update pokemon: evolve Broke's Geodude into Graveler. - * Modification is done using entity instance. - */ - @Test - public void testUpdateEntity() { - ClientUtils.callTest("/test/UpdateIT.testUpdateEntity"); - } - - /** - * Update pokemon: evolve Broke's Slowpoke into Slowbro. - * Modification is done using JPQL. - */ - @Test - public void testUpdateJPQL() { - ClientUtils.callTest("/test/UpdateIT.testUpdateJPQL"); - } - - /** - * Update pokemon: evolve Broke's Teddiursa into Ursaring. - * Modification is done using CriteriaUpdate. - */ - @Test - public void testUpdateCriteria() { - ClientUtils.callTest("/test/UpdateIT.testUpdateCriteria"); - } - - /** - * Update Saffron City data structure. - * Replace stadium trainer with new guy who will get all pokemons from previous trainer. - * Also Alakazam evolves to Mega Alakazam at the same time. - */ - @Test - public void testUpdateSaffron() { - ClientUtils.callTest("/test/UpdateIT.testUpdateSaffron"); - } - -} diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/Validate.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/Validate.java deleted file mode 100644 index 0e7e102a206..00000000000 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/Validate.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.appl.test; - -import java.io.StringReader; -import java.lang.System.Logger.Level; - -import jakarta.json.Json; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; - -import static org.junit.jupiter.api.Assertions.fail; - -/** - * Validate test response. - * Utility class. - */ -public class Validate { - - private static final System.Logger LOGGER = System.getLogger(Validate.class.getName()); - - private Validate() { - throw new UnsupportedOperationException("Instances of Validate class are not allowed"); - } - - /** - * Check test result String. - * - * @param resultString test result string with JSON data - */ - public static void check(String resultString) { - JsonObject testResult = Json.createReader(new StringReader(resultString)).readObject(); - String name = testResult.getString("name"); - String status = testResult.getString("status"); - printTestHeader(name, status); - if (testResult.containsKey("messages")) { - printMessages(testResult.getJsonArray("messages")); - } - if (testResult.containsKey("exception")) { - printException(testResult.getJsonObject("exception")); - } - if (!"SUCCESS".equals(status.toUpperCase())) { - StringBuilder sb = new StringBuilder(); - sb.append("Test "); - sb.append(name != null ? name : "UNKNOWN TEST"); - sb.append(" failed"); - if (testResult.containsKey("error")) { - sb.append(": "); - sb.append(testResult.getString("error")); - } - fail(sb.toString()); - } - } - - private static String dots(int count) { - StringBuilder sb = new StringBuilder(count); - for (int i = 0; i < count; i++) { - sb.append('.'); - } - return sb.toString(); - } - - private static void printTestHeader(final String name, final String status) { - final String printName = name != null ? name : "UNKNOWN TEST"; - final String printStatus = status != null ? status : "N?A"; - final int dotsCount = 60 - printName.length(); - LOGGER.log(Level.DEBUG, () -> String.format("*** %s %s", printName, dots(dotsCount))); - LOGGER.log(Level.DEBUG, printStatus); - } - - private static void printMessages(final JsonArray messages) { - if (messages != null && messages.size() > 0) { - LOGGER.log(Level.DEBUG, "Messages:"); - for (int i = 0; i < messages.size(); i++) { - final int idx = i; - LOGGER.log(Level.DEBUG, () -> String.format(" - %s", messages.getString(idx))); - } - } - } - - private static void printException(final JsonObject exception) { - final String message = exception.getString("message"); - LOGGER.log(Level.DEBUG, () -> String.format("Exception: %s", message != null ? message : "")); - if (exception.containsKey("trace")) { - final JsonArray trace = exception.getJsonArray("trace"); - for (int i = 0; i < trace.size(); i++) { - final int idx = i; - LOGGER.log(Level.DEBUG, () -> String.format(" - %s", trace.getString(idx))); - } - } - } - -} diff --git a/tests/integration/jpa/appl/src/test/resources/logging.properties b/tests/integration/jpa/appl/src/test/resources/logging.properties deleted file mode 100644 index 4e59ca90ef4..00000000000 --- a/tests/integration/jpa/appl/src/test/resources/logging.properties +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (c) 2018, 2023 Oracle and/or its affiliates. -# -# 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 -# -# http://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. -# - -# Example Logging Configuration File -# For more information see $JAVA_HOME/jre/lib/logging.properties - -# Send messages to the console -handlers=io.helidon.logging.jul.HelidonConsoleHandler - -# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread -java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n - -# Global logging level. Can be overridden by specific loggers -.level=WARNING - -io.helidon.level=INFO -io.helidon.config.level=INFO -io.helidon.webserver.level=WARNING -io.helidon.tests.integration.jpa.appl.test.level=INFO diff --git a/tests/integration/jpa/model/pom.xml b/tests/integration/jpa/common/pom.xml similarity index 60% rename from tests/integration/jpa/model/pom.xml rename to tests/integration/jpa/common/pom.xml index 39c7204c7c0..78e46c83dd0 100644 --- a/tests/integration/jpa/model/pom.xml +++ b/tests/integration/jpa/common/pom.xml @@ -1,6 +1,6 @@ + + + 4.0.0 + + io.helidon.tests.integration.jpa + helidon-tests-integration-jpa-parent + 4.2.0-SNAPSHOT + ../parent/pom.xml + + helidon-tests-integration-jpa-h2 + Helidon Tests Integration JPA H2 + + + + io.helidon.integrations.db + h2 + + + com.h2database + h2 + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + diff --git a/tests/integration/jpa/simple/src/test/resources/META-INF/microprofile-config.properties b/tests/integration/jpa/h2/src/main/resources/META-INF/microprofile-config.properties similarity index 80% rename from tests/integration/jpa/simple/src/test/resources/META-INF/microprofile-config.properties rename to tests/integration/jpa/h2/src/main/resources/META-INF/microprofile-config.properties index d7e8bc6db9e..9570fe7a0d2 100644 --- a/tests/integration/jpa/simple/src/test/resources/META-INF/microprofile-config.properties +++ b/tests/integration/jpa/h2/src/main/resources/META-INF/microprofile-config.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,10 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -mp.initializer.allow=true -mp.initializer.no-warn=true +server.port=0 +features.print-details=true javax.sql.DataSource.test.dataSource.url=jdbc:h2:mem:test;INIT=SET TRACE_LEVEL_FILE=4 javax.sql.DataSource.test.dataSource.user=sa -javax.sql.DataSource.test.dataSource.password=${EMPTY} -javax.sql.DataSource.test.dataSourceClassName=org.h2.jdbcx.JdbcDataSource \ No newline at end of file +javax.sql.DataSource.test.dataSource.password= +javax.sql.DataSource.test.dataSourceClassName=org.h2.jdbcx.JdbcDataSource diff --git a/tests/integration/jpa/simple/src/test/resources/hibernate.properties b/tests/integration/jpa/h2/src/main/resources/logging.properties similarity index 71% rename from tests/integration/jpa/simple/src/test/resources/hibernate.properties rename to tests/integration/jpa/h2/src/main/resources/logging.properties index e27b413cc73..451cb7946ca 100644 --- a/tests/integration/jpa/simple/src/test/resources/hibernate.properties +++ b/tests/integration/jpa/h2/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,5 @@ # limitations under the License. # -# Byte code for JPA must be generated at compile time. -# This is a limitation of native image -# No longer supported with Hibernate 6.3.x -# hibernate.bytecode.provider=none +handlers=org.slf4j.bridge.SLF4JBridgeHandler +.level=INFO diff --git a/tests/integration/jpa/h2/src/main/resources/simplelogger.properties b/tests/integration/jpa/h2/src/main/resources/simplelogger.properties new file mode 100644 index 00000000000..5e27d815414 --- /dev/null +++ b/tests/integration/jpa/h2/src/main/resources/simplelogger.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.log.org.testcontainers=info +org.slf4j.simpleLogger.log.org.testcontainers.utility=error +org.slf4j.simpleLogger.log.io.helidon=info +org.slf4j.simpleLogger.log.org.hibernate=info +org.slf4j.simpleLogger.log.com.zaxxer=info diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2DeleteLocalTestIT.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2DeleteLocalTestIT.java new file mode 100644 index 00000000000..5264a44d621 --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2DeleteLocalTestIT.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.DeleteTest; +import io.helidon.tests.integration.jpa.common.DeleteTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local delete test. + */ +@HelidonTest +class H2DeleteLocalTestIT extends H2LocalTest implements DeleteTest { + + @Inject + private DeleteTestImpl delegate; + + @Test + @Override + public void testDeleteEntity() { + delegate.testDeleteEntity(); + } + + @Test + @Override + public void testDeleteJPQL() { + delegate.testDeleteJPQL(); + } + + @Test + @Override + public void testDeleteCriteria() { + delegate.testDeleteCriteria(); + } + + @Test + @Override + public void testDeleteCity() { + delegate.testDeleteCity(); + } +} diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2DeleteRemoteTestIT.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2DeleteRemoteTestIT.java new file mode 100644 index 00000000000..ebe5689a6f9 --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2DeleteRemoteTestIT.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +import io.helidon.tests.integration.jpa.common.DeleteTest; + +import org.junit.jupiter.api.Test; + +/** + * Remote delete test. + */ +class H2DeleteRemoteTestIT extends H2RemoteTest implements DeleteTest { + + H2DeleteRemoteTestIT() { + super("/test/delete"); + } + + @Test + @Override + public void testDeleteEntity() { + remoteTest(); + } + + @Test + @Override + public void testDeleteJPQL() { + remoteTest(); + } + + @Test + @Override + public void testDeleteCriteria() { + remoteTest(); + } + + @Test + @Override + public void testDeleteCity() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2InsertLocalTestIT.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2InsertLocalTestIT.java new file mode 100644 index 00000000000..527383ac0ea --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2InsertLocalTestIT.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.InsertTest; +import io.helidon.tests.integration.jpa.common.InsertTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local insert test. + */ +@HelidonTest +class H2InsertLocalTestIT extends H2LocalTest implements InsertTest { + + @Inject + private InsertTestImpl delegate; + + @Test + @Override + public void testInsertType() { + delegate.testInsertType(); + } + + @Test + @Override + public void testInsertTrainerWithPokemons() { + delegate.testInsertTrainerWithPokemons(); + } + + @Test + @Override + public void testTownWithStadium() { + delegate.testTownWithStadium(); + } +} diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2InsertRemoteTestIT.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2InsertRemoteTestIT.java new file mode 100644 index 00000000000..e97567f0670 --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2InsertRemoteTestIT.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +import io.helidon.tests.integration.jpa.common.InsertTest; + +import org.junit.jupiter.api.Test; + +/** + * Local insert test. + */ +class H2InsertRemoteTestIT extends H2RemoteTest implements InsertTest { + + H2InsertRemoteTestIT() { + super("/test/insert"); + } + + @Test + @Override + public void testInsertType() { + remoteTest(); + } + + @Test + @Override + public void testInsertTrainerWithPokemons() { + remoteTest(); + } + + @Test + @Override + public void testTownWithStadium() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2LocalTest.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2LocalTest.java new file mode 100644 index 00000000000..28afe89307b --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2LocalTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +/** + * Base class for the local tests. + */ +abstract class H2LocalTest { +} diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2QueryLocalTestIT.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2QueryLocalTestIT.java new file mode 100644 index 00000000000..dbedc2d9725 --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2QueryLocalTestIT.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.QueryTest; +import io.helidon.tests.integration.jpa.common.QueryTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local query test. + */ +@HelidonTest +class H2QueryLocalTestIT extends H2LocalTest implements QueryTest { + + @Inject + private QueryTestImpl delegate; + + @Test + @Override + public void testFind() { + delegate.testFind(); + } + + @Test + @Override + public void testQueryJPQL() { + delegate.testQueryJPQL(); + } + + @Test + @Override + public void testQueryCriteria() { + delegate.testQueryCriteria(); + } + + @Test + @Override + public void testQueryCeladonJPQL() { + delegate.testQueryCeladonJPQL(); + } + + @Test + @Override + public void testQueryCeladonCriteria() { + delegate.testQueryCeladonCriteria(); + } +} diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2QueryRemoteTestIT.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2QueryRemoteTestIT.java new file mode 100644 index 00000000000..3039e597daa --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2QueryRemoteTestIT.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +import io.helidon.tests.integration.jpa.common.QueryTest; + +import org.junit.jupiter.api.Test; + +/** + * Invoke {@code /test/query} endpoints. + */ +class H2QueryRemoteTestIT extends H2RemoteTest implements QueryTest { + + H2QueryRemoteTestIT() { + super("/test/query"); + } + + @Test + @Override + public void testFind() { + remoteTest(); + } + + @Test + @Override + public void testQueryJPQL() { + remoteTest(); + } + + @Test + @Override + public void testQueryCriteria() { + remoteTest(); + } + + @Test + @Override + public void testQueryCeladonJPQL() { + remoteTest(); + } + + @Test + @Override + public void testQueryCeladonCriteria() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2RemoteTest.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2RemoteTest.java new file mode 100644 index 00000000000..91424ac3f4c --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2RemoteTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +import java.nio.file.Path; +import java.util.Map; + +import io.helidon.tests.integration.harness.ProcessRunner; +import io.helidon.tests.integration.harness.ProcessRunner.ExecMode; +import io.helidon.tests.integration.harness.WaitStrategy; +import io.helidon.tests.integration.harness.TestProcess; +import io.helidon.tests.integration.harness.TestProcesses; +import io.helidon.tests.integration.jpa.common.RemoteTest; + +/** + * Base class for the remote tests. + */ +@TestProcesses +abstract class H2RemoteTest extends RemoteTest { + @TestProcess + static final ProcessRunner PROCESS_RUNNER = ProcessRunner.of(ExecMode.CLASS_PATH) + .finalName("helidon-tests-integration-jpa-h2") + .properties(Map.of("java.util.logging.config.file", Path.of("target/classes/logging.properties").toAbsolutePath())) + .waitingFor(WaitStrategy.waitForPort()); + + /** + * Create a new instance. + * + * @param path base path + */ + @SuppressWarnings("resource") + H2RemoteTest(String path) { + super(path, PROCESS_RUNNER.process().port()); + } +} diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2UpdateLocalTestIT.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2UpdateLocalTestIT.java new file mode 100644 index 00000000000..2d12e2260f9 --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2UpdateLocalTestIT.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.UpdateTest; +import io.helidon.tests.integration.jpa.common.UpdateTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local update test. + */ +@HelidonTest +class H2UpdateLocalTestIT extends H2LocalTest implements UpdateTest { + + @Inject + private UpdateTestImpl delegate; + + @Test + @Override + public void testUpdateEntity() { + delegate.testUpdateEntity(); + } + + @Test + @Override + public void testUpdateJPQL() { + delegate.testUpdateJPQL(); + } + + @Test + @Override + public void testUpdateCriteria() { + delegate.testUpdateCriteria(); + } + + @Test + @Override + public void testUpdateCity() { + delegate.testUpdateCity(); + } +} diff --git a/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2UpdateRemoteTestIT.java b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2UpdateRemoteTestIT.java new file mode 100644 index 00000000000..6ef6326ece6 --- /dev/null +++ b/tests/integration/jpa/h2/src/test/java/io/helidon/tests/integration/jpa/h2/H2UpdateRemoteTestIT.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.h2; + +import io.helidon.tests.integration.jpa.common.UpdateTest; + +import org.junit.jupiter.api.Test; + +/** + * Invoke {@code /test/update} endpoints. + */ +class H2UpdateRemoteTestIT extends H2RemoteTest implements UpdateTest { + + H2UpdateRemoteTestIT() { + super("/test/update"); + } + + @Test + @Override + public void testUpdateEntity() { + remoteTest(); + } + + @Test + @Override + public void testUpdateJPQL() { + remoteTest(); + } + + @Test + @Override + public void testUpdateCriteria() { + remoteTest(); + } + + @Test + @Override + public void testUpdateCity() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Create.java b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Create.java deleted file mode 100644 index 899dbae50cc..00000000000 --- a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Create.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.dao; - -import java.util.Arrays; -import java.util.List; - -import jakarta.persistence.EntityManager; - -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Stadium; -import io.helidon.tests.integration.jpa.model.Trainer; -import io.helidon.tests.integration.jpa.model.Type; - -/** - * Create data for the tests. - */ -public class Create { - - private Create() { - throw new UnsupportedOperationException("Instances of Create class are not allowed"); - } - - /** - * Insert pokemon types. - * - * @param em Entity manager instance - */ - public static void dbInsertTypes(final EntityManager em) { - em.persist(new Type(1, "Normal")); - em.persist(new Type(2, "Fighting")); - em.persist(new Type(3, "Flying")); - em.persist(new Type(4, "Poison")); - em.persist(new Type(5, "Ground")); - em.persist(new Type(6, "Rock")); - em.persist(new Type(7, "Bug")); - em.persist(new Type(8, "Ghost")); - em.persist(new Type(9, "Steel")); - em.persist(new Type(10, "Fire")); - em.persist(new Type(11, "Water")); - em.persist(new Type(12, "Grass")); - em.persist(new Type(13, "Electric")); - em.persist(new Type(14, "Psychic")); - em.persist(new Type(15, "Ice")); - em.persist(new Type(16, "Dragon")); - em.persist(new Type(17, "Dark")); - em.persist(new Type(18, "Fairy")); - em.flush(); - } - - /** - * Insert trainer Ash and his pokemons. - * Ash and his pokemons are used for query tests only. - * - * @param em Entity manager instance - * @return ID of the trainer (Ash) - */ - public static int dbInsertAsh(final EntityManager em) { - Trainer trainer = new Trainer("Ash Ketchum", 10); - Type normal = em.find(Type.class, 1); - Type flying = em.find(Type.class, 3); - Type poison = em.find(Type.class, 4); - Type bug = em.find(Type.class, 7); - Type fire = em.find(Type.class, 10); - Type water = em.find(Type.class, 11); - Type grass = em.find(Type.class, 12); - Type electric = em.find(Type.class, 13); - em.persist(trainer); - em.persist(new Pokemon(trainer, "Pikachu", 252, Arrays.asList(electric))); - em.persist(new Pokemon(trainer, "Caterpie", 123, Arrays.asList(bug))); - em.persist(new Pokemon(trainer, "Charmander", 207, Arrays.asList(fire))); - em.persist(new Pokemon(trainer, "Squirtle", 187, Arrays.asList(water))); - em.persist(new Pokemon(trainer, "Bulbasaur", 204, Arrays.asList(grass, poison))); - em.persist(new Pokemon(trainer, "Pidgey", 107, Arrays.asList(normal, flying))); - em.flush(); - return trainer.getId(); - } - - /** - * Create Brock and his pokemons. - * Brock and his pokemons are used for update tests only. - * - * @param em Entity manager instance - */ - public static void dbInsertBrock(final EntityManager em) { - Trainer trainer = new Trainer("Brock", 12); - Type normal = em.find(Type.class, 1); - Type ground = em.find(Type.class, 5); - Type rock = em.find(Type.class, 6); - Type water = em.find(Type.class, 11); - Type psychic = em.find(Type.class, 14); - List types = Arrays.asList(rock, ground); - em.persist(trainer); - em.persist(new Pokemon(trainer, "Geodude", 236, types)); - em.persist(new Pokemon(trainer, "Onix", 251, types)); - em.persist(new Pokemon(trainer, "Rhyhorn", 251, types)); - em.persist(new Pokemon(trainer, "Slowpoke", 251, Arrays.asList(water, psychic))); - em.persist(new Pokemon(trainer, "Teddiursa", 275, Arrays.asList(normal))); - em.persist(new Pokemon(trainer, "Omanyte", 275, Arrays.asList(rock, water))); - em.flush(); - em.clear(); - em.getEntityManagerFactory().getCache().evictAll(); - } - - /** - * Create Misty and her pokemons. - * Misty and her pokemons are used for delete tests only. - * - * @param em Entity manager instance - */ - public static void dbInsertMisty(final EntityManager em) { - Trainer trainer = new Trainer("Misty", 10); - Type normal = em.find(Type.class, 1); - Type rock = em.find(Type.class, 6); - Type water = em.find(Type.class, 11); - Type fairy = em.find(Type.class, 18); - em.persist(trainer); - em.persist(new Pokemon(trainer, "Staryu", 184, Arrays.asList(water))); - em.persist(new Pokemon(trainer, "Psyduck", 92, Arrays.asList(water))); - em.persist(new Pokemon(trainer, "Corsola", 147, Arrays.asList(rock))); - em.persist(new Pokemon(trainer, "Horsea", 64, Arrays.asList(water))); - em.persist(new Pokemon(trainer, "Azurill", 217, Arrays.asList(normal, fairy))); - em.persist(new Pokemon(trainer, "Togepi", 51, Arrays.asList(fairy))); - em.flush(); - em.clear(); - em.getEntityManagerFactory().getCache().evictAll(); - } - - /** - * Create Celadon City with stadium and trainer. - * Used for query tests only. - */ - public static void dbInsertCeladon(final EntityManager em) { - Type poison = em.find(Type.class, 4); - Type grass = em.find(Type.class, 12); - Type psychic = em.find(Type.class, 14); - Trainer trainer = new Trainer("Erika", 16); - Stadium stadium = new Stadium("Celadon Gym", trainer); - City city = new City("Celadon City", "Madam Celadon", stadium); - em.persist(trainer); - em.persist(new Pokemon(trainer, "Gloom", 651, Arrays.asList(grass, poison))); - em.persist(new Pokemon(trainer, "Victreebel", 751, Arrays.asList(grass, poison))); - em.persist(new Pokemon(trainer, "Tangela", 234, Arrays.asList(grass))); - em.persist(new Pokemon(trainer, "Vileplume", 1571, Arrays.asList(grass, poison))); - em.persist(new Pokemon(trainer, "Weepinbell", 1923, Arrays.asList(grass, poison))); - em.persist(new Pokemon(trainer, "Exeggcute", 317, Arrays.asList(grass, psychic))); - //em.persist(stadium); - em.persist(city); - } - - /** - * Create Saffron City with stadium and trainer. - * Used for update tests only. - */ - public static void dbInsertSaffron(final EntityManager em) { - Type fighting = em.find(Type.class, 2); - Type psychic = em.find(Type.class, 14); - Type ice = em.find(Type.class, 15); - Trainer trainer = new Trainer("Sabrina", 23); - Stadium stadium = new Stadium("Saffron Gym", trainer); - City city = new City("Saffron City", "Koichi", stadium); - em.persist(trainer); - em.persist(new Pokemon(trainer, "Alakazam", 2178, Arrays.asList(psychic))); - em.persist(new Pokemon(trainer, "Espeon", 2745, Arrays.asList(psychic))); - em.persist(new Pokemon(trainer, "Mr. Mime", 1478, Arrays.asList(psychic))); - em.persist(new Pokemon(trainer, "Jynx", 2471, Arrays.asList(psychic, ice))); - em.persist(new Pokemon(trainer, "Wobbuffet", 1478, Arrays.asList(psychic))); - em.persist(new Pokemon(trainer, "Gallade", 2147, Arrays.asList(psychic, fighting))); - //em.persist(stadium); - em.persist(city); - } - - /** - * Create Viridian City with stadium and trainer. - * Used for delete tests only. - */ - public static void dbInsertViridian(final EntityManager em) { - Type poison = em.find(Type.class, 4); - Type ground = em.find(Type.class, 5); - Type rock = em.find(Type.class, 6); - Trainer trainer = new Trainer("Giovanni", 37); - Stadium stadium = new Stadium("Viridian Gym", trainer); - City city = new City("Viridian City", "Koichi", stadium); - em.persist(trainer); - em.persist(new Pokemon(trainer, "Rhyperior", 3841, Arrays.asList(ground, rock))); - em.persist(new Pokemon(trainer, "Golem", 3651, Arrays.asList(ground, rock))); - em.persist(new Pokemon(trainer, "Nidoking", 2451, Arrays.asList(ground, poison))); - em.persist(new Pokemon(trainer, "Marowak", 2249, Arrays.asList(ground))); - em.persist(new Pokemon(trainer, "Sandslash", 1953, Arrays.asList(ground))); - em.persist(new Pokemon(trainer, "Nidoqueen", 3147, Arrays.asList(ground))); - //em.persist(stadium); - em.persist(city); - } - -} diff --git a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Delete.java b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Delete.java deleted file mode 100644 index 51b51412943..00000000000 --- a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Delete.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.dao; - -import java.util.List; - -import jakarta.persistence.EntityManager; - -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Stadium; -import io.helidon.tests.integration.jpa.model.Trainer; - -/** - * Delete data for the tests. - */ -public class Delete { - - private Delete() { - throw new UnsupportedOperationException("Instances of Delete class are not allowed"); - } - - /** - * Delete Brock and his pokemons. - * Brock and his pokemons are used for update tests only. - * - * @param em Entity manager instance - */ - public static void dbDeleteBrock(final EntityManager em) { - dbDeleteTrainerAndHisPokemons(em, "Brock"); - } - - /** - * Delete Misty and her pokemons. - * Misty and her pokemons are used for delete tests only. - * - * @param em Entity manager instance - */ - public static void dbDeleteMisty(final EntityManager em) { - dbDeleteTrainerAndHisPokemons(em, "Misty"); - } - - /** - * Delete Ash and his pokemons. - * Ash and his pokemons are used for delete tests only. - * - * @param em Entity manager instance - */ - public static void dbDeleteAsh(final EntityManager em) { - dbDeleteTrainerAndHisPokemons(em, "Ash Ketchum"); - } - - /** - * Delete Celadon City. - * Celadon City is used for query tests only. - * - * @param em Entity manager instance - */ - public static void dbDeleteCeladon(final EntityManager em) { - dbDeleteCity(em, "Celadon City"); - } - - /** - * Delete Saffron City. - * Saffron City is used for update tests only. - * - * @param em Entity manager instance - */ - public static void dbDeleteSaffron(final EntityManager em) { - dbDeleteCity(em, "Saffron City"); - } - - /** - * Delete Viridian City. - * Viridian City is used for update tests only. - * - * @param em Entity manager instance - */ - public static void dbDeleteViridian(final EntityManager em) { - dbDeleteCity(em, "Viridian City"); - } - - /** - * Delete city. - * - * @param em Entity manager instance - * @param name name of city to delete - */ - public static void dbDeleteCity(final EntityManager em, final String name) { - List cities = em.createQuery( - "SELECT c FROM City c WHERE c.name = :name", City.class) - .setParameter("name", name) - .getResultList(); - if (!cities.isEmpty()) { - cities.forEach((city) -> { - Stadium stadium = city.getStadium(); - Trainer trainer = stadium.getTrainer(); - List pokemons = trainer.getPokemons(); - em.remove(city); - //em.remove(stadium); - pokemons.forEach((pokemon) -> em.remove(pokemon)); - em.remove(trainer); - }); - } - } - - /** - * Delete trainer and his pokemons. - * Trainer is identified by his name. - * - * @param em Entity manager instance - * @param name name of trainer to delete - */ - public static void dbDeleteTrainerAndHisPokemons(final EntityManager em, final String name) { - List trainers = em.createQuery( - "SELECT t FROM Trainer t WHERE t.name = :name", Trainer.class) - .setParameter("name", name) - .getResultList(); - if (!trainers.isEmpty()) { - trainers.forEach((trainer) -> { - List pokemons = em.createQuery( - "SELECT p FROM Pokemon p INNER JOIN p.trainer t WHERE t.name = :name", Pokemon.class) - .setParameter("name", name) - .getResultList(); - pokemons.forEach((pokemon) -> em.remove(pokemon)); - em.remove(trainer); - }); - } - } - - /** - * Delete all pokemons. - * - * @param em Entity manager instance - */ - public static void deletePokemons(final EntityManager em) { - em.createQuery("DELETE FROM Pokemon").executeUpdate(); - } - - /** - * Delete all trainers. - * - * @param em Entity manager instance - */ - public static void deleteTrainers(final EntityManager em) { - em.createQuery("DELETE FROM Trainer").executeUpdate(); - } - - /** - * Delete all types. - * - * @param em Entity manager instance - */ - public static void deleteTypes(final EntityManager em) { - em.createQuery("DELETE FROM Type").executeUpdate(); - } - - /** - * Delete all cities. - * - * @param em Entity manager instance - */ - public static void deleteCities(final EntityManager em) { - em.createQuery("DELETE FROM City").executeUpdate(); - } - - /** - * Delete all stadiums. - * - * @param em Entity manager instance - */ - public static void deleteStadiums(final EntityManager em) { - em.createQuery("DELETE FROM Stadium").executeUpdate(); - } - - /** - * Delete all database records. - * - * @param em Entity manager instance - */ - public static void dbCleanup(final EntityManager em) { - deleteCities(em); - deleteStadiums(em); - deletePokemons(em); - deleteTrainers(em); - deleteTypes(em); - } - -} diff --git a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/Pokemon.java b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/Pokemon.java deleted file mode 100644 index 44cbed6fb77..00000000000 --- a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/Pokemon.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.model; - -import java.io.Serializable; -import java.lang.System.Logger.Level; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; - -import jakarta.persistence.*; - -/** - * Pokemon entity. - */ -@Entity -public class Pokemon implements Serializable { - - private static final System.Logger LOGGER = System.getLogger(Pokemon.class.getName()); - - @Id - @GeneratedValue(strategy = GenerationType.TABLE) - private int id; - - @ManyToOne - @JoinColumn(name = "trainer_id") - private Trainer trainer; - - @ManyToMany - @JoinTable( - name="pokemon_type", - joinColumns=@JoinColumn(name="type_id", referencedColumnName="id"), - inverseJoinColumns=@JoinColumn(name="pokemon_id", referencedColumnName="id"), - uniqueConstraints=@UniqueConstraint(columnNames={"type_id", "pokemon_id"}) - ) - private List types; - - private String name; - - private int cp; - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Pokemon: {id="); - sb.append(id); - sb.append(", name="); - if (name != null) { - sb.append('"').append(name).append('"'); - } else { - sb.append(""); - } - sb.append(", cp="); - sb.append(cp); - sb.append(", trainer="); - sb.append(trainer != null ? trainer.toString() : ""); - sb.append(", types="); - sb.append(types != null ? types.toString() : ""); - return sb.toString(); - } - - public Pokemon() { - } - - public Pokemon(Trainer trainer, String name, int cp, List types) { - this.trainer = trainer; - this.name = name; - this.cp = cp; - this.types = types; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public Trainer getTrainer() { - return trainer; - } - - public void setTrainer(Trainer trainer) { - this.trainer = trainer; - } - - public List getTypes() { - return types; - } - - public void setTypes(List types) { - this.types = types; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getCp() { - return cp; - } - - public void setCp(int cp) { - this.cp = cp; - } - - @Override - public boolean equals(Object oth) { - if (this == oth) { - return true; - } - if (oth == null) { - return false; - } - if (oth instanceof Pokemon) { - return id == ((Pokemon)oth).id - && cp == ((Pokemon)oth).cp - && Objects.equals(name, ((Pokemon)oth).name) - && Objects.equals(trainer, ((Pokemon)oth).trainer) - && listEquals(((Pokemon)oth).types); - } - LOGGER.log(Level.WARNING, () -> String.format("Pokemon instanceof failed: %s", oth.getClass().getName())); - return false; - } - - private boolean listEquals(List other) { - if (this.types == other) { - return true; - } - if (this.types == null && other != null - || this.types != null && other == null - || this.types.size() != other.size()) { - return false; - } - Iterator otherIterator = other.iterator(); - for (Type thisType : this.types) { - Type otherType = otherIterator.next(); - if ( (thisType != otherType) && ( thisType == null || !thisType.equals(otherType)) ) { - return false; - } - } - return true; - } - - @Override - public int hashCode() { - int hashCode = id; - if (name != null) { - hashCode = hashCode * 31 + name.hashCode(); - } - if (trainer != null) { - hashCode = hashCode * 31 + trainer.hashCode(); - } - if (cp != 0) { - hashCode = hashCode * 31 + cp; - } - if (types != null) { - for (Type type : types) { - hashCode = hashCode * 31 + type.hashCode(); - } - } - return hashCode; - } - -} diff --git a/tests/integration/jpa/mysql/README.md b/tests/integration/jpa/mysql/README.md new file mode 100644 index 00000000000..13d6b1e1b16 --- /dev/null +++ b/tests/integration/jpa/mysql/README.md @@ -0,0 +1,17 @@ +# JPA Integration Test MySQL + +To run this test: +```shell +mvn clean verify +``` + +Start the database: +```shell +docker run -d \ + --name mysql \ + -e MYSQL_DATABASE=test \ + -e MYSQL_USER=test \ + -e MYSQL_PASSWORD=mysql123 \ + -p 3306:3306 \ + container-registry.oracle.com/mysql/community-server:latest +``` diff --git a/tests/integration/jpa/mysql/pom.xml b/tests/integration/jpa/mysql/pom.xml new file mode 100644 index 00000000000..3cbf5fecd3b --- /dev/null +++ b/tests/integration/jpa/mysql/pom.xml @@ -0,0 +1,72 @@ + + + + + 4.0.0 + + io.helidon.tests.integration.jpa + helidon-tests-integration-jpa-parent + 4.2.0-SNAPSHOT + ../parent/pom.xml + + helidon-tests-integration-jpa-mysql + Helidon Tests Integration JPA MySQL + + + + io.helidon.integrations.db + helidon-integrations-db-mysql + + + com.mysql + mysql-connector-j + + + org.testcontainers + mysql + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + diff --git a/tests/integration/jpa/mysql/src/main/resources/META-INF/microprofile-config.properties b/tests/integration/jpa/mysql/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..4cd632709b1 --- /dev/null +++ b/tests/integration/jpa/mysql/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# +config_ordinal=1 +server.port=0 +features.print-details=true +javax.sql.DataSource.test.dataSource.url=jdbc:mysql://localhost:3306/test?useSSL=false&allowPublicKeyRetrieval=true +javax.sql.DataSource.test.dataSource.user=test +javax.sql.DataSource.test.dataSource.password=mysql123 +javax.sql.DataSource.test.dataSourceClassName=com.mysql.cj.jdbc.MysqlDataSource diff --git a/tests/integration/jpa/appl/src/main/resources/hibernate.properties b/tests/integration/jpa/mysql/src/main/resources/logging.properties similarity index 70% rename from tests/integration/jpa/appl/src/main/resources/hibernate.properties rename to tests/integration/jpa/mysql/src/main/resources/logging.properties index 19ea0ba7e87..451cb7946ca 100644 --- a/tests/integration/jpa/appl/src/main/resources/hibernate.properties +++ b/tests/integration/jpa/mysql/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2023 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,5 @@ # limitations under the License. # -# Byte code for JPA must be generated at compile time. -# This is a limitation of native image -# No longer supported with Hibernate 6.3.x -# hibernate.bytecode.provider=none +handlers=org.slf4j.bridge.SLF4JBridgeHandler +.level=INFO diff --git a/tests/integration/jpa/mysql/src/main/resources/simplelogger.properties b/tests/integration/jpa/mysql/src/main/resources/simplelogger.properties new file mode 100644 index 00000000000..5e27d815414 --- /dev/null +++ b/tests/integration/jpa/mysql/src/main/resources/simplelogger.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.log.org.testcontainers=info +org.slf4j.simpleLogger.log.org.testcontainers.utility=error +org.slf4j.simpleLogger.log.io.helidon=info +org.slf4j.simpleLogger.log.org.hibernate=info +org.slf4j.simpleLogger.log.com.zaxxer=info diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLDeleteLocalTestIT.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLDeleteLocalTestIT.java new file mode 100644 index 00000000000..e63bbf5697c --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLDeleteLocalTestIT.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.DeleteTest; +import io.helidon.tests.integration.jpa.common.DeleteTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local delete test. + */ +@HelidonTest +class MySQLDeleteLocalTestIT extends MySQLLocalTest implements DeleteTest { + + @Inject + private DeleteTestImpl delegate; + + @Test + @Override + public void testDeleteEntity() { + delegate.testDeleteEntity(); + } + + @Test + @Override + public void testDeleteJPQL() { + delegate.testDeleteJPQL(); + } + + @Test + @Override + public void testDeleteCriteria() { + delegate.testDeleteCriteria(); + } + + @Test + @Override + public void testDeleteCity() { + delegate.testDeleteCity(); + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLDeleteRemoteTestIT.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLDeleteRemoteTestIT.java new file mode 100644 index 00000000000..a658fe39522 --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLDeleteRemoteTestIT.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import io.helidon.tests.integration.jpa.common.DeleteTest; + +import org.junit.jupiter.api.Test; + +/** + * Remote delete test. + */ +class MySQLDeleteRemoteTestIT extends MySQLRemoteTest implements DeleteTest { + + MySQLDeleteRemoteTestIT() { + super("/test/delete"); + } + + @Test + @Override + public void testDeleteEntity() { + remoteTest(); + } + + @Test + @Override + public void testDeleteJPQL() { + remoteTest(); + } + + @Test + @Override + public void testDeleteCriteria() { + remoteTest(); + } + + @Test + @Override + public void testDeleteCity() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLInsertLocalTestIT.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLInsertLocalTestIT.java new file mode 100644 index 00000000000..b95f7498b72 --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLInsertLocalTestIT.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.InsertTest; +import io.helidon.tests.integration.jpa.common.InsertTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local insert test. + */ +@HelidonTest +class MySQLInsertLocalTestIT extends MySQLLocalTest implements InsertTest { + + @Inject + private InsertTestImpl delegate; + + @Test + @Override + public void testInsertType() { + delegate.testInsertType(); + } + + @Test + @Override + public void testInsertTrainerWithPokemons() { + delegate.testInsertTrainerWithPokemons(); + } + + @Test + @Override + public void testTownWithStadium() { + delegate.testTownWithStadium(); + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLInsertRemoteTestIT.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLInsertRemoteTestIT.java new file mode 100644 index 00000000000..0f8c98175cb --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLInsertRemoteTestIT.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import io.helidon.tests.integration.jpa.common.InsertTest; + +import org.junit.jupiter.api.Test; + +/** + * Local insert test. + */ +class MySQLInsertRemoteTestIT extends MySQLRemoteTest implements InsertTest { + + MySQLInsertRemoteTestIT() { + super("/test/insert"); + } + + @Test + @Override + public void testInsertType() { + remoteTest(); + } + + @Test + @Override + public void testInsertTrainerWithPokemons() { + remoteTest(); + } + + @Test + @Override + public void testTownWithStadium() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLLocalTest.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLLocalTest.java new file mode 100644 index 00000000000..def4a326389 --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLLocalTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import io.helidon.config.mp.MpConfigSources; +import io.helidon.microprofile.testing.AddConfigSource; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * Base class for the local tests. + */ +@Testcontainers(disabledWithoutDocker = true) +abstract class MySQLLocalTest { + + @Container + static final MySQLContainer CONTAINER = MySQLTestContainer.CONTAINER; + + @AddConfigSource + static ConfigSource config() { + return MpConfigSources.create(MySQLTestContainer.config()); + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLQueryLocalTestIT.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLQueryLocalTestIT.java new file mode 100644 index 00000000000..443ffb540fa --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLQueryLocalTestIT.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.QueryTest; +import io.helidon.tests.integration.jpa.common.QueryTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local query test. + */ +@HelidonTest +class MySQLQueryLocalTestIT extends MySQLLocalTest implements QueryTest { + + @Inject + private QueryTestImpl delegate; + + @Test + @Override + public void testFind() { + delegate.testFind(); + } + + @Test + @Override + public void testQueryJPQL() { + delegate.testQueryJPQL(); + } + + @Test + @Override + public void testQueryCriteria() { + delegate.testQueryCriteria(); + } + + @Test + @Override + public void testQueryCeladonJPQL() { + delegate.testQueryCeladonJPQL(); + } + + @Test + @Override + public void testQueryCeladonCriteria() { + delegate.testQueryCeladonCriteria(); + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLQueryRemoteTestIT.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLQueryRemoteTestIT.java new file mode 100644 index 00000000000..2dc513163ff --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLQueryRemoteTestIT.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import io.helidon.tests.integration.jpa.common.QueryTest; + +import org.junit.jupiter.api.Test; + +/** + * Invoke {@code /test/query} endpoints. + */ +class MySQLQueryRemoteTestIT extends MySQLRemoteTest implements QueryTest { + + MySQLQueryRemoteTestIT() { + super("/test/query"); + } + + @Test + @Override + public void testFind() { + remoteTest(); + } + + @Test + @Override + public void testQueryJPQL() { + remoteTest(); + } + + @Test + @Override + public void testQueryCriteria() { + remoteTest(); + } + + @Test + @Override + public void testQueryCeladonJPQL() { + remoteTest(); + } + + @Test + @Override + public void testQueryCeladonCriteria() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLRemoteTest.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLRemoteTest.java new file mode 100644 index 00000000000..20e51855aac --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLRemoteTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import java.nio.file.Path; +import java.util.Map; + +import io.helidon.tests.integration.harness.ProcessRunner; +import io.helidon.tests.integration.harness.ProcessRunner.ExecMode; +import io.helidon.tests.integration.harness.WaitStrategy; +import io.helidon.tests.integration.harness.TestProcess; +import io.helidon.tests.integration.harness.TestProcesses; +import io.helidon.tests.integration.jpa.common.RemoteTest; + +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * Base class for the remote tests. + */ +@Testcontainers(disabledWithoutDocker = true) +@TestProcesses +abstract class MySQLRemoteTest extends RemoteTest { + + @Container + static final MySQLContainer CONTAINER = MySQLTestContainer.CONTAINER; + + @TestProcess + static final ProcessRunner PROCESS_RUNNER = ProcessRunner.of(ExecMode.CLASS_PATH) + .finalName("helidon-tests-integration-jpa-mysql") + .properties(Map.of("java.util.logging.config.file", Path.of("target/classes/logging.properties").toAbsolutePath())) + .properties(MySQLTestContainer::config) + .waitingFor(WaitStrategy.waitForPort()); + + /** + * Create a new instance. + * + * @param path base path + */ + @SuppressWarnings("resource") + MySQLRemoteTest(String path) { + super(path, PROCESS_RUNNER.process().port()); + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLTestContainer.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLTestContainer.java new file mode 100644 index 00000000000..b0b867b0b0a --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLTestContainer.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import java.util.Map; + +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * Database container utility. + */ +abstract class MySQLTestContainer { + + private static final DockerImageName IMAGE = DockerImageName.parse("container-registry.oracle.com/mysql/community-server") + .asCompatibleSubstituteFor("mysql"); + + static final MySQLContainer CONTAINER = new MySQLContainer<>(IMAGE) + .withPassword("mysql123"); + + static Map config() { + return Map.of("javax.sql.DataSource.test.dataSource.url", CONTAINER.getJdbcUrl()); + } + + private MySQLTestContainer() { + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLUpdateLocalTestIT.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLUpdateLocalTestIT.java new file mode 100644 index 00000000000..0807b04d9bb --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLUpdateLocalTestIT.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.UpdateTest; +import io.helidon.tests.integration.jpa.common.UpdateTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local update test. + */ +@HelidonTest +class MySQLUpdateLocalTestIT extends MySQLLocalTest implements UpdateTest { + + @Inject + private UpdateTestImpl delegate; + + @Test + @Override + public void testUpdateEntity() { + delegate.testUpdateEntity(); + } + + @Test + @Override + public void testUpdateJPQL() { + delegate.testUpdateJPQL(); + } + + @Test + @Override + public void testUpdateCriteria() { + delegate.testUpdateCriteria(); + } + + @Test + @Override + public void testUpdateCity() { + delegate.testUpdateCity(); + } +} diff --git a/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLUpdateRemoteTestIT.java b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLUpdateRemoteTestIT.java new file mode 100644 index 00000000000..f6f15e196f9 --- /dev/null +++ b/tests/integration/jpa/mysql/src/test/java/io/helidon/tests/integration/jpa/mysql/MySQLUpdateRemoteTestIT.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.mysql; + +import io.helidon.tests.integration.jpa.common.UpdateTest; + +import org.junit.jupiter.api.Test; + +/** + * Invoke {@code /test/update} endpoints. + */ +class MySQLUpdateRemoteTestIT extends MySQLRemoteTest implements UpdateTest { + + MySQLUpdateRemoteTestIT() { + super("/test/update"); + } + + @Test + @Override + public void testUpdateEntity() { + remoteTest(); + } + + @Test + @Override + public void testUpdateJPQL() { + remoteTest(); + } + + @Test + @Override + public void testUpdateCriteria() { + remoteTest(); + } + + @Test + @Override + public void testUpdateCity() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/oracle/README.md b/tests/integration/jpa/oracle/README.md new file mode 100644 index 00000000000..55996fb990b --- /dev/null +++ b/tests/integration/jpa/oracle/README.md @@ -0,0 +1,15 @@ +# JPA Integration Test Oracle + +To run this test: +```shell +mvn clean verify +``` + +Start the database: +```shell +docker run -d \ + -name oracledb \ + -e ORACLE_PWD=oracle123 \ + -p 1521:1521 \ + container-registry.oracle.com/database/express:latest +``` diff --git a/tests/integration/jpa/oracle/pom.xml b/tests/integration/jpa/oracle/pom.xml new file mode 100644 index 00000000000..db4b2bdbd1c --- /dev/null +++ b/tests/integration/jpa/oracle/pom.xml @@ -0,0 +1,63 @@ + + + + + 4.0.0 + + io.helidon.tests.integration.jpa + helidon-tests-integration-jpa-parent + 4.2.0-SNAPSHOT + ../parent/pom.xml + + helidon-tests-integration-jpa-oracle + Helidon Tests Integration JPA Oracle + + + + io.helidon.integrations.db + ojdbc + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + diff --git a/tests/integration/jpa/oracle/src/main/resources/META-INF/microprofile-config.properties b/tests/integration/jpa/oracle/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..17ff76423c9 --- /dev/null +++ b/tests/integration/jpa/oracle/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# +config_ordinal=1 +server.port=0 +features.print-details=true +javax.sql.DataSource.test.dataSource.url=jdbc:oracle:thin:@localhost:1521/XE +javax.sql.DataSource.test.dataSource.user=system +javax.sql.DataSource.test.dataSource.password=oracle123 +javax.sql.DataSource.test.dataSourceClassName=oracle.jdbc.pool.OracleDataSource diff --git a/tests/integration/jpa/oracle/src/main/resources/logging.properties b/tests/integration/jpa/oracle/src/main/resources/logging.properties new file mode 100644 index 00000000000..bfcd9aa7a22 --- /dev/null +++ b/tests/integration/jpa/oracle/src/main/resources/logging.properties @@ -0,0 +1,18 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# + +handlers=org.slf4j.bridge.SLF4JBridgeHandler +.level=INFO diff --git a/tests/integration/jpa/oracle/src/main/resources/simplelogger.properties b/tests/integration/jpa/oracle/src/main/resources/simplelogger.properties new file mode 100644 index 00000000000..5e27d815414 --- /dev/null +++ b/tests/integration/jpa/oracle/src/main/resources/simplelogger.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.log.org.testcontainers=info +org.slf4j.simpleLogger.log.org.testcontainers.utility=error +org.slf4j.simpleLogger.log.io.helidon=info +org.slf4j.simpleLogger.log.org.hibernate=info +org.slf4j.simpleLogger.log.com.zaxxer=info diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleDeleteLocalTestIT.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleDeleteLocalTestIT.java new file mode 100644 index 00000000000..4045f022942 --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleDeleteLocalTestIT.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.DeleteTest; +import io.helidon.tests.integration.jpa.common.DeleteTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local delete test. + */ +@HelidonTest +class OracleDeleteLocalTestIT extends OracleLocalTest implements DeleteTest { + + @Inject + private DeleteTestImpl delegate; + + @Test + @Override + public void testDeleteEntity() { + delegate.testDeleteEntity(); + } + + @Test + @Override + public void testDeleteJPQL() { + delegate.testDeleteJPQL(); + } + + @Test + @Override + public void testDeleteCriteria() { + delegate.testDeleteCriteria(); + } + + @Test + @Override + public void testDeleteCity() { + delegate.testDeleteCity(); + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleDeleteRemoteTestIT.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleDeleteRemoteTestIT.java new file mode 100644 index 00000000000..ede5eca6692 --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleDeleteRemoteTestIT.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import io.helidon.tests.integration.jpa.common.DeleteTest; + +import org.junit.jupiter.api.Test; + +/** + * Remote delete test. + */ +class OracleDeleteRemoteTestIT extends OracleRemoteTest implements DeleteTest { + + OracleDeleteRemoteTestIT() { + super("/test/delete"); + } + + @Test + @Override + public void testDeleteEntity() { + remoteTest(); + } + + @Test + @Override + public void testDeleteJPQL() { + remoteTest(); + } + + @Test + @Override + public void testDeleteCriteria() { + remoteTest(); + } + + @Test + @Override + public void testDeleteCity() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleInsertLocalTestIT.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleInsertLocalTestIT.java new file mode 100644 index 00000000000..2a785bfd69c --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleInsertLocalTestIT.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.InsertTest; +import io.helidon.tests.integration.jpa.common.InsertTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local insert test. + */ +@HelidonTest +class OracleInsertLocalTestIT extends OracleLocalTest implements InsertTest { + + @Inject + private InsertTestImpl delegate; + + @Test + @Override + public void testInsertType() { + delegate.testInsertType(); + } + + @Test + @Override + public void testInsertTrainerWithPokemons() { + delegate.testInsertTrainerWithPokemons(); + } + + @Test + @Override + public void testTownWithStadium() { + delegate.testTownWithStadium(); + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleInsertRemoteTestIT.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleInsertRemoteTestIT.java new file mode 100644 index 00000000000..63e50d1aec9 --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleInsertRemoteTestIT.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import io.helidon.tests.integration.jpa.common.InsertTest; + +import org.junit.jupiter.api.Test; + +/** + * Local insert test. + */ +class OracleInsertRemoteTestIT extends OracleRemoteTest implements InsertTest { + + OracleInsertRemoteTestIT() { + super("/test/insert"); + } + + @Test + @Override + public void testInsertType() { + remoteTest(); + } + + @Test + @Override + public void testInsertTrainerWithPokemons() { + remoteTest(); + } + + @Test + @Override + public void testTownWithStadium() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleLocalTest.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleLocalTest.java new file mode 100644 index 00000000000..e7d154291cc --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleLocalTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import io.helidon.config.mp.MpConfigSources; +import io.helidon.microprofile.testing.AddConfigSource; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * Base class for the local tests. + */ +@Testcontainers(disabledWithoutDocker = true) +abstract class OracleLocalTest { + + @Container + static final GenericContainer CONTAINER = OracleTestContainer.CONTAINER; + + @AddConfigSource + static ConfigSource config() { + return MpConfigSources.create(OracleTestContainer.config()); + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleQueryLocalTestIT.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleQueryLocalTestIT.java new file mode 100644 index 00000000000..662d3b58ab3 --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleQueryLocalTestIT.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.QueryTest; +import io.helidon.tests.integration.jpa.common.QueryTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local query test. + */ +@HelidonTest +class OracleQueryLocalTestIT extends OracleLocalTest implements QueryTest { + + @Inject + private QueryTestImpl delegate; + + @Test + @Override + public void testFind() { + delegate.testFind(); + } + + @Test + @Override + public void testQueryJPQL() { + delegate.testQueryJPQL(); + } + + @Test + @Override + public void testQueryCriteria() { + delegate.testQueryCriteria(); + } + + @Test + @Override + public void testQueryCeladonJPQL() { + delegate.testQueryCeladonJPQL(); + } + + @Test + @Override + public void testQueryCeladonCriteria() { + delegate.testQueryCeladonCriteria(); + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleQueryRemoteTestIT.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleQueryRemoteTestIT.java new file mode 100644 index 00000000000..967a08601ca --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleQueryRemoteTestIT.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import io.helidon.tests.integration.jpa.common.QueryTest; + +import org.junit.jupiter.api.Test; + +/** + * Invoke {@code /test/query} endpoints. + */ +class OracleQueryRemoteTestIT extends OracleRemoteTest implements QueryTest { + + OracleQueryRemoteTestIT() { + super("/test/query"); + } + + @Test + @Override + public void testFind() { + remoteTest(); + } + + @Test + @Override + public void testQueryJPQL() { + remoteTest(); + } + + @Test + @Override + public void testQueryCriteria() { + remoteTest(); + } + + @Test + @Override + public void testQueryCeladonJPQL() { + remoteTest(); + } + + @Test + @Override + public void testQueryCeladonCriteria() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleRemoteTest.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleRemoteTest.java new file mode 100644 index 00000000000..c41b235a313 --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleRemoteTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import java.nio.file.Path; +import java.util.Map; + +import io.helidon.tests.integration.harness.ProcessRunner; +import io.helidon.tests.integration.harness.ProcessRunner.ExecMode; +import io.helidon.tests.integration.harness.WaitStrategy; +import io.helidon.tests.integration.harness.TestProcess; +import io.helidon.tests.integration.harness.TestProcesses; +import io.helidon.tests.integration.jpa.common.RemoteTest; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * Base class for the remote tests. + */ +@Testcontainers(disabledWithoutDocker = true) +@TestProcesses +abstract class OracleRemoteTest extends RemoteTest { + + @Container + static final GenericContainer CONTAINER = OracleTestContainer.CONTAINER; + + @TestProcess + static final ProcessRunner PROCESS_RUNNER = ProcessRunner.of(ExecMode.CLASS_PATH) + .finalName("helidon-tests-integration-jpa-oracle") + .properties(Map.of("java.util.logging.config.file", Path.of("target/classes/logging.properties").toAbsolutePath())) + .properties(OracleTestContainer::config) + .waitingFor(WaitStrategy.waitForPort()); + + /** + * Create a new instance. + * + * @param path base path + */ + @SuppressWarnings("resource") + OracleRemoteTest(String path) { + super(path, PROCESS_RUNNER.process().port()); + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleTestContainer.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleTestContainer.java new file mode 100644 index 00000000000..45a2edad701 --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleTestContainer.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import java.time.Duration; +import java.util.Map; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +/** + * Database container utility. + */ +abstract class OracleTestContainer { + + private static final DockerImageName IMAGE = DockerImageName.parse("container-registry.oracle.com/database/express"); + + static final GenericContainer CONTAINER = new GenericContainer<>(IMAGE) + .withEnv("ORACLE_PWD", "oracle123") + .withExposedPorts(1521) + .withStartupAttempts(5) + .waitingFor(Wait.forHealthcheck() + .withStartupTimeout(Duration.ofMinutes(5))); + + static Map config() { + String jdbcUrl = String.format("jdbc:oracle:thin:@localhost:%s/XE", CONTAINER.getMappedPort(1521)); + return Map.of("javax.sql.DataSource.test.dataSource.url", jdbcUrl); + } + + private OracleTestContainer() { + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleUpdateLocalTestIT.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleUpdateLocalTestIT.java new file mode 100644 index 00000000000..0c442928164 --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleUpdateLocalTestIT.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.UpdateTest; +import io.helidon.tests.integration.jpa.common.UpdateTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local update test. + */ +@HelidonTest +class OracleUpdateLocalTestIT extends OracleLocalTest implements UpdateTest { + + @Inject + private UpdateTestImpl delegate; + + @Test + @Override + public void testUpdateEntity() { + delegate.testUpdateEntity(); + } + + @Test + @Override + public void testUpdateJPQL() { + delegate.testUpdateJPQL(); + } + + @Test + @Override + public void testUpdateCriteria() { + delegate.testUpdateCriteria(); + } + + @Test + @Override + public void testUpdateCity() { + delegate.testUpdateCity(); + } +} diff --git a/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleUpdateRemoteTestIT.java b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleUpdateRemoteTestIT.java new file mode 100644 index 00000000000..84dd30ef777 --- /dev/null +++ b/tests/integration/jpa/oracle/src/test/java/io/helidon/tests/integration/jpa/oracle/OracleUpdateRemoteTestIT.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.oracle; + +import io.helidon.tests.integration.jpa.common.UpdateTest; + +import org.junit.jupiter.api.Test; + +/** + * Invoke {@code /test/update} endpoints. + */ +class OracleUpdateRemoteTestIT extends OracleRemoteTest implements UpdateTest { + + OracleUpdateRemoteTestIT() { + super("/test/update"); + } + + @Test + @Override + public void testUpdateEntity() { + remoteTest(); + } + + @Test + @Override + public void testUpdateJPQL() { + remoteTest(); + } + + @Test + @Override + public void testUpdateCriteria() { + remoteTest(); + } + + @Test + @Override + public void testUpdateCity() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/parent/pom.xml b/tests/integration/jpa/parent/pom.xml new file mode 100644 index 00000000000..5273e35275b --- /dev/null +++ b/tests/integration/jpa/parent/pom.xml @@ -0,0 +1,146 @@ + + + + + 4.0.0 + + io.helidon.applications + helidon-mp + 4.2.0-SNAPSHOT + ../../../../applications/mp/pom.xml + + io.helidon.tests.integration.jpa + helidon-tests-integration-jpa-parent + Helidon Tests Integration JPA Parent + pom + + + true + + + + + io.helidon.tests.integration.jpa + helidon-tests-integration-jpa-common + ${project.version} + + + io.helidon.logging + helidon-logging-slf4j + + + org.slf4j + jul-to-slf4j + + + org.slf4j + slf4j-simple + + + org.junit.jupiter + junit-jupiter-api + test + + + io.helidon.microprofile.testing + helidon-microprofile-testing-junit5 + test + + + + org.testcontainers + junit-jupiter + test + + + io.helidon.tests.integration + helidon-tests-integration-harness + ${project.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IT + + + + ${project.build.outputDirectory}/logging.properties + + + ${redirectTestOutputToFile} + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + ${project.build.testOutputDirectory}/logging.properties + + + ${redirectTestOutputToFile} + ${project.build.outputDirectory} + + + + + + + + remote + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + **/*RemoteTestIT + + + + + + + + local + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + **/*LocalTestIT + + + + + + + + diff --git a/tests/integration/jpa/pgsql/README.md b/tests/integration/jpa/pgsql/README.md new file mode 100644 index 00000000000..bcf023b65b3 --- /dev/null +++ b/tests/integration/jpa/pgsql/README.md @@ -0,0 +1,22 @@ +# JPA Integration Test PostgreSQL + +To run this test: +```shell +mvn clean verify +``` + +Build the database Docker image: +```shell +docker build etc/docker -t pgsql +``` + +Start the database: +```shell +docker run -d \ + --name pgsql \ + -e POSTGRES_USER=test \ + -e POSTGRES_PASSWORD=pgsql123 \ + -e POSTGRES_DB=test \ + -p 5432:5432 \ + pgsql +``` diff --git a/tests/integration/jpa/pgsql/etc/docker/Dockerfile b/tests/integration/jpa/pgsql/etc/docker/Dockerfile new file mode 100644 index 00000000000..acb15773ad8 --- /dev/null +++ b/tests/integration/jpa/pgsql/etc/docker/Dockerfile @@ -0,0 +1,28 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# + +FROM oraclelinux:9-slim + +RUN microdnf install postgresql-server && microdnf clean all +RUN mkdir -p /var/run/postgresql && \ + chown postgres:postgres /var/run/postgresql +ADD entrypoint.sh /usr/local/bin/ +ENV PGDATA /var/lib/pgsql/data +USER postgres +ENTRYPOINT ["entrypoint.sh"] +STOPSIGNAL SIGINT +EXPOSE 5432 +CMD ["postgres"] diff --git a/tests/integration/jpa/pgsql/etc/docker/entrypoint.sh b/tests/integration/jpa/pgsql/etc/docker/entrypoint.sh new file mode 100755 index 00000000000..f8feafd7e4c --- /dev/null +++ b/tests/integration/jpa/pgsql/etc/docker/entrypoint.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# +set -Eeo pipefail + +PGDATA="${PGDATA:-/var/lib/pgsql/data}" + +# initialize the data directory +initdb \ + --username="${POSTGRES_USER:-test}" \ + --pwfile=<(printf "%s" "${POSTGRES_PASSWORD:-test}") \ + -D "${PGDATA}" + +# update config file +# - listen on all network interfaces +# - turn off logging collector to log to the console +sed -e "s/^#listen_addresses = 'localhost'/listen_addresses = '*'/g" \ + -e "s/logging_collector = on/logging_collector = off/g" \ + -e "s/^#session_replication_role = 'origin'/session_replication_role = '${POSTGRES_SRR:-origin}'/g" \ + -i "${PGDATA}/postgresql.conf" + +# enabling trust for all connections +printf 'host all all all %s\n' "$(postgres -C password_encryption)" >> "${PGDATA}/pg_hba.conf" + +# temporary start to create the database +pg_ctl -D "${PGDATA}" -w start +psql \ + --username "${POSTGRES_USER:-test}" \ + --no-password \ + --dbname postgres \ + --set db="${POSTGRES_DB:-test}" <<-'EOSQL' + create database :"db"; + set session_replication_role = replica; +EOSQL +pg_ctl -D "${PGDATA}" -m fast -w stop + +exec "${@}" diff --git a/tests/integration/jpa/pgsql/pom.xml b/tests/integration/jpa/pgsql/pom.xml new file mode 100644 index 00000000000..a62528c7718 --- /dev/null +++ b/tests/integration/jpa/pgsql/pom.xml @@ -0,0 +1,72 @@ + + + + + 4.0.0 + + io.helidon.tests.integration.jpa + helidon-tests-integration-jpa-parent + 4.2.0-SNAPSHOT + ../parent/pom.xml + + helidon-tests-integration-jpa-pgsql + Helidon Tests Integration JPA PostgreSQL + + + + io.helidon.integrations.db + helidon-integrations-db-pgsql + + + org.postgresql + postgresql + + + org.testcontainers + jdbc + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + diff --git a/tests/integration/jpa/appl/src/main/resources/META-INF/microprofile-config.properties b/tests/integration/jpa/pgsql/src/main/resources/META-INF/microprofile-config.properties similarity index 62% rename from tests/integration/jpa/appl/src/main/resources/META-INF/microprofile-config.properties rename to tests/integration/jpa/pgsql/src/main/resources/META-INF/microprofile-config.properties index 9ecc2c009ac..182a2bc8c0c 100644 --- a/tests/integration/jpa/appl/src/main/resources/META-INF/microprofile-config.properties +++ b/tests/integration/jpa/pgsql/src/main/resources/META-INF/microprofile-config.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2025 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,11 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -mp.initializer.allow=true -mp.initializer.no-warn=true +config_ordinal=1 +server.port=0 features.print-details=true -javax.sql.DataSource.test.dataSource.url=${db.url} -javax.sql.DataSource.test.dataSource.user=${db.user} -javax.sql.DataSource.test.dataSource.password=${db.password} -javax.sql.DataSource.test.dataSourceClassName=${db.datasource} +javax.sql.DataSource.test.dataSource.url=jdbc:postgresql://localhost:5432/test +javax.sql.DataSource.test.dataSource.user=test +javax.sql.DataSource.test.dataSource.password=pgsql123 +javax.sql.DataSource.test.dataSourceClassName=org.postgresql.ds.PGSimpleDataSource diff --git a/tests/integration/jpa/pgsql/src/main/resources/logging.properties b/tests/integration/jpa/pgsql/src/main/resources/logging.properties new file mode 100644 index 00000000000..bfcd9aa7a22 --- /dev/null +++ b/tests/integration/jpa/pgsql/src/main/resources/logging.properties @@ -0,0 +1,18 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# + +handlers=org.slf4j.bridge.SLF4JBridgeHandler +.level=INFO diff --git a/tests/integration/jpa/pgsql/src/main/resources/simplelogger.properties b/tests/integration/jpa/pgsql/src/main/resources/simplelogger.properties new file mode 100644 index 00000000000..5e27d815414 --- /dev/null +++ b/tests/integration/jpa/pgsql/src/main/resources/simplelogger.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# 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 +# +# http://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. +# +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.log.org.testcontainers=info +org.slf4j.simpleLogger.log.org.testcontainers.utility=error +org.slf4j.simpleLogger.log.io.helidon=info +org.slf4j.simpleLogger.log.org.hibernate=info +org.slf4j.simpleLogger.log.com.zaxxer=info diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLDeleteLocalTestIT.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLDeleteLocalTestIT.java new file mode 100644 index 00000000000..76f1b1ee411 --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLDeleteLocalTestIT.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.DeleteTest; +import io.helidon.tests.integration.jpa.common.DeleteTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local delete test. + */ +@HelidonTest +class PostgreSQLDeleteLocalTestIT extends PostgreSQLLocalTest implements DeleteTest { + + @Inject + private DeleteTestImpl delegate; + + @Test + @Override + public void testDeleteEntity() { + delegate.testDeleteEntity(); + } + + @Test + @Override + public void testDeleteJPQL() { + delegate.testDeleteJPQL(); + } + + @Test + @Override + public void testDeleteCriteria() { + delegate.testDeleteCriteria(); + } + + @Test + @Override + public void testDeleteCity() { + delegate.testDeleteCity(); + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLDeleteRemoteTestIT.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLDeleteRemoteTestIT.java new file mode 100644 index 00000000000..f410d88fc79 --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLDeleteRemoteTestIT.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import io.helidon.tests.integration.jpa.common.DeleteTest; + +import org.junit.jupiter.api.Test; + +/** + * Remote delete test. + */ +class PostgreSQLDeleteRemoteTestIT extends PostgreSQLRemoteTest implements DeleteTest { + + PostgreSQLDeleteRemoteTestIT() { + super("/test/delete"); + } + + @Test + @Override + public void testDeleteEntity() { + remoteTest(); + } + + @Test + @Override + public void testDeleteJPQL() { + remoteTest(); + } + + @Test + @Override + public void testDeleteCriteria() { + remoteTest(); + } + + @Test + @Override + public void testDeleteCity() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLInsertLocalTestIT.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLInsertLocalTestIT.java new file mode 100644 index 00000000000..5c86899d3d6 --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLInsertLocalTestIT.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.InsertTest; +import io.helidon.tests.integration.jpa.common.InsertTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local insert test. + */ +@HelidonTest +class PostgreSQLInsertLocalTestIT extends PostgreSQLLocalTest implements InsertTest { + + @Inject + private InsertTestImpl delegate; + + @Test + @Override + public void testInsertType() { + delegate.testInsertType(); + } + + @Test + @Override + public void testInsertTrainerWithPokemons() { + delegate.testInsertTrainerWithPokemons(); + } + + @Test + @Override + public void testTownWithStadium() { + delegate.testTownWithStadium(); + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLInsertRemoteTestIT.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLInsertRemoteTestIT.java new file mode 100644 index 00000000000..35f4ad140ce --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLInsertRemoteTestIT.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import io.helidon.tests.integration.jpa.common.InsertTest; + +import org.junit.jupiter.api.Test; + +/** + * Local insert test. + */ +class PostgreSQLInsertRemoteTestIT extends PostgreSQLRemoteTest implements InsertTest { + + PostgreSQLInsertRemoteTestIT() { + super("/test/insert"); + } + + @Test + @Override + public void testInsertType() { + remoteTest(); + } + + @Test + @Override + public void testInsertTrainerWithPokemons() { + remoteTest(); + } + + @Test + @Override + public void testTownWithStadium() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLLocalTest.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLLocalTest.java new file mode 100644 index 00000000000..07ceb8fbdf4 --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLLocalTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import io.helidon.config.mp.MpConfigSources; +import io.helidon.microprofile.testing.AddConfigSource; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * Base class for the local tests. + */ +@Testcontainers(disabledWithoutDocker = true) +abstract class PostgreSQLLocalTest { + + @Container + static final JdbcDatabaseContainer CONTAINER = PostgreSQLTestContainer.CONTAINER; + + @AddConfigSource + static ConfigSource config() { + return MpConfigSources.create(PostgreSQLTestContainer.config()); + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLQueryLocalTestIT.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLQueryLocalTestIT.java new file mode 100644 index 00000000000..d740a39149d --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLQueryLocalTestIT.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.QueryTest; +import io.helidon.tests.integration.jpa.common.QueryTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local query test. + */ +@HelidonTest +class PostgreSQLQueryLocalTestIT extends PostgreSQLLocalTest implements QueryTest { + + @Inject + private QueryTestImpl delegate; + + @Test + @Override + public void testFind() { + delegate.testFind(); + } + + @Test + @Override + public void testQueryJPQL() { + delegate.testQueryJPQL(); + } + + @Test + @Override + public void testQueryCriteria() { + delegate.testQueryCriteria(); + } + + @Test + @Override + public void testQueryCeladonJPQL() { + delegate.testQueryCeladonJPQL(); + } + + @Test + @Override + public void testQueryCeladonCriteria() { + delegate.testQueryCeladonCriteria(); + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLQueryRemoteTestIT.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLQueryRemoteTestIT.java new file mode 100644 index 00000000000..7f65ee9f5cf --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLQueryRemoteTestIT.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import io.helidon.tests.integration.jpa.common.QueryTest; + +import org.junit.jupiter.api.Test; + +/** + * Invoke {@code /test/query} endpoints. + */ +class PostgreSQLQueryRemoteTestIT extends PostgreSQLRemoteTest implements QueryTest { + + PostgreSQLQueryRemoteTestIT() { + super("/test/query"); + } + + @Test + @Override + public void testFind() { + remoteTest(); + } + + @Test + @Override + public void testQueryJPQL() { + remoteTest(); + } + + @Test + @Override + public void testQueryCriteria() { + remoteTest(); + } + + @Test + @Override + public void testQueryCeladonJPQL() { + remoteTest(); + } + + @Test + @Override + public void testQueryCeladonCriteria() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLRemoteTest.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLRemoteTest.java new file mode 100644 index 00000000000..5801ca5ab88 --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLRemoteTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import java.nio.file.Path; +import java.util.Map; + +import io.helidon.tests.integration.harness.ProcessRunner; +import io.helidon.tests.integration.harness.ProcessRunner.ExecMode; +import io.helidon.tests.integration.harness.WaitStrategy; +import io.helidon.tests.integration.harness.TestProcess; +import io.helidon.tests.integration.harness.TestProcesses; +import io.helidon.tests.integration.jpa.common.RemoteTest; + +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * Base class for the remote tests. + */ +@Testcontainers(disabledWithoutDocker = true) +@TestProcesses +abstract class PostgreSQLRemoteTest extends RemoteTest { + + @Container + static final JdbcDatabaseContainer CONTAINER = PostgreSQLTestContainer.CONTAINER; + + @TestProcess + static final ProcessRunner PROCESS_RUNNER = ProcessRunner.of(ExecMode.CLASS_PATH) + .finalName("helidon-tests-integration-jpa-pgsql") + .properties(Map.of("java.util.logging.config.file", Path.of("target/classes/logging.properties").toAbsolutePath())) + .properties(PostgreSQLTestContainer::config) + .waitingFor(WaitStrategy.waitForPort()); + + /** + * Create a new instance. + * + * @param path base path + */ + @SuppressWarnings("resource") + PostgreSQLRemoteTest(String path) { + super(path, PROCESS_RUNNER.process().port()); + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLTestContainer.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLTestContainer.java new file mode 100644 index 00000000000..a097c9ef368 --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLTestContainer.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import java.nio.file.Path; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Map; +import java.util.concurrent.Future; + +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.images.builder.ImageFromDockerfile; + +/** + * Database container utility. + */ +abstract class PostgreSQLTestContainer { + + private static final ImageFromDockerfile IMAGE = new ImageFromDockerfile("pgsql", false) + .withFileFromPath(".", Path.of("etc/docker")); + + static final JdbcDatabaseContainer CONTAINER = new PostgreSQLContainer(IMAGE) + .withPassword("pgsql123"); + + static Map config() { + return Map.of("javax.sql.DataSource.test.dataSource.url", CONTAINER.getJdbcUrl()); + } + + private PostgreSQLTestContainer() { + } + + private static final class PostgreSQLContainer extends JdbcDatabaseContainer { + + private String dbName = "test"; + private String username = "test"; + private String password = "test"; + + PostgreSQLContainer(Future image) { + super(image); + waitStrategy = new LogMessageWaitStrategy() + .withRegEx(".*database system is ready to accept connections.*\\s") + .withTimes(2) + .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS)); + addExposedPort(5432); + } + + @Override + protected void configure() { + // Disable Postgres driver use of java.util.logging to reduce noise at startup time + withUrlParam("loggerLevel", "OFF"); + addEnv("POSTGRES_DB", dbName); + addEnv("POSTGRES_USER", username); + addEnv("POSTGRES_PASSWORD", password); + } + + @Override + public PostgreSQLContainer withUsername(String username) { + this.username = username; + return this; + } + + @Override + public PostgreSQLContainer withPassword(String password) { + this.password = password; + return this; + } + + @Override + public PostgreSQLContainer withDatabaseName(String dbName) { + this.dbName = dbName; + return this; + } + + @Override + public String getDriverClassName() { + return "org.postgresql.Driver"; + } + + @Override + public String getJdbcUrl() { + return "jdbc:postgresql://localhost:%d/%s".formatted(getMappedPort(5432), dbName); + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + protected String getTestQueryString() { + return "SELECT 1"; + } + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLUpdateLocalTestIT.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLUpdateLocalTestIT.java new file mode 100644 index 00000000000..0b643c82e59 --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLUpdateLocalTestIT.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.tests.integration.jpa.common.UpdateTest; +import io.helidon.tests.integration.jpa.common.UpdateTestImpl; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +/** + * Local update test. + */ +@HelidonTest +class PostgreSQLUpdateLocalTestIT extends PostgreSQLLocalTest implements UpdateTest { + + @Inject + private UpdateTestImpl delegate; + + @Test + @Override + public void testUpdateEntity() { + delegate.testUpdateEntity(); + } + + @Test + @Override + public void testUpdateJPQL() { + delegate.testUpdateJPQL(); + } + + @Test + @Override + public void testUpdateCriteria() { + delegate.testUpdateCriteria(); + } + + @Test + @Override + public void testUpdateCity() { + delegate.testUpdateCity(); + } +} diff --git a/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLUpdateRemoteTestIT.java b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLUpdateRemoteTestIT.java new file mode 100644 index 00000000000..7cec1dafcbf --- /dev/null +++ b/tests/integration/jpa/pgsql/src/test/java/io/helidon/tests/integration/jpa/pgsql/PostgreSQLUpdateRemoteTestIT.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.tests.integration.jpa.pgsql; + +import io.helidon.tests.integration.jpa.common.UpdateTest; + +import org.junit.jupiter.api.Test; + +/** + * Invoke {@code /test/update} endpoints. + */ +class PostgreSQLUpdateRemoteTestIT extends PostgreSQLRemoteTest implements UpdateTest { + + PostgreSQLUpdateRemoteTestIT() { + super("/test/update"); + } + + @Test + @Override + public void testUpdateEntity() { + remoteTest(); + } + + @Test + @Override + public void testUpdateJPQL() { + remoteTest(); + } + + @Test + @Override + public void testUpdateCriteria() { + remoteTest(); + } + + @Test + @Override + public void testUpdateCity() { + remoteTest(); + } +} diff --git a/tests/integration/jpa/pom.xml b/tests/integration/jpa/pom.xml index da491cc7ffd..84660cb6a3d 100644 --- a/tests/integration/jpa/pom.xml +++ b/tests/integration/jpa/pom.xml @@ -1,6 +1,6 @@ - - - 4.0.0 - - - io.helidon.tests.integration.jpa - helidon-tests-integration-jpa-project - 4.2.0-SNAPSHOT - - - helidon-tests-integration-jpa-simple - Helidon Tests Integration JPA Simple Tests - - - - io.helidon.tests.integration.jpa - helidon-tests-integration-jpa-model - ${project.version} - - - io.helidon.microprofile.metrics - helidon-microprofile-metrics - - - io.helidon.integrations.cdi - helidon-integrations-cdi-jta-weld - test - - - io.helidon.integrations.cdi - helidon-integrations-cdi-datasource-hikaricp - test - - - io.helidon.integrations.cdi - helidon-integrations-cdi-jpa - test - - - io.helidon.integrations.cdi - helidon-integrations-cdi-hibernate - test - - - io.helidon.microprofile.cdi - helidon-microprofile-cdi - test - - - io.helidon.integrations.cdi - helidon-integrations-cdi-delegates - test - - - io.helidon.integrations.cdi - helidon-integrations-cdi-reference-counted-context - test - - - - jakarta.persistence - jakarta.persistence-api - test - - - jakarta.transaction - jakarta.transaction-api - test - - - jakarta.enterprise - jakarta.enterprise.cdi-api - test - - - jakarta.validation - jakarta.validation-api - - - - com.h2database - h2 - test - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - io.smallrye - jandex - runtime - - - org.slf4j - slf4j-jdk14 - runtime - - - io.helidon.logging - helidon-logging-jul - test - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-libs - - - - - io.smallrye - jandex-maven-plugin - - - make-index - - jandex - - process-classes - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - org.apache.maven.plugins - maven-failsafe-plugin - - methods - 10 - - - - test-run - integration-test - - integration-test - - - - io.helidon.tests.integration.jpa.simple.test.*IT - - - - - - - - - diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/DbUtils.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/DbUtils.java deleted file mode 100644 index 24b5bc4d932..00000000000 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/DbUtils.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.simple; - -import java.util.List; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; - -import io.helidon.tests.integration.jpa.dao.Create; -import io.helidon.tests.integration.jpa.dao.Delete; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Trainer; - -/** - * Database utilities. - */ -public class DbUtils { - - public static int ASH_ID = -1; - - private DbUtils() { - throw new UnsupportedOperationException("Instances of DbUtils class are not allowed"); - } - - /** - * Initialize database records. - * - * @param pu persistence unit context - */ - public static void dbInit(PU pu) { - Create.dbInsertTypes(pu.getEm()); - ASH_ID = Create.dbInsertAsh(pu.getEm()); - Create.dbInsertCeladon(pu.getEm()); - pu.getEm().flush(); - pu.getEm().clear(); - pu.getEm().getEntityManagerFactory().getCache().evictAll(); - - } - - /** - * Delete all database records. - * - * @param pu persistence unit context - */ - public static void dbCleanup(PU pu) { - Delete.dbCleanup(pu.getEm()); - } - - /** - * Find trainer by ID. - * - * @param pu persistence unit context - * @param id trainer ID - * @return trainer with specified ID or {@code null} if no such trainer exists - */ - public static Trainer findTrainer(PU pu, int id) { - final EntityManager em = pu.getEm(); - TypedQuery q = em.createQuery("SELECT t FROM Trainer t JOIN FETCH t.pokemons WHERE t.id = :id", Trainer.class); - q.setParameter("id", ASH_ID); - List result = q.getResultList(); - return result != null && result.size() > 0 ? result.get(0) : null; - } - - /** - * Find trainer's pokemons. - * - * @param pu persistence unit context - * @param trainer trainer entity - * @return {@code List} of trainer's pokemons - */ - public static List trainersPokemons(PU pu, Trainer trainer) { - final EntityManager em = pu.getEm(); - TypedQuery q = em.createQuery("SELECT p FROM Pokemon p WHERE p.trainer.id = :id", Pokemon.class); - q.setParameter("id", trainer.getId()); - return q.getResultList(); - } - - /** - * Find pokemon by name from pokemon List. - * - * @param pokemons List to search - * @param name name of pokemon - * @return found pokemon or null when no such pokemon exists - */ - public static Pokemon findPokemonByName(List pokemons, String name) { - if (pokemons != null && !pokemons.isEmpty()) { - for (Pokemon pokemon : pokemons) { - if (pokemon.getName().equals(name)) { - return pokemon; - } - } - } - return null; - } - -} diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/PU.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/PU.java deleted file mode 100644 index a07e4f09fca..00000000000 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/PU.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.simple; - -import java.util.function.Consumer; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.se.SeContainer; -import jakarta.enterprise.inject.se.SeContainerInitializer; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.transaction.Transactional; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -/** - * Persistence unit context. - */ -@ApplicationScoped -public class PU { - - private static final SeContainer CONTAINER = initContainer(); - - private static SeContainer initContainer() { - final SeContainerInitializer initializer = SeContainerInitializer - .newInstance() - .addBeanClasses(PU.class); - assertNotNull(initializer); - return initializer.initialize(); - } - - /** - * Provides an instance of persistence unit context. - * - * @return an instance of persistence unit context - */ - public static PU getInstance() { - return CONTAINER.select(PU.class).get(); - } - - @PersistenceContext(unitName = "test") - private EntityManager em; - - /** - * Get EntityManager instance. - * - * @return EntityManager instance - */ - public EntityManager getEm() { - return em; - } - - /** - * Get EntityManager instance with all caches evicted. - * - * @return EntityManager instance with all caches evicted - */ - public EntityManager getCleanEm() { - em.flush(); - em.clear(); - em.getEntityManagerFactory().getCache().evictAll(); - return em; - } - - /*** - * Run provided function in transaction. - * - * @param tx function to be run in transaction - */ - @Transactional - public void tx(Consumer tx) { - tx.accept(this); - } - -} diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/DeleteIT.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/DeleteIT.java deleted file mode 100644 index d9b30c93393..00000000000 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/DeleteIT.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.simple.test; - -import java.util.List; - -import io.helidon.tests.integration.jpa.simple.DbUtils; -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Root; - -import io.helidon.tests.integration.jpa.dao.Create; -import io.helidon.tests.integration.jpa.dao.Delete; -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Stadium; -import io.helidon.tests.integration.jpa.model.Trainer; -import io.helidon.tests.integration.jpa.simple.PU; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; - -/** - * Verify delete operations of ORM. - */ -public class DeleteIT { - - private static PU pu; - - // Misty and her pokemons are used for delete tests only - private static void dbInsertMisty() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - Create.dbInsertMisty(em); - Create.dbInsertViridian(em); - }); - } - - // Delete Misty and her pokemons after delete tests - private static void dbDeleteMisty() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - Delete.dbDeleteMisty(em); - Delete.dbDeleteViridian(em); - }); - } - - @BeforeAll - public static void setup() { - pu = PU.getInstance(); - pu.tx(pu -> DbUtils.dbInit(pu)); - dbInsertMisty(); - } - - @AfterAll - public static void destroy() { - pu.tx(pu -> DbUtils.dbCleanup(pu)); - pu = null; - } - - /** - * Delete pokemon: release Misty's Staryu. - * Modification is done using entity instance. - */ - @Test - public void testDeleteEntity() { - int ids[] = new int[1]; - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - Pokemon staryu = em.createQuery( - "SELECT p FROM Pokemon p WHERE p.name = :name", Pokemon.class) - .setParameter("name", "Staryu") - .getSingleResult(); - ids[0] = staryu.getId(); - em.remove(staryu); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - Pokemon dbStaryu = em.find(Pokemon.class, ids[0]); - assertThat(dbStaryu, nullValue()); - }); - } - - /** - * Delete pokemon: release Misty's Psyduck. - * Modification is done using JPQL. - */ - @Test - public void testDeleteJPQL() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - int deleted = em.createQuery( - "DELETE FROM Pokemon p WHERE p.name = :name") - .setParameter("name", "Psyduck") - .executeUpdate(); - assertThat(deleted, is(1)); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - List pokemons = em.createQuery( - "SELECT p FROM Pokemon p WHERE p.name=:name", Pokemon.class) - .setParameter("name", "Psyduck") - .getResultList(); - assertThat(pokemons, empty()); - }); - } - - /** - * Delete pokemon: release Misty's Corsola. - * Modification is done using CriteriaDelete. - */ - @Test - public void testDeleteCriteria() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaDelete cu = cb.createCriteriaDelete(Pokemon.class); - Root pokemonRoot = cu.from(Pokemon.class); - cu.where(cb.equal(pokemonRoot.get("name"), "Corsola")); - int deleted = em.createQuery(cu).executeUpdate(); - assertThat(deleted, is(1)); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(Pokemon.class); - Root pokemonRoot = cq.from(Pokemon.class); - cq.select(pokemonRoot) - .where(cb.equal(pokemonRoot.get("name"), "Corsola")); - List pokemons = em.createQuery(cq).getResultList(); - assertThat(pokemons, empty()); - }); - } - - /** - * Delete Viridian City. - */ - @Test - public void testDeleteViridianCity() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - City city = em.createQuery( - "SELECT c FROM City c WHERE c.name = :name", City.class) - .setParameter("name", "Viridian City") - .getSingleResult(); - Stadium stadium = city.getStadium(); - Trainer trainer = stadium.getTrainer(); - List pokemons = trainer.getPokemons(); - em.remove(city); - em.remove(trainer); - pokemons.forEach(poklemon -> em.remove(poklemon)); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - List cities = em.createQuery( - "SELECT c FROM City c WHERE c.name = :name", City.class) - .setParameter("name", "Viridian City") - .getResultList(); - assertThat(cities, empty()); - }); - } - -} diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/InsertIT.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/InsertIT.java deleted file mode 100644 index b3733339c01..00000000000 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/InsertIT.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.simple.test; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import io.helidon.tests.integration.jpa.simple.DbUtils; -import jakarta.persistence.EntityManager; - -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Stadium; -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Trainer; -import io.helidon.tests.integration.jpa.model.Type; -import io.helidon.tests.integration.jpa.simple.PU; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; - -/** - * Verify create/insert operations of ORM. - */ -public class InsertIT { - - private static PU pu; - - private static final Set DELETE_POKEMONS = new HashSet<>(); - private static final Set DELETE_TRAINERS = new HashSet<>(); - private static final Set DELETE_STADIUMS = new HashSet<>(); - private static final Set DELETE_TOWNS = new HashSet<>(); - - @BeforeAll - public static void setup() { - pu = PU.getInstance(); - pu.tx(pu -> DbUtils.dbInit(pu)); - } - - @AfterAll - public static void destroy() { - pu.tx(pu -> DbUtils.dbCleanup(pu)); - pu = null; - } - - /** - * Verify simple create operation (persist) on a single database row. - */ - @Test - public void testInsertType() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - Type type = new Type(20, "TestType"); - em.persist(type); - em.flush(); - Type dbType = em.find(Type.class, 20); - assertThat(dbType, is(type)); - }); - } - - /** - * Verify complex create operation (persist) on a full ORM model (Gary Oak and his 6 pokemons). - * Relations are not marked for cascade persist operation so every entity instance has to be persisted separately. - */ - @Test - void testInsertTrainerWithPokemons() { - // Pass data between lambdas - final Pokemon[] pokemons = new Pokemon[6]; - final Trainer[] trainers = new Trainer[1]; - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - Type normal = em.find(Type.class, 1); - Type flying = em.find(Type.class, 3); - Type poison = em.find(Type.class, 4); - Type fire = em.find(Type.class, 10); - Type water = em.find(Type.class, 11); - Type electric = em.find(Type.class, 13); - trainers[0] = new Trainer("Gary Oak", 10); - pokemons[0] = new Pokemon(trainers[0], "Krabby", 236, Arrays.asList(water)); - pokemons[1] = new Pokemon(trainers[0], "Nidoran", 251, Arrays.asList(poison)); - pokemons[2] = new Pokemon(trainers[0], "Eevee", 115, Arrays.asList(normal)); - pokemons[3] = new Pokemon(trainers[0], "Electivire", 648, Arrays.asList(electric)); - pokemons[4] = new Pokemon(trainers[0], "Dodrio", 346, Arrays.asList(normal, flying)); - pokemons[5] = new Pokemon(trainers[0], "Magmar", 648, Arrays.asList(fire)); - em.persist(trainers[0]); - em.persist(pokemons[0]); - em.persist(pokemons[1]); - em.persist(pokemons[2]); - em.persist(pokemons[3]); - em.persist(pokemons[4]); - em.persist(pokemons[5]); - em.flush(); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - Pokemon dbKrabby = em.find(Pokemon.class, pokemons[0].getId()); - Pokemon dbNidoran = em.find(Pokemon.class, pokemons[1].getId()); - Pokemon dbEvee = em.find(Pokemon.class, pokemons[2].getId()); - Pokemon dbElectivire = em.find(Pokemon.class, pokemons[3].getId()); - Pokemon dbDodrio = em.find(Pokemon.class, pokemons[4].getId()); - Pokemon dbMagmar = em.find(Pokemon.class, pokemons[5].getId()); - Trainer dbTrainer = dbKrabby.getTrainer(); - assertThat(dbKrabby, is(pokemons[0])); - assertThat(dbNidoran, is(pokemons[1])); - assertThat(dbEvee, is(pokemons[2])); - assertThat(dbElectivire, is(pokemons[3])); - assertThat(dbDodrio, is(pokemons[4])); - assertThat(dbMagmar, is(pokemons[5])); - assertThat(dbTrainer, is(trainers[0])); - for (Pokemon pokemon : pokemons) { - DELETE_POKEMONS.add(pokemon.getId()); - } - DELETE_TRAINERS.add(dbTrainer.getId()); - }); - } - - /** - * Verify complex create operation (persist) on a full ORM model (Lt. Surge in Vermilion City). - */ - @Test - void testTownWithStadium() { - final Trainer[] trainers = new Trainer[1]; - final Pokemon[] pokemons = new Pokemon[6]; - final Stadium[] stadiums = new Stadium[1]; - final City[] cities = new City[1]; - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - Type steel = em.find(Type.class, 9); - Type electric = em.find(Type.class, 13); - trainers[0] = new Trainer("Lt. Surge", 28); - pokemons[0] = new Pokemon(trainers[0], "Raichu", 1521, Arrays.asList(electric)); - pokemons[1] = new Pokemon(trainers[0], "Manectric", 1589, Arrays.asList(electric)); - pokemons[2] = new Pokemon(trainers[0], "Magnezone", 1853, Arrays.asList(electric)); - pokemons[3] = new Pokemon(trainers[0], "Electrode", 1237, Arrays.asList(electric)); - pokemons[4] = new Pokemon(trainers[0], "Pachirisu", 942, Arrays.asList(electric)); - pokemons[5] = new Pokemon(trainers[0], "Electivire", 1931, Arrays.asList(electric)); - stadiums[0] = new Stadium("Vermilion Gym", trainers[0]); - cities[0] = new City("Vermilion City", "Mina", stadiums[0]); - em.persist(trainers[0]); - em.persist(pokemons[0]); - em.persist(pokemons[1]); - em.persist(pokemons[2]); - em.persist(pokemons[3]); - em.persist(pokemons[4]); - em.persist(pokemons[5]); - //em.persist(stadiums[0]); - em.persist(cities[0]); - em.flush(); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - City dbCity = em.find(City.class, cities[0].getId()); - Stadium dbStadium = dbCity.getStadium(); - Trainer dbTrainer = dbStadium.getTrainer(); - List dbPokemons = dbTrainer.getPokemons(); - Set pokemonSet = new HashSet<>(pokemons.length); - for (Pokemon pokemon : pokemons) { - pokemonSet.add(pokemon); - } - for (Pokemon dbPokemon : dbPokemons) { - assertThat(pokemonSet.remove(dbPokemon), is(true)); - } - assertThat(pokemonSet, empty()); - assertThat(dbTrainer, is(trainers[0])); - assertThat(dbStadium, is(stadiums[0])); - assertThat(dbCity, is(cities[0])); - for (Pokemon pokemon : pokemons) { - DELETE_POKEMONS.add(pokemon.getId()); - } - DELETE_TRAINERS.add(dbTrainer.getId()); - DELETE_STADIUMS.add(dbStadium.getId()); - DELETE_TOWNS.add(dbCity.getId()); - }); - } - -} diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/QueryIT.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/QueryIT.java deleted file mode 100644 index cd91f34887d..00000000000 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/QueryIT.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.simple.test; - -import java.util.List; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Root; - -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Trainer; -import io.helidon.tests.integration.jpa.simple.DbUtils; -import io.helidon.tests.integration.jpa.simple.PU; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; - -/** - * Verify query operations of ORM. - */ -public class QueryIT { - - private static PU pu; - - @BeforeAll - public static void setup() { - pu = PU.getInstance(); - pu.tx(pu -> DbUtils.dbInit(pu)); - } - - @AfterAll - public static void destroy() { - pu.tx(pu -> DbUtils.dbCleanup(pu)); - pu = null; - } - - /** - * Find trainer Ash and his pokemons. - */ - @Test - public void testFind() { - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - Trainer ash = em.find(Trainer.class, DbUtils.ASH_ID); - List pokemons = ash.getPokemons(); - assertThat(ash, notNullValue()); - assertThat(pokemons, not(empty())); - }); - } - - /** - * Query trainer Ash and his pokemons using JPQL. - */ - @Test - public void testQueryJPQL() { - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - Trainer ash = em.createQuery( - "SELECT t FROM Trainer t JOIN FETCH t.pokemons p WHERE t.id = :id", Trainer.class) - .setParameter("id", DbUtils.ASH_ID) - .getSingleResult(); - List pokemons = ash.getPokemons(); - assertThat(ash, notNullValue()); - assertThat(pokemons, not(empty())); - }); - } - - /** - * Query trainer Ash and his pokemons using CriteriaQuery. - */ - @Test - public void testQueryCriteria() { - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(Trainer.class); - Root trainerRoot = cq.from(Trainer.class); - cq.select(trainerRoot) - .where(cb.equal(trainerRoot.get("id"), DbUtils.ASH_ID)); - Trainer ash = em.createQuery(cq).getSingleResult(); - List pokemons = ash.getPokemons(); - assertThat(ash, notNullValue()); - assertThat(pokemons, not(empty())); - }); - } - - /** - * Query Celadon city using JPQL. - */ - @Test - public void testQueryCeladonJPQL() { - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - City city = em.createQuery( - "SELECT c FROM City c " - + "JOIN FETCH c.stadium s " - + "JOIN FETCH s.trainer t " - + "WHERE c.name = :name", City.class) - .setParameter("name", "Celadon City") - .getSingleResult(); - assertThat(city.getName(), is("Celadon City")); - assertThat(city.getStadium().getName(), is("Celadon Gym")); - assertThat(city.getStadium().getTrainer().getName(), is("Erika")); - }); - } - - /** - * Query Celadon city using CriteriaQuery. - */ - @Test - public void testQueryCeladonCriteria() { - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(City.class); - Root cityRoot = cq.from(City.class); - cityRoot - .fetch("stadium") - .fetch("trainer"); - cq.select(cityRoot) - .where(cb.equal(cityRoot.get("name"), "Celadon City")); - City city = em.createQuery(cq).getSingleResult(); - assertThat(city.getName(), is("Celadon City")); - assertThat(city.getStadium().getName(), is("Celadon Gym")); - assertThat(city.getStadium().getTrainer().getName(), is("Erika")); - }); - } - -} diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/UpdateIT.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/UpdateIT.java deleted file mode 100644 index aa9a11b955f..00000000000 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/UpdateIT.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. - * - * 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 - * - * http://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 io.helidon.tests.integration.jpa.simple.test; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.CriteriaUpdate; -import jakarta.persistence.criteria.Root; - -import io.helidon.tests.integration.jpa.dao.Create; -import io.helidon.tests.integration.jpa.model.City; -import io.helidon.tests.integration.jpa.model.Pokemon; -import io.helidon.tests.integration.jpa.model.Stadium; -import io.helidon.tests.integration.jpa.model.Trainer; -import io.helidon.tests.integration.jpa.simple.DbUtils; -import io.helidon.tests.integration.jpa.simple.PU; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Verify update operations on ORM. - */ -public class UpdateIT { - - private static PU pu; - - @BeforeAll - public static void setup() { - pu = PU.getInstance(); - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - DbUtils.dbInit(pu); - Create.dbInsertBrock(em); - Create.dbInsertSaffron(em); - }); - } - - @AfterAll - public static void destroy() { - pu.tx(pu -> DbUtils.dbCleanup(pu)); - pu = null; - } - - /** - * Update pokemon: evolve Broke's Geodude into Graveler. - * Modification is done using entity instance. - */ - @Test - public void testUpdateEntity() { - Pokemon[] pokemons = new Pokemon[1]; - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - pokemons[0] = em.createQuery( - "SELECT p FROM Pokemon p WHERE p.name = :name", Pokemon.class) - .setParameter("name", "Geodude") - .getSingleResult(); - pokemons[0].getTypes().size(); - pokemons[0].setName("Graveler"); - pokemons[0].setCp(527); - em.persist(pokemons[0]); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - Pokemon dbGraveler = em.find(Pokemon.class, pokemons[0].getId()); - assertThat(dbGraveler, is(pokemons[0])); - }); - } - - /** - * Update pokemon: evolve Broke's Slowpoke into Slowbro. - * Modification is done using JPQL. - */ - @Test - public void testUpdateJPQL() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - int updated = em.createQuery( - "UPDATE Pokemon p SET p.name = :newName, p.cp = :newCp WHERE p.name = :name") - .setParameter("newName", "Slowbro") - .setParameter("newCp", 647) - .setParameter("name", "Slowpoke") - .executeUpdate(); - assertThat(updated, is(1)); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - Pokemon dbWartortle = em.createQuery( - "SELECT p FROM Pokemon p WHERE p.name=:name", Pokemon.class) - .setParameter("name", "Slowbro") - .getSingleResult(); - assertThat(dbWartortle.getCp(), is(647)); - }); - } - - /** - * Update pokemon: evolve Broke's Teddiursa into Ursaring. - * Modification is done using CriteriaUpdate. - */ - @Test - public void testUpdateCriteria() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaUpdate cu = cb.createCriteriaUpdate(Pokemon.class); - Root pokemonRoot = cu.from(Pokemon.class); - cu.where(cb.equal(pokemonRoot.get("name"), "Teddiursa")) - .set("name", "Ursaring") - .set("cp", 1568); - int updated = em.createQuery(cu).executeUpdate(); - assertThat(updated, is(1)); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(Pokemon.class); - Root pokemonRoot = cq.from(Pokemon.class); - cq.select(pokemonRoot) - .where(cb.equal(pokemonRoot.get("name"), "Ursaring")); - Pokemon dbUrsaring = em.createQuery(cq).getSingleResult(); - assertThat(dbUrsaring.getCp(), is(1568)); - }); - } - - /** - * Update Saffron City data structure. - * Replace stadium trainer with new guy who will get all pokemons from previous trainer. - * Also Alakazam evolves to Mega Alakazam at the same time. - */ - @Test - public void testUpdateSaffron() { - City[] cities = new City[1]; - Set pokemonNames = new HashSet<>(6); - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - cities[0] = em.createQuery( - "SELECT c FROM City c WHERE c.name = :name", City.class) - .setParameter("name", "Saffron City") - .getSingleResult(); - Stadium stadium = cities[0].getStadium(); - Trainer sabrina = stadium.getTrainer(); - Trainer janine = new Trainer("Janine", 24); - stadium.setTrainer(janine); - List pokemons = sabrina.getPokemons(); - janine.setPokemons(pokemons); - sabrina.setPokemons(Collections.EMPTY_LIST); - em.remove(sabrina); - em.persist(janine); - for (Pokemon pokemon : pokemons) { - pokemon.setTrainer(janine); - pokemonNames.add(pokemon.getName()); - em.persist(pokemon); - } - em.persist(stadium); - Pokemon alkazam = DbUtils.findPokemonByName(pokemons, "Alakazam"); - // Update pokemon by query - em.createQuery( - "UPDATE Pokemon p SET p.name = :newName, p.cp = :newCp WHERE p.id = :id") - .setParameter("newName", "Mega Alakazam") - .setParameter("newCp", 4348) - .setParameter("id", alkazam.getId()) - .executeUpdate(); - pokemonNames.remove("Alakazam"); - pokemonNames.add("Mega Alakazam"); - }); - pu.tx(pu -> { - final EntityManager em = pu.getCleanEm(); - City city = em.find(City.class, cities[0].getId()); - Stadium stadium = city.getStadium(); - Trainer trainer = stadium.getTrainer(); - List pokemons = trainer.getPokemons(); - assertThat(trainer.getName(), is("Janine")); - for (Pokemon pokemon : pokemons) { - assertThat("Pokemon " + pokemon.getName() + " is missing", pokemonNames.remove(pokemon.getName()), is(true)); - } - }); - } - -} diff --git a/tests/integration/jpa/simple/src/test/resources/META-INF/persistence.xml b/tests/integration/jpa/simple/src/test/resources/META-INF/persistence.xml deleted file mode 100644 index 163e1d7a11e..00000000000 --- a/tests/integration/jpa/simple/src/test/resources/META-INF/persistence.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - io.helidon.tests.integration.jpa.model.Type - io.helidon.tests.integration.jpa.model.Trainer - io.helidon.tests.integration.jpa.model.Pokemon - io.helidon.tests.integration.jpa.model.Stadium - io.helidon.tests.integration.jpa.model.City - - - - - - - - - - diff --git a/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/AbstractGraphQLEndpointIT.java b/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/AbstractGraphQLEndpointIT.java index 5cfc36b38db..0629c42ac54 100644 --- a/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/AbstractGraphQLEndpointIT.java +++ b/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/AbstractGraphQLEndpointIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,18 +22,13 @@ import java.util.logging.Level; import java.util.logging.Logger; -import io.helidon.microprofile.cdi.Main; -import io.helidon.microprofile.server.ServerCdiExtension; - -import jakarta.enterprise.inject.spi.CDI; -import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.logging.LoggingFeature; -import org.junit.jupiter.api.AfterAll; +import static io.helidon.graphql.server.GraphQlConstants.GRAPHQL_WEB_CONTEXT; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -48,86 +43,72 @@ public abstract class AbstractGraphQLEndpointIT extends AbstractGraphQLTest { /** * Initial GraphiQL query from UI. */ - protected static final String QUERY_INTROSPECT = "query {\n" - + " __schema {\n" - + " types {\n" - + " name\n" - + " }\n" - + " }\n" - + "}"; + protected static final String QUERY_INTROSPECT = """ + query { + __schema { + types { + name + } + } + }"""; protected static final String QUERY = "query"; protected static final String VARIABLES = "variables"; protected static final String OPERATION = "operationName"; - protected static final String GRAPHQL = "graphql"; - protected static final String UI = "ui"; - - private static String graphQLUrl; - private static Client client; + private final WebTarget target; - public static Client getClient() { - return client; + @SuppressWarnings("resource") + public AbstractGraphQLEndpointIT(String uri) { + this.target = ClientBuilder.newBuilder() + .register(new LoggingFeature(LOGGER, Level.WARNING, LoggingFeature.Verbosity.PAYLOAD_ANY, 32768)) + .property(ClientProperties.FOLLOW_REDIRECTS, true) + .build() + .target(uri) + .path(GRAPHQL_WEB_CONTEXT); } /** - * Startup the test and create the Jandex index with the supplied {@link Class}es. + * Create the Jandex index with the supplied classes. * - * @param clazzes {@link Class}es to add to index + * @param clazzes classes to add to index */ - public static void _startupTest(Class... clazzes) throws IOException { - // setup the Jandex index with the required classes + public static void setupIndex(Class... clazzes) throws IOException { + // set up the Jandex index with the required classes System.clearProperty(JandexUtils.PROP_INDEX_FILE); String indexFileName = getTempIndexFile(); setupIndex(indexFileName, clazzes); System.setProperty(JandexUtils.PROP_INDEX_FILE, indexFileName); - - Main.main(new String[0]); - - ServerCdiExtension current = CDI.current().getBeanManager().getExtension(ServerCdiExtension.class); - - graphQLUrl= "http://127.0.0.1:" + current.port() + "/"; - - System.out.println("GraphQL URL: " + graphQLUrl); - - client = ClientBuilder.newBuilder() - .register(new LoggingFeature(LOGGER, Level.WARNING, LoggingFeature.Verbosity.PAYLOAD_ANY, 32768)) - .property(ClientProperties.FOLLOW_REDIRECTS, true) - .build(); - } - - @AfterAll - public static void teardownTest() { - Main.shutdown(); } /** - * Return a {@link WebTarget} for the graphQL end point. + * Get the {@link WebTarget}. * - * @return a {@link WebTarget} for the graphQL end point + * @return WebTarget */ - protected static WebTarget getGraphQLWebTarget() { - Client client = getClient(); - return client.target(graphQLUrl); + protected WebTarget target() { + return target; } /** - * Encode the { and }. - * @param param {@link String} to encode - * @return an encoded @link String} + * Encode the { and }. + * + * @param param string to encode + * @return an encoded string */ - protected String encode(String param) { + protected String encode(String param) { return param == null ? null : param.replaceAll("}", "%7D").replaceAll("\\{", "%7B"); } /** - * Generate a Json Map with a request to send to graphql + * Generate a JSON Map with a request to send to graphql. * * @param query the query to send * @param operation optional operation * @param variables optional variables - * @return a {@link java.util.Map} + * @return map */ + @SuppressWarnings("SameParameterValue") protected Map generateJsonRequest(String query, String operation, Map variables) { Map map = new HashMap<>(); map.put(QUERY, query); @@ -141,10 +122,10 @@ protected Map generateJsonRequest(String query, String operation * Return the response as Json. * * @param response {@link jakarta.ws.rs.core.Response} received from web server - * @return the response as Json + * @return JSON entity as a map */ protected Map getJsonResponse(Response response) { - String stringResponse = (response.readEntity(String.class)); + String stringResponse = response.readEntity(String.class); assertThat(stringResponse, is(notNullValue())); return JsonUtils.convertJSONtoMap(stringResponse); } diff --git a/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/GraphQLEndpointIT.java b/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/GraphQLEndpointIT.java index 33539ac6d3c..026958e8b53 100644 --- a/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/GraphQLEndpointIT.java +++ b/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/GraphQLEndpointIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,9 @@ import io.helidon.microprofile.testing.junit5.AddExtension; import io.helidon.microprofile.testing.junit5.DisableDiscovery; import io.helidon.microprofile.testing.junit5.HelidonTest; +import io.helidon.microprofile.testing.junit5.Socket; +import jakarta.inject.Inject; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.MediaType; @@ -39,7 +41,6 @@ import org.junit.jupiter.api.Test; import static io.helidon.graphql.server.GraphQlConstants.GRAPHQL_SCHEMA_URI; -import static io.helidon.graphql.server.GraphQlConstants.GRAPHQL_WEB_CONTEXT; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -60,17 +61,21 @@ public class GraphQLEndpointIT @BeforeAll public static void setup() throws IOException { - _startupTest(Person.class); + setupIndex(Person.class); + } + + @Inject + public GraphQLEndpointIT(@Socket("@default") String uri) { + super(uri); } @Test public void basicEndpointTests() { // test /graphql endpoint - WebTarget webTarget = getGraphQLWebTarget().path(GRAPHQL); Map mapRequest = generateJsonRequest(QUERY_INTROSPECT, null, null); // test POST - Response response = webTarget.request(MediaType.APPLICATION_JSON_TYPE) + Response response = target().request(MediaType.APPLICATION_JSON_TYPE) .post(Entity.json(JsonUtils.convertMapToJson(mapRequest))); assertThat(response, is(notNullValue())); assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode())); @@ -80,12 +85,11 @@ public void basicEndpointTests() { assertThat(graphQLResults.size(), CoreMatchers.is(1)); // test GET - webTarget = getGraphQLWebTarget().path(GRAPHQL) + response = target() .queryParam(QUERY, encode((String) mapRequest.get(QUERY))) .queryParam(OPERATION, encode((String) mapRequest.get(OPERATION))) - .queryParam(VARIABLES, encode((String) mapRequest.get(VARIABLES))); - - response = webTarget.request(MediaType.APPLICATION_JSON_TYPE).get(); + .queryParam(VARIABLES, encode((String) mapRequest.get(VARIABLES))) + .request(MediaType.APPLICATION_JSON_TYPE).get(); assertThat(response, is(notNullValue())); assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode())); graphQLResults = getJsonResponse(response); @@ -95,7 +99,7 @@ public void basicEndpointTests() { @Test public void testGetSchema() { - WebTarget webTarget = getGraphQLWebTarget().path(GRAPHQL_WEB_CONTEXT).path(GRAPHQL_SCHEMA_URI); + WebTarget webTarget = target().path(GRAPHQL_SCHEMA_URI); Response response = webTarget.request(MediaType.TEXT_PLAIN).get(); assertThat(response, is(notNullValue())); assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode())); diff --git a/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/IngorableIT.java b/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/IgnorableIT.java similarity index 98% rename from tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/IngorableIT.java rename to tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/IgnorableIT.java index 7d0b26b3629..5d73e406bf8 100644 --- a/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/IngorableIT.java +++ b/tests/integration/mp-graphql/src/test/java/io/helidon/microprofile/graphql/server/IgnorableIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.

- * To disable automated bean and extension discovery, annotate the class with - * {@link DisableDiscovery}. + * A shorthand to use {@link HelidonTestNgListener} with additional settings. + * + * @see HelidonTestNgListener */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface HelidonTest { /** - * By default, CDI container is created once before the class is initialized and shut down - * after. All test methods run within the same container. - * - * If this is set to {@code true}, a container is created per test method invocation. - * This restricts the test in the following way: - * 1. No injection into fields - * 2. No injection into constructor + * Forces the CDI container to be initialized and shutdown for each test method. * - * @return whether to reset container per test method + * @return whether to reset per test method */ boolean resetPerTest() default false; diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestDescriptorImpl.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestDescriptorImpl.java new file mode 100644 index 00000000000..c066ce579ee --- /dev/null +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestDescriptorImpl.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.testng; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import io.helidon.microprofile.testing.HelidonTestDescriptorBase; + +import static io.helidon.microprofile.testing.Proxies.mirror; + +/** + * Base descriptor implementation that supports the deprecated annotations. + */ +@SuppressWarnings("deprecation") +class HelidonTestDescriptorImpl extends HelidonTestDescriptorBase { + + HelidonTestDescriptorImpl(T element) { + super(element); + } + + @Override + protected List lookupAddBeans() { + return lookup(io.helidon.microprofile.testing.AddBean.class, super.lookupAddBeans().stream(), + AddBean.class, AddBeans.class, AddBeans::value).toList(); + } + + @Override + protected List lookupAddConfigs() { + return lookup(io.helidon.microprofile.testing.AddConfig.class, super.lookupAddConfigs().stream(), + AddConfig.class, AddConfigs.class, AddConfigs::value).toList(); + } + + @Override + protected List lookupAddConfigBlocks() { + return Stream.concat(super.lookupAddConfigBlocks().stream(), annotations(AddConfigBlock.class) + .map(a -> mirror(io.helidon.microprofile.testing.AddConfigBlock.class, a))) + .toList(); + } + + @Override + protected List lookupAddExtensions() { + return lookup(io.helidon.microprofile.testing.AddExtension.class, super.lookupAddExtensions().stream(), + AddExtension.class, AddExtensions.class, AddExtensions::value).toList(); + } + + @Override + protected Optional lookupConfiguration() { + return super.lookupConfiguration().or(() -> annotations(Configuration.class) + .map(a -> mirror(io.helidon.microprofile.testing.Configuration.class, a)) + .findFirst()); + } + + @Override + protected boolean lookupAddJaxRs() { + return super.lookupAddJaxRs() || annotations(AddJaxRs.class) + .findFirst() + .isPresent(); + } + + @Override + protected boolean lookupDisableDiscovery() { + return super.lookupDisableDiscovery() || annotations(DisableDiscovery.class) + .findFirst() + .map(DisableDiscovery::value) + .orElse(false); + } + + @Override + protected boolean lookupResetPerTest() { + return annotations(HelidonTest.class) + .findFirst() + .map(HelidonTest::resetPerTest) + .orElse(false); + } + + @Override + protected boolean lookupPinningDetection() { + return annotations(HelidonTest.class) + .findFirst() + .map(HelidonTest::pinningDetection) + .orElse(false); + } + + @Override + public long pinningThreshold() { + return annotations(HelidonTest.class) + .findFirst() + .map(HelidonTest::pinningThreshold) + .orElse(20L); + } + + private Stream lookup(Class tType, + Stream initial, + Class aType, + Class cType, + Function function) { + + return Stream.concat(initial, annotations(aType, cType, function) + .map(a -> mirror(tType, a))); + } +} diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestExtensionImpl.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestExtensionImpl.java new file mode 100644 index 00000000000..62c1c057c80 --- /dev/null +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestExtensionImpl.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * http://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 io.helidon.microprofile.testing.testng; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.helidon.microprofile.testing.HelidonTestExtension; +import io.helidon.microprofile.testing.HelidonTestInfo; +import io.helidon.microprofile.testing.HelidonTestScope; + +import static io.helidon.microprofile.testing.Proxies.mirror; + +/** + * An implementation of {@link io.helidon.microprofile.testing.HelidonTestExtension} that supports the deprecated annotations. + */ +@SuppressWarnings("deprecation") +final class HelidonTestExtensionImpl extends HelidonTestExtension { + + private static final Set> TYPE_ANNOTATION_TYPES = Set.of( + AddConfig.class, + AddConfigs.class, + AddConfigBlock.class, + Configuration.class); + + private static final Set> METHOD_ANNOTATION_TYPES = Set.of( + AddConfig.class, + AddConfigs.class, + AddConfigBlock.class, + Configuration.class); + + private final Set> typeAnnotationTypes; + private final Set> methodAnnotationTypes; + + HelidonTestExtensionImpl(HelidonTestInfo testInfo, HelidonTestScope testScope) { + super(testInfo, testScope); + this.typeAnnotationTypes = concat(super.typeAnnotationTypes(), TYPE_ANNOTATION_TYPES); + this.methodAnnotationTypes = concat(super.methodAnnotationTypes(), METHOD_ANNOTATION_TYPES); + } + + @Override + protected Set> typeAnnotationTypes() { + return typeAnnotationTypes; + } + + @Override + protected Set> methodAnnotationTypes() { + return methodAnnotationTypes; + } + + @Override + protected void processTypeAnnotation(Annotation annotation) { + switch (annotation) { + case Configuration e -> processConfiguration(e); + case AddConfig e -> processAddConfig(e); + case AddConfigs e -> processAddConfig(e.value()); + case AddConfigBlock e -> processAddConfigBlock(e); + default -> super.processTypeAnnotation(annotation); + } + } + + @Override + protected void processTestMethodAnnotation(Annotation annotation, Method method) { + switch (annotation) { + case Configuration e -> processConfiguration(e); + case AddConfig e -> processAddConfig(e); + case AddConfigs e -> processAddConfig(e.value()); + case AddConfigBlock e -> processAddConfigBlock(e); + default -> super.processTestMethodAnnotation(annotation, method); + } + } + + private void processConfiguration(Configuration annotation) { + processConfiguration(mirror(io.helidon.microprofile.testing.Configuration.class, annotation)); + } + + private void processAddConfigBlock(AddConfigBlock annotation) { + processAddConfigBlock(mirror(io.helidon.microprofile.testing.AddConfigBlock.class, annotation)); + } + + private void processAddConfig(AddConfig... annotations) { + for (AddConfig annotation : annotations) { + processAddConfig(mirror(io.helidon.microprofile.testing.AddConfig.class, annotation)); + } + } + + private static Set> concat(Set> set1, + Set> set2) { + + return Stream.concat(set1.stream(), set2.stream()).collect(Collectors.toSet()); + } +} diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListener.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListener.java index 27f6049ec89..de31a9db473 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListener.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,663 +16,207 @@ package io.helidon.microprofile.testing.testng; -import java.io.IOException; -import java.io.StringReader; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.net.URL; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; - -import io.helidon.common.testing.virtualthreads.PinningRecorder; -import io.helidon.config.mp.MpConfigSources; -import io.helidon.microprofile.server.JaxRsCdiExtension; -import io.helidon.microprofile.server.ServerCdiExtension; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.Dependent; -import jakarta.enterprise.context.RequestScoped; -import jakarta.enterprise.context.spi.CreationalContext; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.se.SeContainer; -import jakarta.enterprise.inject.se.SeContainerInitializer; -import jakarta.enterprise.inject.spi.AfterBeanDiscovery; -import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; -import jakarta.enterprise.inject.spi.CDI; -import jakarta.enterprise.inject.spi.Extension; -import jakarta.enterprise.inject.spi.InjectionPoint; -import jakarta.enterprise.inject.spi.InjectionTarget; -import jakarta.enterprise.inject.spi.ProcessInjectionTarget; -import jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator; -import jakarta.enterprise.util.AnnotationLiteral; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import jakarta.ws.rs.client.ClientBuilder; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.spi.ConfigBuilder; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; -import org.testng.IClassListener; -import org.testng.ITestClass; +import java.util.concurrent.Semaphore; + +import io.helidon.microprofile.testing.HelidonTestContainer; +import io.helidon.microprofile.testing.HelidonTestInfo; +import io.helidon.microprofile.testing.HelidonTestInfo.ClassInfo; +import io.helidon.microprofile.testing.HelidonTestInfo.MethodInfo; +import io.helidon.microprofile.testing.HelidonTestScope; +import io.helidon.microprofile.testing.Proxies; + +import org.testng.IAlterSuiteListener; +import org.testng.ISuite; +import org.testng.ISuiteListener; import org.testng.ITestListener; -import org.testng.ITestResult; -import org.testng.annotations.Test; +import org.testng.ITestNGMethod; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Guice; +import org.testng.xml.XmlClass; +import org.testng.xml.XmlPackage; +import org.testng.xml.XmlSuite; +import org.testng.xml.XmlTest; + +import static io.helidon.microprofile.testing.Instrumented.instrument; +import static io.helidon.microprofile.testing.Instrumented.isInstrumented; +import static io.helidon.microprofile.testing.testng.ClassContext.classInfo; /** - * TestNG extension to support Helidon CDI container in tests. + * A TestNG listener that integrates CDI with TestNG to support Helidon MP. + *