This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch maven-4.0.x in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-4.0.x by this push: new acd66898b5 GH-11055: Inject all services into mojos and enable easy real-session mojo testing (#11103) (#11139) acd66898b5 is described below commit acd66898b5590be7bbac6b8088f4405cbee12a3d Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Mon Sep 22 10:05:03 2025 +0200 GH-11055: Inject all services into mojos and enable easy real-session mojo testing (#11103) (#11139) Highlights - All services are injectable into mojos - Add InternalSession#getAllServices() to expose all DI service suppliers for the session (Map<Class<? extends Service>, Supplier<? extends Service>>) - Add Injector#bindSupplier(Class<T>, Supplier<T>) to bind a type directly to a Supplier - Adjust DI bootstrap/bindings (Injector/Binding/InjectorImpl, SisuDiBridgeModule) and touch minor call sites to respect the wiring - Mojos can be easily tested with a real session - @MojoTest(realSession=true) supported by MojoExtension; creates a real InternalSession via ApiRunner, otherwise uses SessionMock - New MojoRealSessionTest covers default/custom mock vs real-session paths Details - SessionScope alignment: when @Typed is empty, include only the class’s direct interfaces (not super-interfaces) - Testing support: SecDispatcherProvider for encrypted password handling without Sonatype dispatcher; expanded MojoTest for evaluator coverage - IT: add gh-11055-di-service-injection verifying DI service injection end-to-end with pre-populated resources under its/core-it-suite; integrated into TestSuiteOrdering (cherry picked from commit 6b7c9f2bd6a9c49a9e23508a96da0c4031bab74d) --- .../internal/impl/DefaultArtifactManager.java | 9 +- .../maven/internal/impl/DefaultProjectManager.java | 8 +- .../maven/internal/impl/SisuDiBridgeModule.java | 19 +++- .../maven/plugin/DefaultBuildPluginManager.java | 7 ++ .../plugin/internal/DefaultMavenPluginManager.java | 6 ++ .../main/java/org/apache/maven/di/Injector.java | 15 +++ .../java/org/apache/maven/di/impl/Binding.java | 23 +++++ .../org/apache/maven/di/impl/InjectorImpl.java | 11 +++ .../org/apache/maven/impl/AbstractSession.java | 47 +++++++++ .../org/apache/maven/impl/InternalSession.java | 11 +++ .../org/apache/maven/impl/di/SessionScope.java | 44 ++++----- .../org/apache/maven/impl/di/SessionScopeTest.java | 87 ++++++++++++++++ impl/maven-testing/pom.xml | 12 +-- .../maven/api/plugin/testing/MojoExtension.java | 13 +++ .../apache/maven/api/plugin/testing/MojoTest.java | 8 +- .../api/plugin/testing/SecDispatcherProvider.java | 85 ++++++++++++++++ .../api/plugin/testing/MojoRealSessionTest.java | 110 +++++++++++++++++++++ .../it/MavenITgh11055DIServiceInjectionTest.java | 46 +++++++++ .../org/apache/maven/it/TestSuiteOrdering.java | 1 + .../gh-11055-di-service-injection/pom.xml | 101 +++++++++++++++++++ .../src/it/inject-service/.mvn/maven.config | 1 + .../src/it/inject-service/pom.xml | 50 ++++++++++ .../src/it/settings.xml | 35 +++++++ .../com/gitlab/tkslaw/ditests/DITestsMojoBase.java | 47 +++++++++ .../gitlab/tkslaw/ditests/InjectServiceMojo.java | 52 ++++++++++ .../tkslaw/ditests/InjectServiceMojoTests.java | 54 ++++++++++ .../com/gitlab/tkslaw/ditests/TestProviders.java | 76 ++++++++++++++ 27 files changed, 945 insertions(+), 33 deletions(-) diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultArtifactManager.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultArtifactManager.java index 0f20222060..2b8692a86c 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultArtifactManager.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultArtifactManager.java @@ -30,17 +30,24 @@ import org.apache.maven.api.Artifact; import org.apache.maven.api.ProducedArtifact; +import org.apache.maven.api.Service; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.di.SessionScoped; import org.apache.maven.api.services.ArtifactManager; import org.apache.maven.impl.DefaultArtifact; +import org.apache.maven.impl.InternalSession; import org.apache.maven.project.MavenProject; import org.eclipse.sisu.Typed; import static java.util.Objects.requireNonNull; +/** + * This implementation of {@code ArtifactManager} is explicitly bound to + * both {@code ArtifactManager} and {@code Service} interfaces so that it can be retrieved using + * {@link InternalSession#getAllServices()}. + */ @Named -@Typed +@Typed({ArtifactManager.class, Service.class}) @SessionScoped public class DefaultArtifactManager implements ArtifactManager { diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java index c7d7505724..7c495a7344 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java @@ -38,6 +38,7 @@ import org.apache.maven.api.Project; import org.apache.maven.api.ProjectScope; import org.apache.maven.api.RemoteRepository; +import org.apache.maven.api.Service; import org.apache.maven.api.SourceRoot; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.di.SessionScoped; @@ -52,8 +53,13 @@ import static java.util.Objects.requireNonNull; import static org.apache.maven.internal.impl.CoreUtils.map; +/** + * This implementation of {@code ProjectManager} is explicitly bound to + * both {@code ProjectManager} and {@code Service} interfaces so that it can be retrieved using + * {@link InternalSession#getAllServices()}. + */ @Named -@Typed +@Typed({ProjectManager.class, Service.class}) @SessionScoped public class DefaultProjectManager implements ProjectManager { diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java index c1e020b3ed..eeb6215f93 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java @@ -25,6 +25,7 @@ import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -127,7 +128,7 @@ private static <U> com.google.inject.Key<U> toGuiceKey(Key<U> key) { } else if (key.getQualifier() instanceof Annotation a) { return (com.google.inject.Key<U>) com.google.inject.Key.get(key.getType(), a); } else { - return (com.google.inject.Key<U>) com.google.inject.Key.get(key.getType()); + return (com.google.inject.Key<U>) com.google.inject.Key.get(key.getType(), Named.class); } } @@ -203,6 +204,22 @@ private <Q> Supplier<Q> getBeanSupplier(Dependency<Q> dep, Key<Q> key) { } } + @Override + public <T> Set<Binding<T>> getAllBindings(Class<T> clazz) { + Key<T> key = Key.of(clazz); + Set<Binding<T>> bindings = new HashSet<>(); + Set<Binding<T>> diBindings = super.getBindings(key); + if (diBindings != null) { + bindings.addAll(diBindings); + } + for (var bean : locator.get().locate(toGuiceKey(key))) { + if (isPlexusBean(bean)) { + bindings.add(new BindingToBeanEntry<>(Key.of(bean.getImplementationClass())).toBeanEntry(bean)); + } + } + return bindings; + } + private <Q> Supplier<Q> getListSupplier(Key<Q> key) { Key<Object> elementType = key.getTypeParameter(0); return () -> { diff --git a/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java b/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java index 6d9c76100d..e395d1ed00 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java +++ b/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java @@ -25,8 +25,11 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; import org.apache.maven.api.Project; +import org.apache.maven.api.Service; import org.apache.maven.api.services.MavenException; import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.MojoExecutionEvent; @@ -128,6 +131,10 @@ public void executeMojo(MavenSession session, MojoExecution mojoExecution) scope.seed(org.apache.maven.api.MojoExecution.class, new DefaultMojoExecution(sessionV4, mojoExecution)); if (mojoDescriptor.isV4Api()) { + // For Maven 4 plugins, register a service so that they can be directly injected into plugins + Map<Class<? extends Service>, Supplier<? extends Service>> services = sessionV4.getAllServices(); + services.forEach((itf, svc) -> scope.seed((Class<Service>) itf, (Supplier<Service>) svc)); + org.apache.maven.api.plugin.Mojo mojoV4 = mavenPluginManager.getConfiguredMojo( org.apache.maven.api.plugin.Mojo.class, session, mojoExecution); mojo = new MojoWrapper(mojoV4); diff --git a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java index 7d55730f5d..93c0d5a123 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java +++ b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.zip.ZipEntry; @@ -47,6 +48,7 @@ import org.apache.maven.api.PathScope; import org.apache.maven.api.PathType; import org.apache.maven.api.Project; +import org.apache.maven.api.Service; import org.apache.maven.api.Session; import org.apache.maven.api.plugin.descriptor.Resolution; import org.apache.maven.api.services.DependencyResolver; @@ -565,6 +567,10 @@ private <T> T loadV4Mojo( injector.bindInstance(Project.class, project); injector.bindInstance(org.apache.maven.api.MojoExecution.class, execution); injector.bindInstance(org.apache.maven.api.plugin.Log.class, log); + + Map<Class<? extends Service>, Supplier<? extends Service>> services = sessionV4.getAllServices(); + services.forEach((itf, svc) -> injector.bindSupplier((Class<Service>) itf, (Supplier<Service>) svc)); + mojo = mojoInterface.cast(injector.getInstance( Key.of(mojoDescriptor.getImplementationClass(), mojoDescriptor.getRoleHint()))); diff --git a/impl/maven-di/src/main/java/org/apache/maven/di/Injector.java b/impl/maven-di/src/main/java/org/apache/maven/di/Injector.java index 8a95f0fd6f..e908b8e2fe 100644 --- a/impl/maven-di/src/main/java/org/apache/maven/di/Injector.java +++ b/impl/maven-di/src/main/java/org/apache/maven/di/Injector.java @@ -123,6 +123,21 @@ static Injector create() { @Nonnull <T> Injector bindInstance(@Nonnull Class<T> cls, @Nonnull T instance); + /** + * Binds a specific instance supplier to a class type. + * <p> + * This method allows pre-created instances to be used for injection instead of + * having the injector create new instances. + * + * @param <T> the type of the instance + * @param cls the class to bind to + * @param supplier the supplier to use for injection + * @return this injector instance for method chaining + * @throws NullPointerException if either parameter is null + */ + @Nonnull + <T> Injector bindSupplier(@Nonnull Class<T> cls, @Nonnull Supplier<T> supplier); + /** * Performs field and method injection on an existing instance. * <p> diff --git a/impl/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java b/impl/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java index a5da997d75..4caa7311b8 100644 --- a/impl/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java +++ b/impl/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java @@ -53,6 +53,10 @@ public static <T> Binding<T> toInstance(T instance) { return new BindingToInstance<>(instance); } + public static <T> Binding<T> toSupplier(Supplier<T> supplier) { + return new BindingToSupplier<>(supplier); + } + public static <R> Binding<R> to(Key<R> originalKey, TupleConstructorN<R> constructor, Class<?>[] types) { return Binding.to( originalKey, @@ -168,6 +172,25 @@ public String toString() { } } + public static class BindingToSupplier<T> extends Binding<T> { + final Supplier<T> supplier; + + public BindingToSupplier(Supplier<T> supplier) { + super(null, Collections.emptySet()); + this.supplier = supplier; + } + + @Override + public Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler) { + return supplier; + } + + @Override + public String toString() { + return "BindingToSupplier[" + supplier + "]" + getDependencies(); + } + } + public static class BindingToConstructor<T> extends Binding<T> { final TupleConstructorN<T> constructor; final Dependency<?>[] args; diff --git a/impl/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java b/impl/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java index c3a069bca5..f2963b911b 100644 --- a/impl/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java +++ b/impl/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java @@ -137,6 +137,13 @@ public <U> Injector bindInstance(@Nonnull Class<U> clazz, @Nonnull U instance) { return doBind(key, binding); } + @Override + public <U> Injector bindSupplier(@Nonnull Class<U> clazz, @Nonnull Supplier<U> supplier) { + Key<?> key = Key.of(clazz, ReflectionUtils.qualifierOf(clazz)); + Binding<U> binding = Binding.toSupplier(supplier); + return doBind(key, binding); + } + @Nonnull @Override public Injector bindImplicit(@Nonnull Class<?> clazz) { @@ -195,6 +202,10 @@ public Map<Key<?>, Set<Binding<?>>> getBindings() { return bindings; } + public <T> Set<Binding<T>> getAllBindings(Class<T> clazz) { + return getBindings(Key.of(clazz)); + } + public <Q> Supplier<Q> getCompiledBinding(Dependency<Q> dep) { Key<Q> key = dep.key(); Supplier<Q> originalSupplier = doGetCompiledBinding(dep); diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/AbstractSession.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/AbstractSession.java index 605a36b903..68e2d6b2a7 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/AbstractSession.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/AbstractSession.java @@ -23,17 +23,20 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.maven.api.Artifact; import org.apache.maven.api.ArtifactCoordinates; @@ -96,6 +99,10 @@ import org.apache.maven.api.services.VersionRangeResolver; import org.apache.maven.api.services.VersionResolver; import org.apache.maven.api.services.VersionResolverException; +import org.apache.maven.di.Injector; +import org.apache.maven.di.Key; +import org.apache.maven.di.impl.Binding; +import org.apache.maven.di.impl.InjectorImpl; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; @@ -112,6 +119,7 @@ public abstract class AbstractSession implements InternalSession { protected final RepositorySystem repositorySystem; protected final List<RemoteRepository> repositories; protected final Lookup lookup; + protected final Injector injector; private final Map<Class<? extends Service>, Service> services = new ConcurrentHashMap<>(); private final List<Listener> listeners = new CopyOnWriteArrayList<>(); private final Map<org.eclipse.aether.graph.DependencyNode, Node> allNodes = @@ -138,6 +146,24 @@ public AbstractSession( this.repositorySystem = repositorySystem; this.repositories = getRepositories(repositories, resolverRepositories); this.lookup = lookup; + this.injector = lookup != null ? lookup.lookupOptional(Injector.class).orElse(null) : null; + } + + @SuppressWarnings("unchecked") + private static Stream<Class<? extends Service>> collectServiceInterfaces(Class<?> clazz) { + if (clazz == null) { + return Stream.empty(); + } else if (clazz.isInterface()) { + return Stream.concat( + Service.class.isAssignableFrom(clazz) ? Stream.of((Class<Service>) clazz) : Stream.empty(), + Stream.of(clazz.getInterfaces()).flatMap(AbstractSession::collectServiceInterfaces)) + .filter(itf -> itf != Service.class); + } else { + return Stream.concat( + Stream.of(clazz.getInterfaces()).flatMap(AbstractSession::collectServiceInterfaces), + collectServiceInterfaces(clazz.getSuperclass())) + .filter(itf -> itf != Service.class); + } } @Override @@ -370,6 +396,27 @@ public <T extends Service> T getService(Class<T> clazz) throws NoSuchElementExce return t; } + @Override + public Map<Class<? extends Service>, Supplier<? extends Service>> getAllServices() { + Map<Class<? extends Service>, Supplier<? extends Service>> allServices = new HashMap<>(services.size()); + // In case the injector is known, lazily populate the map to avoid creating all services upfront. + if (injector instanceof InjectorImpl injector) { + Set<Binding<Service>> bindings = injector.getAllBindings(Service.class); + bindings.stream() + .map(Binding::getOriginalKey) + .map(Key::getRawType) + .flatMap(AbstractSession::collectServiceInterfaces) + .distinct() + .forEach(itf -> allServices.put(itf, () -> injector.getInstance(Key.of(itf)))); + } else { + List<Service> services = this.injector.getInstance(new Key<List<Service>>() {}); + for (Service service : services) { + collectServiceInterfaces(service.getClass()).forEach(itf -> allServices.put(itf, () -> service)); + } + } + return allServices; + } + private Service lookup(Class<? extends Service> c) { try { return lookup.lookup(c); diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/InternalSession.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/InternalSession.java index e39836e255..b3ce36d47b 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/InternalSession.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/InternalSession.java @@ -20,7 +20,9 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; import org.apache.maven.api.Artifact; import org.apache.maven.api.ArtifactCoordinates; @@ -30,6 +32,7 @@ import org.apache.maven.api.Node; import org.apache.maven.api.RemoteRepository; import org.apache.maven.api.Repository; +import org.apache.maven.api.Service; import org.apache.maven.api.Session; import org.apache.maven.api.WorkspaceRepository; import org.apache.maven.api.annotations.Nonnull; @@ -138,4 +141,12 @@ List<org.eclipse.aether.graph.Dependency> toDependencies( * @see RequestTraceHelper#enter(Session, Object) For the recommended way to manage traces */ RequestTrace getCurrentTrace(); + + /** + * Retrieves a map of all services. + * + * @see #getService(Class) + */ + @Nonnull + Map<Class<? extends Service>, Supplier<? extends Service>> getAllServices(); } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/di/SessionScope.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/di/SessionScope.java index b47c5acde8..cb8e818ea0 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/di/SessionScope.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/di/SessionScope.java @@ -119,32 +119,32 @@ protected <T> Object dispatch(Key<T> key, Supplier<T> unscoped, Method method, O protected Class<?>[] getInterfaces(Class<?> superType) { if (superType.isInterface()) { return new Class<?>[] {superType}; - } else { - for (Annotation a : superType.getAnnotations()) { - Class<? extends Annotation> annotationType = a.annotationType(); - if (isTypeAnnotation(annotationType)) { - try { - Class<?>[] value = - (Class<?>[]) annotationType.getMethod("value").invoke(a); - if (value.length == 0) { - value = superType.getInterfaces(); - } - List<Class<?>> nonInterfaces = - Stream.of(value).filter(c -> !c.isInterface()).toList(); - if (!nonInterfaces.isEmpty()) { - throw new IllegalArgumentException( - "The Typed annotation must contain only interfaces but the following types are not: " - + nonInterfaces); - } - return value; - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); + } + for (Annotation a : superType.getAnnotations()) { + Class<? extends Annotation> annotationType = a.annotationType(); + if (isTypeAnnotation(annotationType)) { + try { + Class<?>[] value = + (Class<?>[]) annotationType.getMethod("value").invoke(a); + if (value.length == 0) { + // Only direct interfaces implemented by the class + value = superType.getInterfaces(); + } + List<Class<?>> nonInterfaces = + Stream.of(value).filter(c -> !c.isInterface()).toList(); + if (!nonInterfaces.isEmpty()) { + throw new IllegalArgumentException( + "The Typed annotation must contain only interfaces but the following types are not: " + + nonInterfaces); } + return value; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException(e); } } - throw new IllegalArgumentException("The use of session scoped proxies require " - + "a org.eclipse.sisu.Typed or javax.enterprise.inject.Typed annotation"); } + throw new IllegalArgumentException( + "The use of session scoped proxies require a org.apache.maven.api.di.Typed, org.eclipse.sisu.Typed or javax.enterprise.inject.Typed annotation"); } protected boolean isTypeAnnotation(Class<? extends Annotation> annotationType) { diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/di/SessionScopeTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/di/SessionScopeTest.java new file mode 100644 index 0000000000..043770e39d --- /dev/null +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/di/SessionScopeTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.maven.impl.di; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.maven.api.di.Typed; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for SessionScope#getInterfaces behaviour with @Typed and session-scoped proxies. + */ +class SessionScopeTest { + + interface A {} + + interface B extends A {} + + interface C {} + + static class Base implements B {} + + @Typed // no explicit interfaces: should collect all from hierarchy (B, A, C) + static class Impl extends Base implements C {} + + @Typed({C.class}) // explicit interface list + static class ImplExplicit extends Base implements C {} + + static class NoTyped extends Base implements C {} + + static class ExposedSessionScope extends SessionScope { + Class<?>[] interfacesOf(Class<?> type) { + return getInterfaces(type); + } + } + + @Test + void typedWithoutValuesIncludesOnlyDirectInterfaces() { + ExposedSessionScope scope = new ExposedSessionScope(); + Class<?>[] itfs = scope.interfacesOf(Impl.class); + Set<Class<?>> set = Arrays.stream(itfs).collect(Collectors.toSet()); + assertTrue(set.contains(C.class), "Should include only direct interfaces implemented by the class"); + assertFalse(set.contains(B.class), "Should NOT include interfaces from superclass"); + assertFalse(set.contains(A.class), "Should NOT include super-interfaces"); + // Proxy should not include concrete classes + assertFalse(set.contains(Base.class)); + assertFalse(set.contains(Impl.class)); + } + + @Test + void typedWithExplicitValuesRespectsExplicitInterfacesOnly() { + ExposedSessionScope scope = new ExposedSessionScope(); + Class<?>[] itfs = scope.interfacesOf(ImplExplicit.class); + assertArrayEquals(new Class<?>[] {C.class}, itfs, "Only explicitly listed interfaces should be used"); + } + + @Test + void missingTypedAnnotationThrows() { + ExposedSessionScope scope = new ExposedSessionScope(); + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, () -> scope.interfacesOf(NoTyped.class)); + assertTrue(ex.getMessage().contains("Typed")); + } +} diff --git a/impl/maven-testing/pom.xml b/impl/maven-testing/pom.xml index 5e8d31ad02..6bef6bb4b0 100644 --- a/impl/maven-testing/pom.xml +++ b/impl/maven-testing/pom.xml @@ -76,21 +76,19 @@ under the License. <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> + <dependency> + <groupId>com.google.inject</groupId> + <artifactId>guice</artifactId> + <classifier>classes</classifier> + </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> - <optional>true</optional> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> </dependency> - <dependency> - <groupId>com.google.inject</groupId> - <artifactId>guice</artifactId> - <classifier>classes</classifier> - <scope>test</scope> - </dependency> </dependencies> </project> diff --git a/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java b/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java index e06ea7c58e..1a9bfff9c5 100644 --- a/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java +++ b/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java @@ -444,6 +444,19 @@ class Foo { @Singleton @Priority(-10) private InternalSession createSession() { + MojoTest mojoTest = context.getRequiredTestClass().getAnnotation(MojoTest.class); + if (mojoTest != null && mojoTest.realSession()) { + // Try to create a real session using ApiRunner without compile-time dependency + try { + Class<?> apiRunner = Class.forName("org.apache.maven.impl.standalone.ApiRunner"); + Object session = apiRunner.getMethod("createSession").invoke(null); + return (InternalSession) session; + } catch (Throwable t) { + // Explicit request: do not fall back; abort the test with details instead of mocking + throw new org.opentest4j.TestAbortedException( + "@MojoTest(realSession=true) requested but could not create a real session.", t); + } + } return SessionMock.getMockSession(getBasedir()); } diff --git a/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java b/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java index 81ff117de6..16ced3b9e2 100644 --- a/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java +++ b/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java @@ -85,4 +85,10 @@ @Retention(RetentionPolicy.RUNTIME) @ExtendWith(MojoExtension.class) @Target(ElementType.TYPE) -public @interface MojoTest {} +public @interface MojoTest { + /** + * If true, the test harness will provide a real Maven Session created by ApiRunner.createSession(), + * instead of the default mock session. Default is false. + */ + boolean realSession() default false; +} diff --git a/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/SecDispatcherProvider.java b/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/SecDispatcherProvider.java new file mode 100644 index 0000000000..3f7c676f4d --- /dev/null +++ b/impl/maven-testing/src/main/java/org/apache/maven/api/plugin/testing/SecDispatcherProvider.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.maven.api.plugin.testing; + +import java.util.Map; + +import org.apache.maven.api.di.Named; +import org.apache.maven.api.di.Provides; +import org.codehaus.plexus.components.secdispatcher.Cipher; +import org.codehaus.plexus.components.secdispatcher.Dispatcher; +import org.codehaus.plexus.components.secdispatcher.MasterSource; +import org.codehaus.plexus.components.secdispatcher.internal.cipher.AESGCMNoPadding; +import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher; +import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.MasterDispatcher; +import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource; +import org.codehaus.plexus.components.secdispatcher.internal.sources.GpgAgentMasterSource; +import org.codehaus.plexus.components.secdispatcher.internal.sources.PinEntryMasterSource; +import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource; + +/** + * Delegate that offers just the minimal surface needed to decrypt settings. + */ +@SuppressWarnings("unused") +@Named +public class SecDispatcherProvider { + + @Provides + @Named(LegacyDispatcher.NAME) + public static Dispatcher legacyDispatcher() { + return new LegacyDispatcher(); + } + + @Provides + @Named(MasterDispatcher.NAME) + public static Dispatcher masterDispatcher( + Map<String, Cipher> masterCiphers, Map<String, MasterSource> masterSources) { + return new MasterDispatcher(masterCiphers, masterSources); + } + + @Provides + @Named(AESGCMNoPadding.CIPHER_ALG) + public static Cipher aesGcmNoPaddingCipher() { + return new AESGCMNoPadding(); + } + + @Provides + @Named(EnvMasterSource.NAME) + public static MasterSource envMasterSource() { + return new EnvMasterSource(); + } + + @Provides + @Named(GpgAgentMasterSource.NAME) + public static MasterSource gpgAgentMasterSource() { + return new GpgAgentMasterSource(); + } + + @Provides + @Named(PinEntryMasterSource.NAME) + public static MasterSource pinEntryMasterSource() { + return new PinEntryMasterSource(); + } + + @Provides + @Named(SystemPropertyMasterSource.NAME) + public static MasterSource systemPropertyMasterSource() { + return new SystemPropertyMasterSource(); + } +} diff --git a/impl/maven-testing/src/test/java/org/apache/maven/api/plugin/testing/MojoRealSessionTest.java b/impl/maven-testing/src/test/java/org/apache/maven/api/plugin/testing/MojoRealSessionTest.java new file mode 100644 index 0000000000..114a649199 --- /dev/null +++ b/impl/maven-testing/src/test/java/org/apache/maven/api/plugin/testing/MojoRealSessionTest.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.maven.api.plugin.testing; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.maven.api.Session; +import org.apache.maven.api.di.Inject; +import org.apache.maven.api.di.Provides; +import org.apache.maven.api.di.Singleton; +import org.apache.maven.api.plugin.testing.stubs.SessionMock; +import org.apache.maven.impl.standalone.ApiRunner; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for @MojoTest(realSession=...) support. + */ +class MojoRealSessionTest { + + @Nested + @MojoTest + class DefaultMock { + @Inject + Session session; + + @Test + void hasMockSession() { + assertNotNull(session); + assertTrue(org.mockito.Mockito.mockingDetails(session).isMock()); + } + } + + @Nested + @MojoTest(realSession = true) + class RealSession { + @Inject + Session session; + + @Test + void hasRealSession() { + assertNotNull(session); + // Real session must not be a Mockito mock + assertFalse(Mockito.mockingDetails(session).isMock()); + } + } + + @Nested + @MojoTest + class CustomMock { + @Inject + Session session; + + @Provides + @Singleton + static Session createSession() { + return SessionMock.getMockSession("target/local-repo"); + } + + @Test + void hasCustomMockSession() { + assertNotNull(session); + assertTrue(Mockito.mockingDetails(session).isMock()); + } + } + + @Nested + @MojoTest(realSession = true) + class CustomRealOverridesFlag { + @Inject + Session session; + + @Provides + @Singleton + static Session createSession() { + Path basedir = Paths.get(System.getProperty("basedir", "")); + Path localRepoPath = basedir.resolve("target/local-repo"); + // Rely on DI discovery for SecDispatcherProvider to avoid duplicate bindings + return ApiRunner.createSession(null, localRepoPath); + } + + @Test + void customProviderWinsOverFlag() { + assertNotNull(session); + assertFalse(Mockito.mockingDetails(session).isMock()); + } + } +} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11055DIServiceInjectionTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11055DIServiceInjectionTest.java new file mode 100644 index 0000000000..199704bb60 --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11055DIServiceInjectionTest.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.maven.it; + +import java.io.File; + +import org.junit.jupiter.api.Test; + +/** + * This is a test set for <a href="https://github.com/apache/maven/issues/11055">gh-11055</a>. + * + * It reproduces the behavior difference between using Session::getService and field injection via @Inject + * for some core services. + */ +class MavenITgh11055DIServiceInjectionTest extends AbstractMavenIntegrationTestCase { + + MavenITgh11055DIServiceInjectionTest() { + super("[4.0.0-rc-4,)"); + } + + @Test + void testGetServiceSucceeds() throws Exception { + File testDir = extractResources("/gh-11055-di-service-injection"); + + Verifier verifier = newVerifier(testDir.getAbsolutePath()); + verifier.addCliArgument("verify"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + } +} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java index 0f0dcecce7..cc0ee65b99 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java @@ -103,6 +103,7 @@ public TestSuiteOrdering() { * the tests are to finishing. Newer tests are also more likely to fail, so this is * a fail fast technique as well. */ + suite.addTestSuite(MavenITgh11055DIServiceInjectionTest.class); suite.addTestSuite(MavenITgh11084ReactorReaderPreferConsumerPomTest.class); suite.addTestSuite(MavenITgh10312TerminallyDeprecatedMethodInGuiceTest.class); suite.addTestSuite(MavenITgh10937QuotedPipesInMavenOptsTest.class); diff --git a/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/pom.xml b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/pom.xml new file mode 100644 index 0000000000..040b0b29ac --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/pom.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. +--> +<project xmlns="http://maven.apache.org/POM/4.1.0" root="true"> + <modelVersion>4.1.0</modelVersion> + + <groupId>com.gitlab.tkslaw</groupId> + <artifactId>ditests-maven-plugin</artifactId> + <version>0.1.0-SNAPSHOT</version> + <packaging>maven-plugin</packaging> + + <name>ditests-maven-plugin (IT gh-11055)</name> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <mavenVersion>4.0.0-SNAPSHOT</mavenVersion> + <mavenPluginPluginVersion>4.0.0-beta-1</mavenPluginPluginVersion> + <mavenCompilerPluginVersion>3.13.0</mavenCompilerPluginVersion> + <javaVersion>17</javaVersion> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-api-core</artifactId> + <version>${mavenVersion}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-api-di</artifactId> + <version>${mavenVersion}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-api-annotations</artifactId> + <version>${mavenVersion}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-testing</artifactId> + <version>${mavenVersion}</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>${mavenCompilerPluginVersion}</version> + <configuration> + <release>${javaVersion}</release> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-plugin-plugin</artifactId> + <version>${mavenPluginPluginVersion}</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-invoker-plugin</artifactId> + <configuration> + <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo> + <settingsFile>${project.basedir}/src/it/settings.xml</settingsFile> + <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath> + </configuration> + <executions> + <execution> + <id>integration-test</id> + <goals> + <goal>install</goal> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/it/inject-service/.mvn/maven.config b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/it/inject-service/.mvn/maven.config new file mode 100644 index 0000000000..db6119c689 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/it/inject-service/.mvn/maven.config @@ -0,0 +1 @@ +-ntp diff --git a/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/it/inject-service/pom.xml b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/it/inject-service/pom.xml new file mode 100644 index 0000000000..d8a2995bfc --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/it/inject-service/pom.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.1.0" root="true"> + <modelVersion>4.1.0</modelVersion> + + <groupId>com.gitlab.tkslaw</groupId> + <artifactId>inject-service</artifactId> + <version>0.1.0-SNAPSHOT</version> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + <version>3.6.1</version> + <configuration> + <rules> + <requireMavenVersion> + <version>@requiredMavenVersion@</version> + </requireMavenVersion> + </rules> + </configuration> + <executions> + <execution> + <id>enforce</id> + <goals> + <goal>enforce</goal> + </goals> + <phase>validate</phase> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>com.gitlab.tkslaw</groupId> + <artifactId>ditests-maven-plugin</artifactId> + <version>@project.version@</version> + <executions> + <execution> + <id>inject-service</id> + <goals> + <goal>inject-service</goal> + </goals> + <phase>validate</phase> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> diff --git a/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/it/settings.xml b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/it/settings.xml new file mode 100644 index 0000000000..531c1fc24a --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/it/settings.xml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<settings> + <profiles> + <profile> + <id>it-repo</id> + <repositories> + <repository> + <id>local.central</id> + <url>@localRepositoryUrl@</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + </repositories> + <pluginRepositories> + <pluginRepository> + <id>local.central</id> + <url>@localRepositoryUrl@</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </pluginRepository> + </pluginRepositories> + </profile> + </profiles> + <activeProfiles> + <activeProfile>it-repo</activeProfile> + </activeProfiles> +</settings> diff --git a/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/main/java/com/gitlab/tkslaw/ditests/DITestsMojoBase.java b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/main/java/com/gitlab/tkslaw/ditests/DITestsMojoBase.java new file mode 100644 index 0000000000..813d18ddfd --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/main/java/com/gitlab/tkslaw/ditests/DITestsMojoBase.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 com.gitlab.tkslaw.ditests; + +import org.apache.maven.api.Project; +import org.apache.maven.api.Session; +import org.apache.maven.api.di.Inject; +import org.apache.maven.api.plugin.Log; +import org.apache.maven.api.plugin.Mojo; + +public class DITestsMojoBase implements Mojo { + @Inject + protected Log log; + + @Inject + protected Session session; + + @Inject + protected Project project; + + @Override + public void execute() { + log.info(() -> "log = " + log); + log.info(() -> "session = " + session); + log.info(() -> "project = " + project); + } + + protected void logService(String name, Object service) { + log.info(() -> " | %s = %s".formatted(name, service)); + } +} diff --git a/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/main/java/com/gitlab/tkslaw/ditests/InjectServiceMojo.java b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/main/java/com/gitlab/tkslaw/ditests/InjectServiceMojo.java new file mode 100644 index 0000000000..7cebc84ff0 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/main/java/com/gitlab/tkslaw/ditests/InjectServiceMojo.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 com.gitlab.tkslaw.ditests; + +import org.apache.maven.api.di.Inject; +import org.apache.maven.api.plugin.annotations.Mojo; +import org.apache.maven.api.services.ArtifactManager; +import org.apache.maven.api.services.DependencyResolver; +import org.apache.maven.api.services.OsService; +import org.apache.maven.api.services.ToolchainManager; + +@Mojo(name = "inject-service") +public class InjectServiceMojo extends DITestsMojoBase { + @Inject + protected ArtifactManager artifactManager; + + @Inject + protected DependencyResolver dependencyResolver; + + @Inject + protected ToolchainManager toolchainManager; + + @Inject + protected OsService osService; + + @Override + public void execute() { + super.execute(); + + log.info("Logging services injected via @Inject"); + logService("artifactManager", artifactManager); + logService("dependencyResolver", dependencyResolver); + logService("toolchainManager", toolchainManager); + logService("osService", osService); + } +} diff --git a/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/test/java/com/gitlab/tkslaw/ditests/InjectServiceMojoTests.java b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/test/java/com/gitlab/tkslaw/ditests/InjectServiceMojoTests.java new file mode 100644 index 0000000000..0d66702097 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/test/java/com/gitlab/tkslaw/ditests/InjectServiceMojoTests.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 com.gitlab.tkslaw.ditests; + +import org.apache.maven.api.plugin.testing.InjectMojo; +import org.apache.maven.api.plugin.testing.MojoTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@MojoTest +@DisplayName("Test InjectServiceMojo") +class InjectServiceMojoTests { + + @Test + @InjectMojo(goal = "inject-service") + @DisplayName("had its services injected by the DI container") + void testServicesNotNull(InjectServiceMojo mojo) { + // Preconditions + assertAll( + "Log, Session, and/or Project were not injected. This should not happen!", + () -> assertNotNull(mojo.log, "log"), + () -> assertNotNull(mojo.session, "session"), + () -> assertNotNull(mojo.project, "project")); + + // Actual test + assertDoesNotThrow(mojo::execute, "InjectServiceMojo::execute"); + assertAll( + "Services not injected by DI container", + () -> assertNotNull(mojo.artifactManager, "artifactManager"), + () -> assertNotNull(mojo.dependencyResolver, "dependencyResolver"), + () -> assertNotNull(mojo.toolchainManager, "toolchainManager"), + () -> assertNotNull(mojo.osService, "osService")); + } +} diff --git a/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/test/java/com/gitlab/tkslaw/ditests/TestProviders.java b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/test/java/com/gitlab/tkslaw/ditests/TestProviders.java new file mode 100644 index 0000000000..0933a15248 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11055-di-service-injection/src/test/java/com/gitlab/tkslaw/ditests/TestProviders.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 com.gitlab.tkslaw.ditests; + +import java.util.Map; + +import org.apache.maven.api.di.Named; +import org.apache.maven.api.di.Priority; +import org.apache.maven.api.di.Provides; +import org.apache.maven.api.di.Singleton; +import org.apache.maven.api.plugin.testing.stubs.SessionMock; +import org.apache.maven.api.services.DependencyResolver; +import org.apache.maven.api.services.OsService; +import org.apache.maven.api.services.ToolchainManager; +import org.apache.maven.impl.DefaultToolchainManager; +import org.apache.maven.impl.InternalSession; +import org.apache.maven.impl.model.DefaultOsService; +import org.mockito.Mockito; + +import static org.apache.maven.api.plugin.testing.MojoExtension.getBasedir; + +@Named +public class TestProviders { + + @Provides + @Singleton + @SuppressWarnings("unused") + private static InternalSession getMockSession( + DependencyResolver dependencyResolver, ToolchainManager toolchainManager, OsService osService) { + + InternalSession session = SessionMock.getMockSession(getBasedir()); + Mockito.when(session.getService(DependencyResolver.class)).thenReturn(dependencyResolver); + Mockito.when(session.getService(ToolchainManager.class)).thenReturn(toolchainManager); + Mockito.when(session.getService(OsService.class)).thenReturn(osService); + return session; + } + + @Provides + @Priority(100) + @Singleton + @SuppressWarnings("unused") + private static DependencyResolver getMockDependencyResolver() { + return Mockito.mock(DependencyResolver.class); + } + + @Provides + @Singleton + @SuppressWarnings("unused") + private static ToolchainManager getToolchainManager() { + return new DefaultToolchainManager(Map.of()); + } + + @Provides + @Singleton + @Priority(100) + @SuppressWarnings("unused") + private static OsService getOsService() { + return new DefaultOsService(); + } +}