[MPLUGINTESTING-78] Support maven 4 new api (#24)

* Update to maven 4.0.0-alpha-2
* Support for the Path parameters and the plugin Log api
* Remove obsolete <tt> tags from javadoc and fix other javadocs errors
diff --git a/maven-plugin-testing-harness/pom.xml b/maven-plugin-testing-harness/pom.xml
index db78ea9..b9d7347 100644
--- a/maven-plugin-testing-harness/pom.xml
+++ b/maven-plugin-testing-harness/pom.xml
@@ -25,7 +25,7 @@
   <parent>
     <groupId>org.apache.maven.plugin-testing</groupId>
     <artifactId>maven-plugin-testing</artifactId>
-    <version>3.4.0-SNAPSHOT</version>
+    <version>4.0.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>maven-plugin-testing-harness</artifactId>
@@ -36,6 +36,12 @@
     <!-- maven -->
     <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-core</artifactId>
       <version>${mavenVersion}</version>
       <scope>provided</scope>
@@ -60,7 +66,7 @@
     </dependency>
     <dependency>
       <groupId>org.apache.maven</groupId>
-      <artifactId>maven-aether-provider</artifactId>
+      <artifactId>maven-resolver-provider</artifactId>
       <version>${mavenVersion}</version>
       <scope>provided</scope>
     </dependency>
@@ -81,10 +87,21 @@
       <artifactId>plexus-archiver</artifactId>
       <version>4.3.0</version>
     </dependency>
+
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-testing</artifactId>
+      <version>1.1.0</version>
+    </dependency>
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.13.2</version>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>4.6.1</version>
+    </dependency>
   </dependencies>
 </project>
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/ConfigurationException.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/ConfigurationException.java
new file mode 100644
index 0000000..6d2ea58
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/ConfigurationException.java
@@ -0,0 +1,57 @@
+package org.apache.maven.api.plugin.testing;
+
+/*
+ * 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.
+ */
+
+/**
+ * ConfigurationException
+ *
+ * @author jesse
+ */
+public class ConfigurationException
+    extends Exception
+{
+    /** serialVersionUID */
+    static final long serialVersionUID = -6180939638742159065L;
+
+    /**
+     * @param message The detailed message.
+     */
+    public ConfigurationException( String message )
+    {
+        super( message );
+    }
+
+    /**
+     * @param cause The detailed cause.
+     */
+    public ConfigurationException( Throwable cause )
+    {
+        super( cause );
+    }
+
+    /**
+     * @param message The detailed message.
+     * @param cause The detailed cause.
+     */
+    public ConfigurationException( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/InjectMojo.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/InjectMojo.java
new file mode 100644
index 0000000..313dbba
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/InjectMojo.java
@@ -0,0 +1,38 @@
+package org.apache.maven.api.plugin.testing;
+
+/*
+ * 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.
+ */
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ *
+ */
+@Retention( RetentionPolicy.RUNTIME )
+public @interface InjectMojo
+{
+
+    String goal();
+
+    String pom();
+
+    boolean empty() default false;
+
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java
new file mode 100644
index 0000000..358180a
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java
@@ -0,0 +1,502 @@
+package org.apache.maven.api.plugin.testing;
+
+/*
+ * 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.
+ */
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.inject.internal.ProviderMethodsModule;
+import org.apache.maven.api.MojoExecution;
+import org.apache.maven.api.Project;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.plugin.Log;
+import org.apache.maven.api.plugin.Mojo;
+import org.apache.maven.api.xml.Dom;
+import org.apache.maven.configuration.internal.EnhancedComponentConfigurator;
+import org.apache.maven.internal.impl.DefaultLog;
+import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
+import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.Parameter;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
+import org.codehaus.plexus.DefaultPlexusContainer;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.configurator.ComponentConfigurator;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
+import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
+import org.codehaus.plexus.component.repository.ComponentDescriptor;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
+import org.codehaus.plexus.testing.PlexusExtension;
+import org.codehaus.plexus.util.InterpolationFilterReader;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.ReflectionUtils;
+import org.codehaus.plexus.util.xml.XmlStreamReader;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ */
+public class MojoExtension extends PlexusExtension implements ParameterResolver
+{
+
+    @Override
+    public boolean supportsParameter( ParameterContext parameterContext, ExtensionContext extensionContext )
+            throws ParameterResolutionException
+    {
+        return parameterContext.isAnnotated( InjectMojo.class )
+                || parameterContext.getDeclaringExecutable().isAnnotationPresent( InjectMojo.class );
+    }
+
+    @Override
+    public Object resolveParameter( ParameterContext parameterContext, ExtensionContext extensionContext )
+            throws ParameterResolutionException
+    {
+        try
+        {
+            InjectMojo injectMojo = parameterContext.findAnnotation( InjectMojo.class ).orElseGet(
+                    () -> parameterContext.getDeclaringExecutable().getAnnotation( InjectMojo.class ) );
+            List<MojoParameter> mojoParameters = parameterContext.findRepeatableAnnotations( MojoParameter.class );
+            Class<?> holder = parameterContext.getTarget().get().getClass();
+            PluginDescriptor descriptor = extensionContext.getStore( ExtensionContext.Namespace.GLOBAL )
+                    .get( PluginDescriptor.class, PluginDescriptor.class );
+            return lookupMojo( holder, injectMojo, mojoParameters, descriptor );
+        }
+        catch ( Exception e )
+        {
+            throw new ParameterResolutionException( "Unable to resolve parameter", e );
+        }
+    }
+
+    @Override
+    public void beforeEach( ExtensionContext context )
+            throws Exception
+    {
+        Field field = PlexusExtension.class.getDeclaredField( "basedir" );
+        field.setAccessible( true );
+        field.set( null, getBasedir() );
+        field = PlexusExtension.class.getDeclaredField( "context" );
+        field.setAccessible( true );
+        field.set( this, context );
+
+        getContainer().addComponent( getContainer(), PlexusContainer.class.getName() );
+
+        ( (DefaultPlexusContainer) getContainer() ).addPlexusInjector( Collections.emptyList(),
+                binder ->
+                {
+                    binder.install( ProviderMethodsModule.forObject( context.getRequiredTestInstance() ) );
+                    binder.requestInjection( context.getRequiredTestInstance() );
+                    binder.bind( Log.class ).toInstance( new DefaultLog(
+                            LoggerFactory.getLogger( "anonymous" ) ) );
+                } );
+
+        Map<Object, Object> map = getContainer().getContext().getContextData();
+
+        ClassLoader classLoader = context.getRequiredTestClass().getClassLoader();
+        try ( InputStream is = Objects.requireNonNull( classLoader.getResourceAsStream( getPluginDescriptorLocation() ),
+                                        "Unable to find plugin descriptor: " + getPluginDescriptorLocation() );
+              Reader reader = new BufferedReader( new XmlStreamReader( is ) );
+              InterpolationFilterReader interpolationReader = new InterpolationFilterReader( reader, map, "${", "}" ) )
+        {
+
+            PluginDescriptor pluginDescriptor = new PluginDescriptorBuilder().build( interpolationReader );
+
+//            Artifact artifact =
+//                    lookup( RepositorySystem.class ).createArtifact( pluginDescriptor.getGroupId(),
+//                            pluginDescriptor.getArtifactId(),
+//                            pluginDescriptor.getVersion(), ".jar" );
+//
+//            artifact.setFile( getPluginArtifactFile() );
+//            pluginDescriptor.setPluginArtifact( artifact );
+//            pluginDescriptor.setArtifacts( Collections.singletonList( artifact ) );
+
+            context.getStore( ExtensionContext.Namespace.GLOBAL )
+                    .put( PluginDescriptor.class, pluginDescriptor );
+
+            for ( ComponentDescriptor<?> desc : pluginDescriptor.getComponents() )
+            {
+                getContainer().addComponentDescriptor( desc );
+            }
+        }
+    }
+
+    protected String getPluginDescriptorLocation()
+    {
+        return "META-INF/maven/plugin.xml";
+    }
+
+    private Mojo lookupMojo( Class<?> holder, InjectMojo injectMojo, List<MojoParameter> mojoParameters,
+                             PluginDescriptor descriptor ) throws Exception
+    {
+        String goal = injectMojo.goal();
+        String pom = injectMojo.pom();
+        String[] coord = mojoCoordinates( goal );
+        Xpp3Dom pomDom;
+        if ( pom.startsWith( "file:" ) )
+        {
+            Path path = Paths.get( getBasedir() ).resolve( pom.substring( "file:".length() ) );
+            pomDom = Xpp3DomBuilder.build( ReaderFactory.newXmlReader( path.toFile() ) );
+        }
+        else if ( pom.startsWith( "classpath:" ) )
+        {
+            URL url = holder.getResource( pom.substring( "classpath:".length() ) );
+            if ( url == null )
+            {
+                throw new IllegalStateException( "Unable to find pom on classpath: " + pom );
+            }
+            pomDom = Xpp3DomBuilder.build( ReaderFactory.newXmlReader( url.openStream() ) );
+        }
+        else if ( pom.contains( "<project>" ) )
+        {
+            pomDom = Xpp3DomBuilder.build( new StringReader( pom ) );
+        }
+        else
+        {
+            Path path = Paths.get( getBasedir() ).resolve( pom );
+            pomDom = Xpp3DomBuilder.build( ReaderFactory.newXmlReader( path.toFile() ) );
+        }
+        Dom pluginConfiguration = extractPluginConfiguration( coord[1], pomDom );
+        if ( !mojoParameters.isEmpty() )
+        {
+            List<Dom> children = mojoParameters.stream()
+                    .map( mp -> new org.apache.maven.internal.xml.Xpp3Dom( mp.name(), mp.value() ) )
+                    .collect( Collectors.toList() );
+            Dom config = new org.apache.maven.internal.xml.Xpp3Dom( "configuration",
+                    null, null, children, null );
+            pluginConfiguration = Dom.merge( config, pluginConfiguration );
+        }
+        Mojo mojo = lookupMojo( coord, pluginConfiguration, descriptor );
+        return mojo;
+    }
+
+    protected String[] mojoCoordinates( String goal )
+            throws Exception
+    {
+        if ( goal.matches( ".*:.*:.*:.*" ) )
+        {
+            return goal.split( ":" );
+        }
+        else
+        {
+            Path pluginPom = Paths.get( getBasedir(), "pom.xml" );
+            Xpp3Dom pluginPomDom = Xpp3DomBuilder.build( ReaderFactory.newXmlReader( pluginPom.toFile() ) );
+            String artifactId = pluginPomDom.getChild( "artifactId" ).getValue();
+            String groupId = resolveFromRootThenParent( pluginPomDom, "groupId" );
+            String version = resolveFromRootThenParent( pluginPomDom, "version" );
+            return new String[] { groupId, artifactId, version, goal };
+        }
+    }
+
+    /**
+     * lookup the mojo while we have all of the relavent information
+     */
+    protected Mojo lookupMojo( String[] coord, Dom pluginConfiguration, PluginDescriptor descriptor )
+            throws Exception
+    {
+        // pluginkey = groupId : artifactId : version : goal
+        Mojo mojo = lookup( Mojo.class, coord[0] + ":" + coord[1] + ":" + coord[2] + ":" + coord[3] );
+        for ( MojoDescriptor mojoDescriptor : descriptor.getMojos() )
+        {
+            if ( Objects.equals( mojoDescriptor.getImplementation(), mojo.getClass().getName() ) )
+            {
+                if ( pluginConfiguration != null )
+                {
+                    pluginConfiguration = finalizeConfig( pluginConfiguration, mojoDescriptor );
+                }
+            }
+        }
+        if ( pluginConfiguration != null )
+        {
+            Session session = getContainer().lookup( Session.class );
+            Project project;
+            try
+            {
+                project = getContainer().lookup( Project.class );
+            }
+            catch ( ComponentLookupException e )
+            {
+                project = null;
+            }
+            org.apache.maven.plugin.MojoExecution mojoExecution;
+            try
+            {
+                MojoExecution me = getContainer().lookup( MojoExecution.class );
+                mojoExecution = new org.apache.maven.plugin.MojoExecution(
+                        new org.apache.maven.model.Plugin( me.getPlugin() ),
+                        me.getGoal(),
+                        me.getExecutionId()
+                );
+            }
+            catch ( ComponentLookupException e )
+            {
+                mojoExecution = null;
+            }
+            ExpressionEvaluator evaluator = new WrapEvaluator( getContainer(),
+                    new PluginParameterExpressionEvaluatorV4( session, project, mojoExecution ) );
+            ComponentConfigurator configurator = new EnhancedComponentConfigurator();
+            configurator.configureComponent( mojo, new XmlPlexusConfiguration( new Xpp3Dom( pluginConfiguration ) ),
+                    evaluator, getContainer().getContainerRealm() );
+        }
+
+        return mojo;
+    }
+
+    private Dom finalizeConfig( Dom config, MojoDescriptor mojoDescriptor )
+    {
+        List<Dom> children = new ArrayList<>();
+        if ( mojoDescriptor != null && mojoDescriptor.getParameters() != null )
+        {
+            Dom defaultConfiguration = MojoDescriptorCreator.convert( mojoDescriptor ).getDom();
+            for ( Parameter parameter : mojoDescriptor.getParameters() )
+            {
+                Dom parameterConfiguration = config.getChild( parameter.getName() );
+                if ( parameterConfiguration == null )
+                {
+                    parameterConfiguration = config.getChild( parameter.getAlias() );
+                }
+                Dom parameterDefaults = defaultConfiguration.getChild( parameter.getName() );
+                parameterConfiguration = Dom.merge( parameterConfiguration, parameterDefaults, Boolean.TRUE );
+                if ( parameterConfiguration != null )
+                {
+                    Map<String, String> attributes = new HashMap<>( parameterConfiguration.getAttributes() );
+                    if ( isEmpty( parameterConfiguration.getAttribute( "implementation" ) )
+                            && !isEmpty( parameter.getImplementation() ) )
+                    {
+                        attributes.put( "implementation", parameter.getImplementation() );
+                    }
+                    parameterConfiguration = new org.apache.maven.internal.xml.Xpp3Dom(
+                            parameter.getName(), parameterConfiguration.getValue(),
+                            attributes, parameterConfiguration.getChildren(),
+                            parameterConfiguration.getInputLocation() );
+
+                    children.add( parameterConfiguration );
+                }
+            }
+        }
+        return new org.apache.maven.internal.xml.Xpp3Dom( "configuration", null, null, children, null );
+    }
+
+    private boolean isEmpty( String str )
+    {
+        return str == null || str.isEmpty();
+    }
+
+    private static Optional<Xpp3Dom> child( Xpp3Dom element, String name )
+    {
+        return Optional.ofNullable( element.getChild( name ) );
+    }
+
+    private static Stream<Xpp3Dom> children( Xpp3Dom element )
+    {
+        return Stream.of( element.getChildren() );
+    }
+
+    public static Dom extractPluginConfiguration( String artifactId, Xpp3Dom pomDom )
+            throws Exception
+    {
+        Xpp3Dom pluginConfigurationElement = child( pomDom, "build" )
+                .flatMap( buildElement -> child( buildElement, "plugins" ) )
+                .map( MojoExtension::children )
+                .orElseGet( Stream::empty )
+                .filter( e -> e.getChild( "artifactId" ).getValue().equals( artifactId ) )
+                .findFirst()
+                .flatMap( buildElement -> child( buildElement, "configuration" ) )
+                .orElseThrow( () -> new ConfigurationException(
+                        "Cannot find a configuration element for a plugin with an "
+                        + "artifactId of " + artifactId + "." ) );
+        return pluginConfigurationElement.getDom();
+    }
+
+    /**
+     * sometimes the parent element might contain the correct value so generalize that access
+     *
+     * TODO find out where this is probably done elsewhere
+     */
+    private static String resolveFromRootThenParent( Xpp3Dom pluginPomDom, String element )
+            throws Exception
+    {
+        return Optional.ofNullable(
+                    child( pluginPomDom, element )
+                        .orElseGet( () -> child( pluginPomDom, "parent" )
+                            .flatMap( e -> child( e, element ) )
+                            .orElse( null ) ) )
+                .map( Xpp3Dom::getValue )
+                .orElseThrow( () -> new Exception( "unable to determine " + element ) );
+    }
+
+
+    /**
+     * Convenience method to obtain the value of a variable on a mojo that might not have a getter.
+     *
+     * NOTE: the caller is responsible for casting to to what the desired type is.
+     *
+     * @param object
+     * @param variable
+     * @return object value of variable
+     * @throws IllegalArgumentException
+     */
+    public static Object getVariableValueFromObject( Object object, String variable )
+            throws IllegalAccessException
+    {
+        Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses( variable, object.getClass() );
+        field.setAccessible( true );
+        return field.get( object );
+    }
+
+    /**
+     * Convenience method to obtain all variables and values from the mojo (including its superclasses)
+     *
+     * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
+     *
+     * @param object
+     * @return map of variable names and values
+     */
+    public static Map<String, Object> getVariablesAndValuesFromObject( Object object )
+            throws IllegalAccessException
+    {
+        return getVariablesAndValuesFromObject( object.getClass(), object );
+    }
+
+    /**
+     * Convenience method to obtain all variables and values from the mojo (including its superclasses)
+     *
+     * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
+     *
+     * @param clazz
+     * @param object
+     * @return map of variable names and values
+     */
+    public static Map<String, Object> getVariablesAndValuesFromObject( Class<?> clazz, Object object )
+            throws IllegalAccessException
+    {
+        Map<String, Object> map = new HashMap<>();
+        Field[] fields = clazz.getDeclaredFields();
+        AccessibleObject.setAccessible( fields, true );
+        for ( Field field : fields )
+        {
+            map.put( field.getName(), field.get( object ) );
+        }
+        Class<?> superclass = clazz.getSuperclass();
+        if ( !Object.class.equals( superclass ) )
+        {
+            map.putAll( getVariablesAndValuesFromObject( superclass, object ) );
+        }
+        return map;
+    }
+
+    /**
+     * Convenience method to set values to variables in objects that don't have setters
+     *
+     * @param object
+     * @param variable
+     * @param value
+     * @throws IllegalAccessException
+     */
+    public static void setVariableValueToObject( Object object, String variable, Object value )
+            throws IllegalAccessException
+    {
+        Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses( variable, object.getClass() );
+        Objects.requireNonNull( field, "Field " + variable + " not found" );
+        field.setAccessible( true );
+        field.set( object, value );
+    }
+
+    static class WrapEvaluator implements TypeAwareExpressionEvaluator
+    {
+
+        private final PlexusContainer container;
+        private final TypeAwareExpressionEvaluator evaluator;
+
+        WrapEvaluator( PlexusContainer container, TypeAwareExpressionEvaluator evaluator )
+        {
+            this.container = container;
+            this.evaluator = evaluator;
+        }
+
+        @Override
+        public Object evaluate( String expression ) throws ExpressionEvaluationException
+        {
+            return evaluate( expression, null );
+        }
+
+        @Override
+        public Object evaluate( String expression, Class<?> type ) throws ExpressionEvaluationException
+        {
+            Object value = evaluator.evaluate( expression, type );
+            if ( value == null )
+            {
+                String expr = stripTokens( expression );
+                if ( expr != null )
+                {
+                    try
+                    {
+                        value = container.lookup( type, expr );
+                    }
+                    catch ( ComponentLookupException e )
+                    {
+                        // nothing
+                    }
+                }
+            }
+            return value;
+        }
+
+        private String stripTokens( String expr )
+        {
+            if ( expr.startsWith( "${" ) && expr.endsWith( "}" ) )
+            {
+                return expr.substring( 2, expr.length() - 1 );
+            }
+            return null;
+        }
+
+        @Override
+        public File alignToBaseDirectory( File path )
+        {
+            return evaluator.alignToBaseDirectory( path );
+        }
+    }
+
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameter.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameter.java
new file mode 100644
index 0000000..ebd284d
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameter.java
@@ -0,0 +1,35 @@
+package org.apache.maven.api.plugin.testing;
+
+/*
+ * 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.
+ */
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Mojo parameter
+ */
+@Retention( RetentionPolicy.RUNTIME )
+@Repeatable( MojoParameters.class )
+public @interface MojoParameter
+{
+    String name();
+    String value();
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameters.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameters.java
new file mode 100644
index 0000000..53c8dd9
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameters.java
@@ -0,0 +1,32 @@
+package org.apache.maven.api.plugin.testing;
+
+/*
+ * 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.
+ */
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Mojo parameters container
+ */
+@Retention( RetentionPolicy.RUNTIME )
+public @interface MojoParameters
+{
+    MojoParameter[] value();
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java
new file mode 100644
index 0000000..4f4b1a9
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoTest.java
@@ -0,0 +1,37 @@
+package org.apache.maven.api.plugin.testing;
+
+/*
+ * 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.
+ */
+
+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;
+
+/**
+ *
+ */
+@Retention( RetentionPolicy.RUNTIME )
+@ExtendWith( MojoExtension.class )
+@Target( ElementType.TYPE )
+public @interface MojoTest
+{
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/ResolverExpressionEvaluatorStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/ResolverExpressionEvaluatorStub.java
new file mode 100644
index 0000000..b35b2b0
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/ResolverExpressionEvaluatorStub.java
@@ -0,0 +1,154 @@
+package org.apache.maven.api.plugin.testing;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.util.Map;
+
+import org.codehaus.plexus.PlexusTestCase;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
+import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
+import org.codehaus.plexus.testing.PlexusExtension;
+import org.eclipse.aether.repository.LocalRepository;
+
+/**
+ * Stub for {@link ExpressionEvaluator}
+ *
+ * @author jesse
+ */
+public class ResolverExpressionEvaluatorStub
+    implements TypeAwareExpressionEvaluator
+{
+
+    private final Map<String, Object> properties;
+
+    public ResolverExpressionEvaluatorStub( Map<String, Object> properties )
+    {
+        this.properties = properties;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Object evaluate( String expr )
+            throws ExpressionEvaluationException
+    {
+        return evaluate( expr, null );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Object evaluate( String expr, Class<?> type )
+        throws ExpressionEvaluationException
+    {
+
+        Object value = null;
+
+        if ( expr == null )
+        {
+            return null;
+        }
+
+        String expression = stripTokens( expr );
+
+        if ( expression.equals( expr ) )
+        {
+            int index = expr.indexOf( "${" );
+            if ( index >= 0 )
+            {
+                int lastIndex = expr.indexOf( "}", index );
+                if ( lastIndex >= 0 )
+                {
+                    String retVal = expr.substring( 0, index );
+
+                    if ( index > 0 && expr.charAt( index - 1 ) == '$' )
+                    {
+                        retVal += expr.substring( index + 1, lastIndex + 1 );
+                    }
+                    else
+                    {
+                        retVal += evaluate( expr.substring( index, lastIndex + 1 ) );
+                    }
+
+                    retVal += evaluate( expr.substring( lastIndex + 1 ) );
+                    return retVal;
+                }
+            }
+
+            // Was not an expression
+            return expression.contains( "$$" )
+                    ? expression.replaceAll( "\\$\\$", "\\$" )
+                    : expression;
+        }
+        else
+        {
+            if ( "basedir".equals( expression ) || "project.basedir".equals( expression ) )
+            {
+                value = PlexusExtension.getBasedir();
+            }
+            else if ( expression.startsWith( "basedir" ) || expression.startsWith( "project.basedir" ) )
+            {
+                int pathSeparator = expression.indexOf( "/" );
+                if ( pathSeparator > 0 )
+                {
+                    value = PlexusTestCase.getBasedir() + expression.substring( pathSeparator );
+                }
+            }
+            else if ( "localRepository".equals( expression ) )
+            {
+                File localRepo = new File( PlexusTestCase.getBasedir(), "target/local-repo" );
+                return new LocalRepository( "file://" + localRepo.getAbsolutePath() );
+            }
+            if ( value == null && properties != null && properties.containsKey( expression ) )
+            {
+                value = properties.get( expression );
+            }
+            return value;
+        }
+    }
+
+    private String stripTokens( String expr )
+    {
+        if ( expr.startsWith( "${" ) && expr.indexOf( "}" ) == expr.length() - 1 )
+        {
+            expr = expr.substring( 2, expr.length() - 1 );
+        }
+
+        return expr;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public File alignToBaseDirectory( File file )
+    {
+        if ( file.getAbsolutePath().startsWith( PlexusExtension.getBasedir() ) )
+        {
+            return file;
+        }
+        else if ( file.isAbsolute() )
+        {
+            return file;
+        }
+        else
+        {
+            return new File( PlexusExtension.getBasedir(), file.getPath() );
+        }
+    }
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/ArtifactStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/ArtifactStub.java
new file mode 100644
index 0000000..70b7d89
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/ArtifactStub.java
@@ -0,0 +1,200 @@
+package org.apache.maven.api.plugin.testing.stubs;
+
+/*
+ * 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.
+ */
+
+import java.util.Objects;
+
+import org.apache.maven.api.Artifact;
+import org.apache.maven.api.ArtifactCoordinate;
+import org.apache.maven.api.Version;
+import org.apache.maven.api.VersionRange;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.internal.impl.DefaultVersionParser;
+
+/**
+ *
+ */
+public class ArtifactStub implements Artifact
+{
+    private String groupId;
+    private String artifactId;
+    private String classifier;
+    private String version;
+    private String extension;
+
+    public ArtifactStub()
+    {
+        groupId = "";
+        artifactId = "";
+        version = "";
+        classifier = "";
+        extension = "";
+    }
+
+    public ArtifactStub( String groupId, String artifactId, String classifier, String version, String extension )
+    {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.classifier = classifier;
+        this.version = version;
+        this.extension = extension;
+    }
+
+    @Nonnull
+    @Override
+    public String getGroupId()
+    {
+        return groupId;
+    }
+
+    public void setGroupId( String groupId )
+    {
+        this.groupId = groupId;
+    }
+
+    @Nonnull
+    @Override
+    public String getArtifactId()
+    {
+        return artifactId;
+    }
+
+    public void setArtifactId( String artifactId )
+    {
+        this.artifactId = artifactId;
+    }
+
+    @Nonnull
+    @Override
+    public String getClassifier()
+    {
+        return classifier;
+    }
+
+    public void setClassifier( String classifier )
+    {
+        this.classifier = classifier;
+    }
+
+    @Nonnull
+    @Override
+    public Version getVersion()
+    {
+        return new DefaultVersionParser().parseVersion( version );
+    }
+
+    public void setVersion( String version )
+    {
+        this.version = version;
+    }
+
+    @Nonnull
+    @Override
+    public String getExtension()
+    {
+        return extension;
+    }
+
+    public void setExtension( String extension )
+    {
+        this.extension = extension;
+    }
+
+    @Override
+    public boolean isSnapshot()
+    {
+        return false;
+    }
+
+    @Override
+    public ArtifactCoordinate toCoordinate()
+    {
+        return new ArtifactCoordinate()
+        {
+            @Override
+            public String getGroupId()
+            {
+                return groupId;
+            }
+
+            @Override
+            public String getArtifactId()
+            {
+                return artifactId;
+            }
+
+            @Override
+            public String getClassifier()
+            {
+                return classifier;
+            }
+
+            @Override
+            public VersionRange getVersion()
+            {
+                return new DefaultVersionParser().parseVersionRange( version );
+            }
+
+            @Override
+            public String getExtension()
+            {
+                return extension;
+            }
+        };
+    }
+
+    @Override
+    public String toString()
+    {
+        return "ArtifactStub["
+                + "groupId='" + groupId + '\''
+                + ", artifactId='" + artifactId + '\''
+                + ", classifier='" + classifier + '\''
+                + ", version='" + version + '\''
+                + ", extension='" + extension + '\''
+                + ']';
+    }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( !( o instanceof ArtifactStub ) )
+        {
+            return false;
+        }
+        ArtifactStub that = ( ArtifactStub ) o;
+        return Objects.equals( groupId, that.groupId )
+                && Objects.equals( artifactId, that.artifactId )
+                && Objects.equals( classifier, that.classifier )
+                && Objects.equals( version, that.version )
+                && Objects.equals( extension, that.extension );
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hash( groupId, artifactId, classifier, version, extension );
+    }
+
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/MojoExecutionStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/MojoExecutionStub.java
new file mode 100644
index 0000000..0e01bfc
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/MojoExecutionStub.java
@@ -0,0 +1,76 @@
+package org.apache.maven.api.plugin.testing.stubs;
+
+/*
+ * 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.
+ */
+
+import java.util.Optional;
+
+import org.apache.maven.api.MojoExecution;
+import org.apache.maven.api.model.Plugin;
+import org.apache.maven.api.xml.Dom;
+
+/**
+ * Stub for {@link MojoExecution}.
+ */
+public class MojoExecutionStub implements MojoExecution
+{
+    private final String artifactId;
+    private final String executionId;
+    private final String goal;
+    private final Dom dom;
+
+    public MojoExecutionStub( String artifactId, String executionId, String goal )
+    {
+        this( artifactId, executionId, goal, null );
+    }
+
+    public MojoExecutionStub( String artifactId, String executionId, String goal, Dom dom )
+    {
+        this.artifactId = artifactId;
+        this.executionId = executionId;
+        this.goal = goal;
+        this.dom = dom;
+    }
+
+    @Override
+    public Plugin getPlugin()
+    {
+        return Plugin.newBuilder()
+                .artifactId( artifactId )
+                .build();
+    }
+
+    @Override
+    public String getExecutionId()
+    {
+        return executionId;
+    }
+
+    @Override
+    public String getGoal()
+    {
+        return goal;
+    }
+
+    @Override
+    public Optional<Dom> getConfiguration()
+    {
+        return Optional.ofNullable( dom );
+    }
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/ProjectStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/ProjectStub.java
new file mode 100644
index 0000000..283959a
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/ProjectStub.java
@@ -0,0 +1,202 @@
+package org.apache.maven.api.plugin.testing.stubs;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.maven.api.Artifact;
+import org.apache.maven.api.DependencyCoordinate;
+import org.apache.maven.api.Project;
+import org.apache.maven.api.RemoteRepository;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.model.Model;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.0-beta-1
+ *
+ */
+public class ProjectStub
+        implements Project
+{
+
+    private Model model = Model.newInstance();
+    private Path basedir;
+    private File pomPath;
+    private boolean executionRoot;
+    private Artifact artifact;
+
+    public void setModel( Model model )
+    {
+        this.model = model;
+    }
+
+    @Nonnull
+    @Override
+    public String getGroupId()
+    {
+        return model.getGroupId();
+    }
+
+    @Nonnull
+    @Override
+    public String getArtifactId()
+    {
+        return model.getArtifactId();
+    }
+
+    @Nonnull
+    @Override
+    public String getVersion()
+    {
+        return model.getVersion();
+    }
+
+    public String getName()
+    {
+        return model.getName();
+    }
+
+    @Nonnull
+    @Override
+    public String getPackaging()
+    {
+        return model.getPackaging();
+    }
+
+    @Nonnull
+    @Override
+    public Artifact getArtifact()
+    {
+        return artifact;
+    }
+
+    @Nonnull
+    @Override
+    public Model getModel()
+    {
+        return model;
+    }
+
+    @Nonnull
+    @Override
+    public Optional<Path> getPomPath()
+    {
+        return Optional.ofNullable( pomPath ).map( File::toPath );
+    }
+
+    @Nonnull
+    @Override
+    public List<DependencyCoordinate> getDependencies()
+    {
+        return null;
+    }
+
+    @Nonnull
+    @Override
+    public List<DependencyCoordinate> getManagedDependencies()
+    {
+        return null;
+    }
+
+    @Override
+    public Optional<Path> getBasedir()
+    {
+        return Optional.ofNullable( basedir );
+    }
+
+    public void setBasedir( Path basedir )
+    {
+        this.basedir = basedir;
+    }
+
+    @Override
+    public boolean isExecutionRoot()
+    {
+        return executionRoot;
+    }
+
+    @Override
+    public Optional<Project> getParent()
+    {
+        return Optional.empty();
+    }
+
+    @Override
+    public List<RemoteRepository> getRemoteProjectRepositories()
+    {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<RemoteRepository> getRemotePluginRepositories()
+    {
+        return Collections.emptyList();
+    }
+
+    public void setGroupId( String groupId )
+    {
+        model = model.withGroupId( groupId );
+    }
+
+    public void setArtifactId( String artifactId )
+    {
+        model = model.withArtifactId( artifactId );
+    }
+
+    public void setVersion( String version )
+    {
+        model = model.withVersion( version );
+    }
+
+    public void setName( String name )
+    {
+        model = model.withName( name );
+    }
+
+    public void setPackaging( String packaging )
+    {
+        model = model.withPackaging( packaging );
+    }
+
+    public void setArtifact( Artifact artifact )
+    {
+        this.artifact = artifact;
+    }
+
+    public void setPomPath( File pomPath )
+    {
+        this.pomPath = pomPath;
+    }
+
+    public void setExecutionRoot( boolean executionRoot )
+    {
+        this.executionRoot = executionRoot;
+    }
+
+    public void setMavenModel( org.apache.maven.model.Model model )
+    {
+        this.model = model.getDelegate();
+    }
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/SessionStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/SessionStub.java
new file mode 100644
index 0000000..c6d2305
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/SessionStub.java
@@ -0,0 +1,299 @@
+package org.apache.maven.api.plugin.testing.stubs;
+
+/*
+ * 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.
+ */
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+
+import org.apache.maven.api.Artifact;
+import org.apache.maven.api.LocalRepository;
+import org.apache.maven.api.Project;
+import org.apache.maven.api.RemoteRepository;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Repository;
+import org.apache.maven.api.services.ArtifactDeployer;
+import org.apache.maven.api.services.ArtifactDeployerRequest;
+import org.apache.maven.api.services.ArtifactFactory;
+import org.apache.maven.api.services.ArtifactFactoryRequest;
+import org.apache.maven.api.services.ArtifactInstaller;
+import org.apache.maven.api.services.ArtifactInstallerRequest;
+import org.apache.maven.api.services.ArtifactManager;
+import org.apache.maven.api.services.LocalRepositoryManager;
+import org.apache.maven.api.services.ProjectBuilder;
+import org.apache.maven.api.services.ProjectBuilderRequest;
+import org.apache.maven.api.services.ProjectBuilderResult;
+import org.apache.maven.api.services.ProjectManager;
+import org.apache.maven.api.services.RepositoryFactory;
+import org.apache.maven.api.services.xml.ModelXmlFactory;
+import org.apache.maven.internal.impl.DefaultModelXmlFactory;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.mockito.ArgumentMatchers;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+/**
+ *
+ */
+public class SessionStub
+{
+
+    public static Session getMockSession( String localRepo )
+    {
+        LocalRepository localRepository = mock( LocalRepository.class );
+        when( localRepository.getId() ).thenReturn( "local" );
+        when( localRepository.getPath() ).thenReturn( Paths.get( localRepo ) );
+        return getMockSession( localRepository );
+    }
+
+    public static Session getMockSession( LocalRepository localRepository )
+    {
+        Session session = mock( Session.class );
+
+        RepositoryFactory repositoryFactory = mock( RepositoryFactory.class );
+        when( repositoryFactory.createRemote( any( Repository.class ) ) )
+                .thenAnswer( iom ->
+                {
+                    Repository repository = iom.getArgument( 0, Repository.class );
+                    return repositoryFactory.createRemote( repository.getId(), repository.getUrl() );
+                } );
+        when( repositoryFactory.createRemote( anyString(), anyString() ) )
+                .thenAnswer( iom ->
+                {
+                    String id = iom.getArgument( 0, String.class );
+                    String url = iom.getArgument( 1, String.class );
+                    RemoteRepository remoteRepository = mock( RemoteRepository.class, withSettings().lenient() );
+                    when( remoteRepository.getId() ).thenReturn( id );
+                    when( remoteRepository.getUrl() ).thenReturn( url );
+                    when( remoteRepository.getProtocol() ).thenReturn( URI.create( url ).getScheme() );
+                    return remoteRepository;
+                } );
+
+        LocalRepositoryManager localRepositoryManager = mock( LocalRepositoryManager.class );
+        when( localRepositoryManager.getPathForLocalArtifact( any(), any(), any() ) )
+                .thenAnswer( iom ->
+                {
+                    LocalRepository localRepo = iom.getArgument( 1, LocalRepository.class );
+                    Artifact artifact = iom.getArgument( 2, Artifact.class );
+                    return localRepo.getPath().resolve( getPathForArtifact( artifact, true ) );
+                } );
+
+        ArtifactInstaller artifactInstaller = mock( ArtifactInstaller.class );
+        doAnswer( iom ->
+                {
+                    artifactInstaller.install( ArtifactInstallerRequest.build(
+                            iom.getArgument( 0, Session.class ),
+                            iom.getArgument( 1, Collection.class ) ) );
+                    return null;
+                } )
+                .when( artifactInstaller )
+                .install( any( Session.class ), ArgumentMatchers.<Collection<Artifact>>any() );
+
+        ArtifactDeployer artifactDeployer = mock( ArtifactDeployer.class );
+        doAnswer( iom ->
+                {
+                    artifactDeployer.deploy( ArtifactDeployerRequest.build(
+                            iom.getArgument( 0, Session.class ),
+                            iom.getArgument( 1, RemoteRepository.class ),
+                            iom.getArgument( 2, Collection.class ) ) );
+                    return null;
+                } )
+                .when( artifactDeployer ).deploy( any(), any(), any() );
+
+        ArtifactManager artifactManager = mock( ArtifactManager.class );
+        Map<Artifact, Path> paths = new HashMap<>();
+        doAnswer( iom ->
+                    {
+                        paths.put( iom.getArgument( 0 ), iom.getArgument( 1 ) ); return null;
+                    } )
+                .when( artifactManager ).setPath( any(), any() );
+        doAnswer( iom -> Optional.ofNullable( paths.get( iom.getArgument( 0, Artifact.class ) ) ) )
+                .when( artifactManager ).getPath( any() );
+
+        ProjectManager projectManager = mock( ProjectManager.class );
+        Map<Project, Collection<Artifact>> attachedArtifacts = new HashMap<>();
+        doAnswer( iom ->
+                {
+                    Project project = iom.getArgument( 1, Project.class );
+                    String type = iom.getArgument( 2, String.class );
+                    Path path = iom.getArgument( 3, Path.class );
+                    Artifact artifact = session.createArtifact(
+                            project.getGroupId(), project.getArtifactId(), project.getVersion(),
+                            null, null, type );
+                    artifactManager.setPath( artifact, path );
+                    attachedArtifacts.computeIfAbsent( project, p -> new ArrayList<>() )
+                            .add( artifact );
+                    return null;
+                } )
+                .when( projectManager ).attachArtifact( same( session ), any( Project.class ), any(), any() );
+        doAnswer( iom ->
+                {
+                    Project project = iom.getArgument( 0, Project.class );
+                    Artifact artifact = iom.getArgument( 1, Artifact.class );
+                    Path path = iom.getArgument( 2, Path.class );
+                    artifactManager.setPath( artifact, path );
+                    attachedArtifacts.computeIfAbsent( project, p -> new ArrayList<>() )
+                            .add( artifact );
+                    return null;
+                } )
+                .when( projectManager )
+                .attachArtifact( any( Project.class ), any( Artifact.class ), any( Path.class ) );
+        when( projectManager.getAttachedArtifacts( any() ) )
+                .then( iom -> attachedArtifacts.computeIfAbsent(
+                        iom.getArgument( 0, Project.class ), p -> new ArrayList<>() ) );
+
+        ArtifactFactory artifactFactory = mock( ArtifactFactory.class );
+        when( artifactFactory.create( any() ) )
+                .then( iom ->
+                {
+                    ArtifactFactoryRequest request = iom.getArgument( 0, ArtifactFactoryRequest.class );
+                    String classifier = request.getClassifier();
+                    String extension = request.getExtension();
+                    String type = request.getType();
+                    if ( classifier == null )
+                    {
+                        classifier = "";
+                    }
+                    if ( extension == null )
+                    {
+                        extension = type != null ? type : "";
+                    }
+                    return new ArtifactStub( request.getGroupId(), request.getArtifactId(),
+                            classifier, request.getVersion(), extension );
+                } );
+
+        ProjectBuilder projectBuilder = mock( ProjectBuilder.class );
+        when( projectBuilder.build( any( ProjectBuilderRequest.class ) ) ).then( iom ->
+                {
+                    ProjectBuilderRequest request = iom.getArgument( 0, ProjectBuilderRequest.class );
+                    ProjectBuilderResult result = mock( ProjectBuilderResult.class );
+                    Model model = new MavenXpp3Reader().read(
+                            request.getSource().get().getInputStream() ).getDelegate();
+                    ProjectStub projectStub = new ProjectStub();
+                    projectStub.setModel( model );
+                    ArtifactStub artifactStub = new ArtifactStub( model.getGroupId(), model.getArtifactId(),
+                            "", model.getVersion(), model.getPackaging() );
+                    projectStub.setArtifact( artifactStub );
+                    when( result.getProject() ).thenReturn( Optional.of( projectStub ) );
+                    return result;
+                } );
+
+        Properties sysProps = new Properties();
+        Properties usrProps = new Properties();
+        doReturn( sysProps ).when( session ).getSystemProperties();
+        doReturn( usrProps ).when( session ).getUserProperties();
+
+        when( session.getLocalRepository() ).thenReturn( localRepository );
+        when( session.getService( RepositoryFactory.class ) ).thenReturn( repositoryFactory );
+        when( session.getService( ProjectBuilder.class ) ).thenReturn( projectBuilder );
+        when( session.getService( LocalRepositoryManager.class ) ).thenReturn( localRepositoryManager );
+        when( session.getService( ProjectManager.class ) ).thenReturn( projectManager );
+        when( session.getService( ArtifactManager.class ) ).thenReturn( artifactManager );
+        when( session.getService( ArtifactInstaller.class ) ).thenReturn( artifactInstaller );
+        when( session.getService( ArtifactDeployer.class ) ).thenReturn( artifactDeployer );
+        when( session.getService( ArtifactFactory.class ) ).thenReturn( artifactFactory );
+        when( session.getService( ModelXmlFactory.class ) ).thenReturn( new DefaultModelXmlFactory() );
+
+        when( session.getPathForLocalArtifact( any( Artifact.class ) ) )
+                .then( iom -> localRepositoryManager.getPathForLocalArtifact( session,
+                        session.getLocalRepository(), iom.getArgument( 0, Artifact.class ) ) );
+        when( session.createArtifact( any(), any(), any(), any(), any(), any() ) )
+                .thenAnswer( iom ->
+                {
+                    String groupId = iom.getArgument( 0, String.class );
+                    String artifactId = iom.getArgument( 1, String.class );
+                    String version = iom.getArgument( 2, String.class );
+                    String classifier = iom.getArgument( 3, String.class );
+                    String extension = iom.getArgument( 4, String.class );
+                    String type = iom.getArgument( 5, String.class );
+                    return session.getService( ArtifactFactory.class ).create( ArtifactFactoryRequest.builder()
+                            .session( session )
+                            .groupId( groupId )
+                            .artifactId( artifactId )
+                            .version( version )
+                            .classifier( classifier )
+                            .extension( extension )
+                            .type( type )
+                            .build() );
+                } );
+        when( session.createArtifact( any(), any(), any(), any() ) )
+                .thenAnswer( iom ->
+                {
+                    String groupId = iom.getArgument( 0, String.class );
+                    String artifactId = iom.getArgument( 1, String.class );
+                    String version = iom.getArgument( 2, String.class );
+                    String extension = iom.getArgument( 3, String.class );
+                    return session.getService( ArtifactFactory.class ).create( ArtifactFactoryRequest.builder()
+                            .session( session )
+                            .groupId( groupId )
+                            .artifactId( artifactId )
+                            .version( version )
+                            .extension( extension )
+                            .build() );
+                } );
+        when ( session.createRemoteRepository( anyString(), anyString() ) )
+                .thenAnswer( iom ->
+                {
+                    String id = iom.getArgument( 0, String.class );
+                    String url = iom.getArgument( 1, String.class );
+                    return session.getService( RepositoryFactory.class ).createRemote( id, url );
+                } );
+        doAnswer( iom -> artifactManager.getPath( iom.getArgument( 0, Artifact.class ) ) )
+                .when( session ).getArtifactPath( any() );
+
+        when( session.withLocalRepository( any() ) )
+                .thenAnswer( iom -> getMockSession( iom.getArgument( 0, LocalRepository.class ) ) );
+        return session;
+    }
+
+    static String getPathForArtifact( Artifact artifact, boolean local )
+    {
+        StringBuilder path = new StringBuilder( 128 );
+        path.append( artifact.getGroupId().replace( '.', '/' ) ).append( '/' );
+        path.append( artifact.getArtifactId() ).append( '/' );
+        path.append( artifact.getVersion() ).append( '/' );
+        path.append( artifact.getArtifactId() ).append( '-' );
+        path.append( artifact.getVersion() );
+        if ( artifact.getClassifier().length() > 0 )
+        {
+            path.append( '-' ).append( artifact.getClassifier() );
+        }
+        if ( artifact.getExtension().length() > 0 )
+        {
+            path.append( '.' ).append( artifact.getExtension() );
+        }
+        return path.toString();
+    }
+
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/AbstractMojoTestCase.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/AbstractMojoTestCase.java
index fb69c6e..acea6ec 100644
--- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/AbstractMojoTestCase.java
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/AbstractMojoTestCase.java
@@ -21,6 +21,7 @@
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
@@ -28,7 +29,6 @@
 import java.lang.reflect.Field;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -39,7 +39,6 @@
 import com.google.inject.Module;
 import org.apache.commons.io.input.XmlStreamReader;
 import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.factory.ArtifactFactory;
 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
 import org.apache.maven.execution.DefaultMavenExecutionRequest;
 import org.apache.maven.execution.DefaultMavenExecutionResult;
@@ -48,7 +47,6 @@
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
 import org.apache.maven.model.Plugin;
-import org.apache.maven.monitor.logging.DefaultLog;
 import org.apache.maven.plugin.Mojo;
 import org.apache.maven.plugin.MojoExecution;
 import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
@@ -56,8 +54,8 @@
 import org.apache.maven.plugin.descriptor.Parameter;
 import org.apache.maven.plugin.descriptor.PluginDescriptor;
 import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
-import org.apache.maven.plugin.logging.Log;
 import org.apache.maven.project.MavenProject;
+import org.apache.maven.repository.RepositorySystem;
 import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
 import org.codehaus.plexus.ContainerConfiguration;
 import org.codehaus.plexus.DefaultContainerConfiguration;
@@ -74,7 +72,6 @@
 import org.codehaus.plexus.configuration.PlexusConfiguration;
 import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
 import org.codehaus.plexus.context.Context;
-import org.codehaus.plexus.logging.LoggerManager;
 import org.codehaus.plexus.util.InterpolationFilterReader;
 import org.codehaus.plexus.util.ReaderFactory;
 import org.codehaus.plexus.util.ReflectionUtils;
@@ -153,14 +150,14 @@
         {
             
             PluginDescriptor pluginDescriptor = new PluginDescriptorBuilder().build( interpolationReader );
-
-            Artifact artifact = lookup( ArtifactFactory.class ).createBuildArtifact( pluginDescriptor.getGroupId(),
+    
+            Artifact artifact =
+                lookup( RepositorySystem.class ).createArtifact( pluginDescriptor.getGroupId(),
                                                                  pluginDescriptor.getArtifactId(),
                                                                  pluginDescriptor.getVersion(), ".jar" );
     
             artifact.setFile( getPluginArtifactFile() );
             pluginDescriptor.setPluginArtifact( artifact );
-            //noinspection ArraysAsListWithZeroOrOneArgument
             pluginDescriptor.setArtifacts( Arrays.asList( artifact ) );
     
             for ( ComponentDescriptor<?> desc : pluginDescriptor.getComponents() )
@@ -236,7 +233,7 @@
     protected InputStream getPublicDescriptorStream()
         throws Exception
     {
-        return Files.newInputStream( new File( getPluginDescriptorPath() ).toPath() );
+        return new FileInputStream( new File( getPluginDescriptorPath() ) );
     }
 
     protected String getPluginDescriptorPath()
@@ -269,7 +266,6 @@
     /**
      * @since 3.0.0
      */
-    @SuppressWarnings( "EmptyMethod" )
     protected void addGuiceModules( List<Module> modules )
     {
         // no custom guice modules by default
@@ -277,11 +273,15 @@
 
     protected ContainerConfiguration setupContainerConfiguration()
     {
-        return new DefaultContainerConfiguration()
-          .setClassWorld( new ClassWorld( "plexus.core", Thread.currentThread().getContextClassLoader() ) )
+        ClassWorld classWorld = new ClassWorld( "plexus.core", Thread.currentThread().getContextClassLoader() );
+
+        ContainerConfiguration cc = new DefaultContainerConfiguration()
+          .setClassWorld( classWorld )
           .setClassPathScanning( PlexusConstants.SCANNING_INDEX )
           .setAutoWiring( true )
-          .setName( "maven" );
+          .setName( "maven" );      
+
+        return cc;
     }
     
     @Override
@@ -403,12 +403,6 @@
 
         T mojo = (T) lookup( Mojo.class, groupId + ":" + artifactId + ":" + version + ":" + goal );
 
-        LoggerManager loggerManager = getContainer().lookup( LoggerManager.class );
-        
-        Log mojoLogger = new DefaultLog( loggerManager.getLoggerForComponent( Mojo.ROLE ) );
-
-        mojo.setLog( mojoLogger );
-
         if ( pluginConfiguration != null )
         {
             /* requires v10 of plexus container for lookup on expression evaluator
@@ -494,7 +488,6 @@
 
         MavenSession session = new MavenSession( container, MavenRepositorySystemUtils.newSession(), request, result );
         session.setCurrentProject( project );
-        //noinspection ArraysAsListWithZeroOrOneArgument
         session.setProjects( Arrays.asList( project ) );
         return session;
     }
@@ -525,7 +518,7 @@
             executionConfiguration = new Xpp3Dom( "configuration" );
         }
 
-        Xpp3Dom defaultConfiguration = MojoDescriptorCreator.convert( mojoDescriptor );
+        Xpp3Dom defaultConfiguration = new Xpp3Dom( MojoDescriptorCreator.convert( mojoDescriptor ) );
 
         Xpp3Dom finalConfiguration = new Xpp3Dom( "configuration" );
 
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/ArtifactStubFactory.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/ArtifactStubFactory.java
index 618d36f..73bc8ee 100644
--- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/ArtifactStubFactory.java
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/ArtifactStubFactory.java
@@ -316,7 +316,7 @@
             WarArchiver war = (WarArchiver) archiver;
             // the use of this is counter-intuitive:
             // http://jira.codehaus.org/browse/PLX-286
-            war.setExpectWebXml( false );
+            war.setIgnoreWebxml( false );
         }
         archiver.createArchive();
     }
@@ -549,7 +549,7 @@
      */
     public static String getFormattedFileName( Artifact artifact, boolean removeVersion )
     {
-        String destFileName;
+        String destFileName = null;
 
         // if there is a file and we aren't stripping the version, just get the
         // name directly
@@ -560,7 +560,7 @@
         else
         // if offline
         {
-            String versionString;
+            String versionString = null;
             if ( !removeVersion )
             {
                 versionString = "-" + artifact.getVersion();
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/MojoLogWrapper.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/MojoLogWrapper.java
new file mode 100644
index 0000000..6fb4f24
--- /dev/null
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/MojoLogWrapper.java
@@ -0,0 +1,146 @@
+package org.apache.maven.plugin.testing;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.plugin.logging.Log;
+import org.slf4j.Logger;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * @author jdcasey
+ */
+public class MojoLogWrapper
+    implements Log
+{
+    private final Logger logger;
+
+    public MojoLogWrapper( Logger logger )
+    {
+        this.logger = requireNonNull( logger );
+    }
+
+    public void debug( CharSequence content )
+    {
+        logger.debug( toString( content ) );
+    }
+
+    private String toString( CharSequence content )
+    {
+        if ( content == null )
+        {
+            return "";
+        }
+        else
+        {
+            return content.toString();
+        }
+    }
+
+    @Override
+    public void debug( CharSequence content, Throwable error )
+    {
+        logger.debug( toString( content ), error );
+    }
+
+    @Override
+    public void debug( Throwable error )
+    {
+        logger.debug( "", error );
+    }
+
+    @Override
+    public void info( CharSequence content )
+    {
+        logger.info( toString( content ) );
+    }
+
+    @Override
+    public void info( CharSequence content, Throwable error )
+    {
+        logger.info( toString( content ), error );
+    }
+
+    @Override
+    public void info( Throwable error )
+    {
+        logger.info( "", error );
+    }
+
+    @Override
+    public void warn( CharSequence content )
+    {
+        logger.warn( toString( content ) );
+    }
+
+    @Override
+    public void warn( CharSequence content, Throwable error )
+    {
+        logger.warn( toString( content ), error );
+    }
+
+    @Override
+    public void warn( Throwable error )
+    {
+        logger.warn( "", error );
+    }
+
+    @Override
+    public void error( CharSequence content )
+    {
+        logger.error( toString( content ) );
+    }
+
+    @Override
+    public void error( CharSequence content, Throwable error )
+    {
+        logger.error( toString( content ), error );
+    }
+
+    @Override
+    public void error( Throwable error )
+    {
+        logger.error( "", error );
+    }
+
+    @Override
+    public boolean isDebugEnabled()
+    {
+        return logger.isDebugEnabled();
+    }
+
+    @Override
+    public boolean isInfoEnabled()
+    {
+        return logger.isInfoEnabled();
+    }
+
+    @Override
+    public boolean isWarnEnabled()
+    {
+        return logger.isWarnEnabled();
+    }
+
+    @Override
+    public boolean isErrorEnabled()
+    {
+        return logger.isErrorEnabled();
+    }
+}
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/MojoRule.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/MojoRule.java
index 04d2564..b430bd8 100644
--- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/MojoRule.java
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/MojoRule.java
@@ -23,6 +23,7 @@
 import java.io.InputStream;
 import java.util.Map;
 
+import org.apache.maven.api.Session;
 import org.apache.maven.execution.DefaultMavenExecutionRequest;
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenSession;
@@ -49,8 +50,8 @@
 
 /**
  * {@link TestRule} for usage with Junit-4.10ff. This is just a wrapper for an embedded 
- * {@link AbstractMojoTestCase}, so all <code>protected</code> methods of the TestCase are
- * exhibited as <code>public</code> in the rule. You may annotate single tests methods with
+ * {@link AbstractMojoTestCase}, so all {@code protected} methods of the TestCase are
+ * exhibited as {@code public} in the rule. You may annotate single tests methods with
  * {@link WithoutMojo} to prevent the rule from firing.
  *
  * @author Mirko Friedenhagen
@@ -87,8 +88,7 @@
     /**
      * May be overridden in the implementation to do stuff after the current test was run.
      */
-    @SuppressWarnings( "EmptyMethod" )
-    protected void after()
+    protected void after() 
     {
         
     }
@@ -407,6 +407,7 @@
         {
             sessionScope.enter();
             sessionScope.seed( MavenSession.class, session );
+            sessionScope.seed( Session.class, session.getSession() );
 
             MojoExecutionScope executionScope = lookup( MojoExecutionScope.class );
             try
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/ResolverExpressionEvaluatorStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/ResolverExpressionEvaluatorStub.java
index 92172f9..3fed91f 100644
--- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/ResolverExpressionEvaluatorStub.java
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/ResolverExpressionEvaluatorStub.java
@@ -21,12 +21,11 @@
 
 import java.io.File;
 
-import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
 import org.apache.maven.artifact.repository.MavenArtifactRepository;
+import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
 import org.codehaus.plexus.PlexusTestCase;
 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
-import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
 
 /**
  * Stub for {@link ExpressionEvaluator}
@@ -76,7 +75,7 @@
             }
 
             // Was not an expression
-            if ( expression.contains( "$$" ) )
+            if ( expression.indexOf( "$$" ) > -1 )
             {
                 return expression.replaceAll( "\\$\\$", "\\$" );
             }
@@ -102,11 +101,9 @@
         }
         else if ( "localRepository".equals( expression ) )
         {
-            return new MavenArtifactRepository( "localRepository",
-            "file://" + new File( PlexusTestCase.getBasedir(), "target/local-repo" ).getAbsolutePath(),
-            new DefaultRepositoryLayout(),
-            new ArtifactRepositoryPolicy( true, "release", "always" ),
-            new ArtifactRepositoryPolicy( true, "snapshot", "never" ) );
+            File localRepo = new File( PlexusTestCase.getBasedir(), "target/local-repo" );
+            return new MavenArtifactRepository( "localRepository", "file://" + localRepo.getAbsolutePath(),
+                    new DefaultRepositoryLayout(), null, null );
         }
         else
         {
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/resources/TestResources.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/resources/TestResources.java
index b183c3e..f871292 100644
--- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/resources/TestResources.java
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/resources/TestResources.java
@@ -21,7 +21,6 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Set;
 import java.util.TreeSet;
@@ -73,7 +72,8 @@
     /**
      * Creates new clean copy of test project directory structure. The copy is named after both the test being executed
      * and test project name, which allows the same test project can be used by multiple tests and by different
-     * instances of the same parametrized tests.<br>
+     * instances of the same parametrized tests.
+     * <p>
      * TODO Provide alternative working directory naming for Windows, which still limits path names to ~250 charecters
      */
     public File getBasedir( String project )
@@ -117,7 +117,11 @@
         scanner.addDefaultExcludes();
         scanner.scan();
 
-        Set<String> actual = new TreeSet<>( Arrays.asList( scanner.getIncludedFiles() ) );
+        Set<String> actual = new TreeSet<>();
+        for ( String path : scanner.getIncludedFiles() )
+        {
+            actual.add( path );
+        }
         for ( String path : scanner.getIncludedDirectories() )
         {
             if ( path.length() > 0 )
@@ -129,7 +133,10 @@
         Set<String> expected = new TreeSet<>();
         if ( expectedPaths != null )
         {
-            expected.addAll( Arrays.asList( expectedPaths ) );
+            for ( String path : expectedPaths )
+            {
+                expected.add( path );
+            }
         }
 
         // compare textual representation to make diff easier to understand
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/ArtifactStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/ArtifactStub.java
index 33b7dba..63f15c5 100644
--- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/ArtifactStub.java
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/ArtifactStub.java
@@ -185,7 +185,14 @@
     @Override
     public String getDependencyConflictId()
     {
-        return getGroupId() + ":" + getArtifactId() + ":" + getType() + ":" + getClassifier();
+        StringBuffer buffer = new StringBuffer();
+
+        buffer.append( getGroupId() );
+        buffer.append( ":" ).append( getArtifactId() );
+        buffer.append( ":" ).append( getType() );
+        buffer.append( ":" ).append( getClassifier() );
+
+        return buffer.toString();
     }
 
     /**
@@ -193,7 +200,6 @@
      *
      * @see org.apache.maven.artifact.Artifact#addMetadata(org.apache.maven.artifact.metadata.ArtifactMetadata)
      */
-    @SuppressWarnings( "deprecation" )
     @Override
     public void addMetadata( ArtifactMetadata artifactMetadata )
     {
@@ -204,7 +210,6 @@
      * @return <code>null</code>.
      * @see org.apache.maven.artifact.Artifact#getMetadataList()
      */
-    @SuppressWarnings( "deprecation" )
     @Override
     public Collection<ArtifactMetadata> getMetadataList()
     {
@@ -544,7 +549,6 @@
         return true;
     }
 
-    @SuppressWarnings( "EmptyMethod" )
     public void setFromAuthoritativeRepository( boolean fromAuthoritativeRepository )
     {
         // nothing
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/MavenProjectStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/MavenProjectStub.java
index 4b81e85..aacb6e7 100644
--- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/MavenProjectStub.java
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/MavenProjectStub.java
@@ -69,7 +69,6 @@
  *
  * @author jesse
  */
-@SuppressWarnings( "FieldCanBeLocal" )
 public class MavenProjectStub
     extends MavenProject
 {
@@ -283,14 +282,19 @@
     @Override
     public List<ArtifactRepository> getRemoteArtifactRepositories()
     {
-        return Collections.emptyList();
+        return Collections.<ArtifactRepository>emptyList();
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean hasParent()
     {
-        return parent != null;
+        if ( parent != null )
+        {
+            return true;
+        }
+
+        return false;
     }
 
     /** {@inheritDoc} */
@@ -333,7 +337,7 @@
     @Override
     public List<Dependency> getDependencies()
     {
-        return Collections.emptyList();
+        return Collections.<Dependency>emptyList();
     }
 
     /**
@@ -956,7 +960,7 @@
     @Override
     public List<MailingList> getMailingLists()
     {
-        return Collections.emptyList();
+        return Collections.<MailingList>emptyList();
     }
 
     /**
@@ -989,7 +993,7 @@
     @Override
     public List<Developer> getDevelopers()
     {
-        return Collections.emptyList();
+        return Collections.<Developer>emptyList();
     }
 
     /**
@@ -1022,7 +1026,7 @@
     @Override
     public List<Contributor> getContributors()
     {
-        return Collections.emptyList();
+        return Collections.<Contributor>emptyList();
     }
 
     /**
@@ -1058,7 +1062,7 @@
     @Override
     public List<Resource> getResources()
     {
-        return Collections.emptyList();
+        return Collections.<Resource>emptyList();
     }
 
     /**
@@ -1069,7 +1073,7 @@
     @Override
     public List<Resource> getTestResources()
     {
-        return Collections.emptyList();
+        return Collections.<Resource>emptyList();
     }
 
     /**
@@ -1160,7 +1164,7 @@
     @Override
     public Set<Artifact> getArtifacts()
     {
-        return Collections.emptySet();
+        return Collections.<Artifact>emptySet();
     }
 
     /**
@@ -1171,7 +1175,7 @@
     @Override
     public Map<String, Artifact> getArtifactMap()
     {
-        return Collections.emptyMap();
+        return Collections.<String, Artifact>emptyMap();
     }
 
     /**
@@ -1193,7 +1197,7 @@
     @Override
     public Set<Artifact> getPluginArtifacts()
     {
-        return Collections.emptySet();
+        return Collections.<Artifact>emptySet();
     }
 
     /**
@@ -1204,7 +1208,7 @@
     @Override
     public Map<String, Artifact> getPluginArtifactMap()
     {
-        return Collections.emptyMap();
+        return Collections.<String, Artifact>emptyMap();
     }
 
     /**
@@ -1226,7 +1230,7 @@
     @Override
     public Set<Artifact> getReportArtifacts()
     {
-        return Collections.emptySet();
+        return Collections.<Artifact>emptySet();
     }
 
     /**
@@ -1237,7 +1241,7 @@
     @Override
     public Map<String, Artifact> getReportArtifactMap()
     {
-        return Collections.emptyMap();
+        return Collections.<String, Artifact>emptyMap();
     }
 
     /**
@@ -1259,7 +1263,7 @@
     @Override
     public Set<Artifact> getExtensionArtifacts()
     {
-        return Collections.emptySet();
+        return Collections.<Artifact>emptySet();
     }
 
     /**
@@ -1270,7 +1274,7 @@
     @Override
     public Map<String, Artifact> getExtensionArtifactMap()
     {
-        return Collections.emptyMap();
+        return Collections.<String, Artifact>emptyMap();
     }
 
     /**
@@ -1303,7 +1307,7 @@
     @Override
     public List<Repository> getRepositories()
     {
-        return Collections.emptyList();
+        return Collections.<Repository>emptyList();
     }
 
     /**
@@ -1314,7 +1318,7 @@
     @Override
     public List<ReportPlugin> getReportPlugins()
     {
-        return Collections.emptyList();
+        return Collections.<ReportPlugin>emptyList();
     }
 
     /**
@@ -1325,7 +1329,7 @@
     @Override
     public List<Plugin> getBuildPlugins()
     {
-        return Collections.emptyList();
+        return Collections.<Plugin>emptyList();
     }
 
     /**
@@ -1336,7 +1340,7 @@
     @Override
     public List<String> getModules()
     {
-        return Collections.emptyList();
+        return Collections.<String>emptyList();
     }
 
     /**
@@ -1353,7 +1357,6 @@
     /**
      * By default, do nothing.
      */
-    @SuppressWarnings( "EmptyMethod" )
     public void addPlugin( Plugin plugin )
     {
         // nop
@@ -1364,7 +1367,6 @@
      *
      * @param plugin
      */
-    @SuppressWarnings( "EmptyMethod" )
     public void injectPluginManagementInfo( Plugin plugin )
     {
         // nop
@@ -1417,7 +1419,7 @@
     @Override
     public List<Repository> getPluginRepositories()
     {
-        return Collections.emptyList();
+        return Collections.<Repository>emptyList();
     }
 
     /** {@inheritDoc} */
@@ -1573,7 +1575,7 @@
     @Override
     public List<Extension> getBuildExtensions()
     {
-        return Collections.emptyList();
+        return Collections.<Extension>emptyList();
     }
 
     /**
@@ -1585,7 +1587,7 @@
     public Set<Artifact> createArtifacts( ArtifactFactory artifactFactory, String string,
                                           ArtifactFilter artifactFilter )
     {
-        return Collections.emptySet();
+        return Collections.<Artifact>emptySet();
     }
 
     /**
@@ -1629,7 +1631,7 @@
     @Override
     public List<String> getFilters()
     {
-        return Collections.emptyList();
+        return Collections.<String>emptyList();
     }
 
     /**
@@ -1640,7 +1642,7 @@
     @Override
     public Map<String, MavenProject> getProjectReferences()
     {
-        return Collections.emptyMap();
+        return Collections.<String, MavenProject>emptyMap();
     }
 
     /** {@inheritDoc} */
diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/StubArtifactRepository.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/StubArtifactRepository.java
index 65995c8..3a5d7cb 100644
--- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/StubArtifactRepository.java
+++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/plugin/testing/stubs/StubArtifactRepository.java
@@ -37,7 +37,7 @@
 public class StubArtifactRepository
     implements ArtifactRepository
 {
-    private final String baseDir;
+    private String baseDir = null;
 
     /**
      * Default constructor
@@ -274,4 +274,16 @@
     {
         // no op
     }
+
+    @Override
+    public boolean isBlocked()
+    {
+        return false;
+    }
+
+    @Override
+    public void setBlocked( boolean blocked )
+    {
+        // no op
+    }
 }
diff --git a/maven-plugin-testing-harness/src/site/apt/getting-started/index.apt b/maven-plugin-testing-harness/src/site/apt/getting-started/index.apt
index cd63995..749611f 100644
--- a/maven-plugin-testing-harness/src/site/apt/getting-started/index.apt
+++ b/maven-plugin-testing-harness/src/site/apt/getting-started/index.apt
@@ -34,7 +34,7 @@
  <<<maven-my-plugin>>> which is generated by the Maven Archetype Plugin, i.e.:
 
 +---+
-mvn archetype:generate \
+mvn archetype:create \
   -DgroupId=org.apache.maven.plugin.my \
   -DartifactId=maven-my-plugin \
   -DarchetypeArtifactId=maven-archetype-mojo
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/api/plugin/testing/ExpressionEvaluatorTest.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/api/plugin/testing/ExpressionEvaluatorTest.java
new file mode 100644
index 0000000..b86a8d0
--- /dev/null
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/api/plugin/testing/ExpressionEvaluatorTest.java
@@ -0,0 +1,111 @@
+package org.apache.maven.api.plugin.testing;
+
+/*
+ * 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.
+ */
+
+import javax.inject.Named;
+
+import java.nio.file.Paths;
+import java.util.Properties;
+
+import com.google.inject.Provides;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.plugin.MojoException;
+import org.apache.maven.api.plugin.testing.InjectMojo;
+import org.apache.maven.api.plugin.testing.MojoTest;
+import org.apache.maven.api.plugin.testing.stubs.SessionStub;
+import org.codehaus.plexus.util.StringUtils;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+
+/**
+ * @author Edwin Punzalan
+ */
+@MojoTest
+public class ExpressionEvaluatorTest
+{
+
+    private static final String LOCAL_REPO = "target/local-repo/";
+    private static final String ARTIFACT_ID = "maven-test-mojo";
+    private static final String COORDINATES = "groupId:" + ARTIFACT_ID + ":version:goal";
+    private static final String CONFIG =
+            "<project>\n"
+            + "    <build>\n"
+            + "        <plugins>\n"
+            + "            <plugin>\n"
+            + "                <artifactId>" + ARTIFACT_ID + "</artifactId>\n"
+            + "                <configuration>\n"
+            + "                    <basedir>${basedir}</basedir>\n"
+            + "                    <workdir>${basedir}/workDirectory</workdir>\n"
+            + "                </configuration>\n"
+            + "            </plugin>\n"
+            + "        </plugins>\n"
+            + "    </build>\n"
+            + "</project>\n";
+
+    @Test
+    @InjectMojo( goal = COORDINATES, pom = CONFIG )
+    public void testInjection( ExpressionEvaluatorMojo mojo )
+    {
+        assertDoesNotThrow( mojo::execute );
+    }
+
+    @Named( COORDINATES )
+    public static class ExpressionEvaluatorMojo
+            implements org.apache.maven.api.plugin.Mojo
+    {
+        private String basedir;
+
+        private String workdir;
+
+        /** {@inheritDoc} */
+        @Override
+        public void execute()
+                throws MojoException
+        {
+            if ( StringUtils.isEmpty( basedir ) )
+            {
+                throw new MojoException( "basedir was not injected." );
+            }
+
+            if ( StringUtils.isEmpty( workdir ) )
+            {
+                throw new MojoException( "workdir was not injected." );
+            }
+            else if ( !workdir.startsWith( basedir ) )
+            {
+                throw new MojoException( "workdir does not start with basedir." );
+            }
+        }
+    }
+
+    @Provides @SuppressWarnings( "unused" )
+    Session session()
+    {
+        Session session = SessionStub.getMockSession( LOCAL_REPO );
+        doReturn( new Properties() ).when( session ).getSystemProperties();
+        doReturn( new Properties() ).when( session ).getUserProperties();
+        doAnswer( iom -> Paths.get( MojoExtension.getBasedir() ) ).when( session ).getExecutionRootDirectory();
+        return session;
+    }
+
+}
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ArtifactStubFactoryTest.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ArtifactStubFactoryTest.java
index dfbd8ab..52070bf 100644
--- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ArtifactStubFactoryTest.java
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ArtifactStubFactoryTest.java
@@ -22,10 +22,14 @@
 import java.io.IOException;
 
 import junit.framework.TestCase;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class ArtifactStubFactoryTest
-    extends TestCase
 {
+    @Test
     public void testVersionChecks() throws IOException
     {
         ArtifactStubFactory factory = new ArtifactStubFactory();
@@ -35,7 +39,8 @@
         assertFalse(factory.getSnapshotArtifact().isRelease());
     }
 
-    public void testCreateFiles()
+    @Test
+    public void testCreateFiles() throws IOException
     {
         ArtifactStubFactory factory = new ArtifactStubFactory();
         assertFalse(factory.isCreateFiles());
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ExpressionEvaluatorMojo.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ExpressionEvaluatorMojo.java
index 5f1f802..2d4faa0 100644
--- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ExpressionEvaluatorMojo.java
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ExpressionEvaluatorMojo.java
@@ -22,6 +22,7 @@
 import org.apache.maven.artifact.repository.ArtifactRepository;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
 import org.codehaus.plexus.util.StringUtils;
 
 /**
@@ -39,7 +40,7 @@
     /** {@inheritDoc} */
     @Override
     public void execute()
-        throws MojoExecutionException
+        throws MojoExecutionException, MojoFailureException
     {
         if ( StringUtils.isEmpty( basedir ) )
         {
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ExpressionEvaluatorTest.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ExpressionEvaluatorTest.java
index e4666d5..3939bd2 100644
--- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ExpressionEvaluatorTest.java
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/ExpressionEvaluatorTest.java
@@ -32,6 +32,7 @@
 public class ExpressionEvaluatorTest
     extends AbstractMojoTestCase
 {
+    private Xpp3Dom pomDom;
 
     private PlexusConfiguration pluginConfiguration;
 
@@ -42,22 +43,24 @@
     {
         super.setUp();
 
-        String pom = "<project>" + "\n"
-                + "  <build>" + "\n"
-                + "    <plugins>" + "\n"
-                + "      <plugin>" + "\n"
-                + "        <artifactId>maven-test-mojo</artifactId>" + "\n"
-                + "        <configuration>" + "\n"
-                + "          <basedir>${basedir}</basedir>" + "\n"
-                + "          <workdir>${basedir}/workDirectory</workdir>" + "\n"
-                + "          <localRepository>${localRepository}</localRepository>" + "\n"
-                + "        </configuration>" + "\n"
-                + "      </plugin>" + "\n"
-                + "    </plugins>" + "\n"
-                + "  </build>" + "\n"
-                + "</project>" + "\n";
+        StringBuffer pom = new StringBuffer();
 
-        Xpp3Dom pomDom = Xpp3DomBuilder.build( new StringReader( pom ) );
+        pom.append( "<project>" ).append( "\n" );
+        pom.append( "  <build>" ).append( "\n" );
+        pom.append( "    <plugins>" ).append( "\n" );
+        pom.append( "      <plugin>" ).append( "\n" );
+        pom.append( "        <artifactId>maven-test-mojo</artifactId>" ).append( "\n" );
+        pom.append( "        <configuration>" ).append( "\n" );
+        pom.append( "          <basedir>${basedir}</basedir>" ).append( "\n" );
+        pom.append( "          <workdir>${basedir}/workDirectory</workdir>" ).append( "\n" );
+        pom.append( "          <localRepository>${localRepository}</localRepository>" ).append( "\n" );
+        pom.append( "        </configuration>" ).append( "\n" );
+        pom.append( "      </plugin>" ).append( "\n" );
+        pom.append( "    </plugins>" ).append( "\n" );
+        pom.append( "  </build>" ).append( "\n" );
+        pom.append( "</project>" ).append( "\n" );
+
+        pomDom = Xpp3DomBuilder.build( new StringReader( pom.toString() ) );
 
         pluginConfiguration = extractPluginConfiguration( "maven-test-mojo", pomDom );
     }
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/MojoRuleTest.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/MojoRuleTest.java
index 0c7bc58..86a3902 100644
--- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/MojoRuleTest.java
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/MojoRuleTest.java
@@ -43,14 +43,18 @@
     private boolean beforeWasCalled = false;
 
     @Rule
-    public final MojoRule rule = new MojoRule() {
+    public MojoRule rule = new MojoRule() {
 
         @Override
-        protected void before()
+        protected void before() throws Throwable 
         {
             beforeWasCalled = true;
         }      
     };
+    
+    private String pom;
+
+    private Xpp3Dom pomDom;
 
     private PlexusConfiguration pluginConfiguration;
 
@@ -60,7 +64,8 @@
         throws Exception
     {
 
-        String pom = "<project>" +
+        pom =
+            "<project>" +
                 "<build>" +
                 "<plugins>" +
                 "<plugin>" +
@@ -74,15 +79,17 @@
                 "</build>" +
                 "</project>";
 
-        Xpp3Dom pomDom = Xpp3DomBuilder.build( new StringReader( pom ) );
+        pomDom = Xpp3DomBuilder.build( new StringReader( pom ) );
 
         pluginConfiguration = rule.extractPluginConfiguration( "maven-simple-plugin", pomDom );
     }
 
     /**
+     * @throws Exception if any
      */
     @Test
     public void testPluginConfigurationExtraction()
+        throws Exception
     {
         assertEquals( "valueOne", pluginConfiguration.getChild( "keyOne" ).getValue() );
 
@@ -159,12 +166,14 @@
     @Test
     @WithoutMojo
     public void testNoRuleWrapper()
+        throws Exception
     {
         assertFalse( "before executed although WithMojo annotation was added", beforeWasCalled );
     }
 
     @Test    
     public void testWithRuleWrapper()
+        throws Exception
     {
         assertTrue( "before executed because WithMojo annotation was not added", beforeWasCalled );
     }
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/MojoTestCaseTest.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/MojoTestCaseTest.java
index 6858175..8cf1f23 100644
--- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/MojoTestCaseTest.java
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/MojoTestCaseTest.java
@@ -19,6 +19,7 @@
  * under the License.
  */
 
+import org.apache.maven.api.plugin.testing.MojoTest;
 import org.codehaus.plexus.configuration.PlexusConfiguration;
 import org.codehaus.plexus.util.xml.Xpp3Dom;
 import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
@@ -29,9 +30,13 @@
 /**
  * @author Jason van Zyl
  */
+@MojoTest
 public class MojoTestCaseTest
     extends AbstractMojoTestCase
 {
+    private String pom;
+
+    private Xpp3Dom pomDom;
 
     private PlexusConfiguration pluginConfiguration;
 
@@ -42,7 +47,8 @@
     {
         super.setUp();
 
-        String pom = "<project>" +
+        pom =
+            "<project>" +
                 "<build>" +
                 "<plugins>" +
                 "<plugin>" +
@@ -56,14 +62,16 @@
                 "</build>" +
                 "</project>";
 
-        Xpp3Dom pomDom = Xpp3DomBuilder.build( new StringReader( pom ) );
+        pomDom = Xpp3DomBuilder.build( new StringReader( pom ) );
 
         pluginConfiguration = extractPluginConfiguration( "maven-simple-plugin", pomDom );
     }
 
     /**
+     * @throws Exception if any
      */
     public void testPluginConfigurationExtraction()
+        throws Exception
     {
         assertEquals( "valueOne", pluginConfiguration.getChild( "keyOne" ).getValue() );
 
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/PluginArtifactFileTest.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/PluginArtifactFileTest.java
index 3fb57c0..c4f32be 100644
--- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/PluginArtifactFileTest.java
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/PluginArtifactFileTest.java
@@ -30,6 +30,7 @@
     private static final String FS = System.getProperty( "file.separator" );
     
     public void testArtifact()
+        throws Exception
     {
         MojoExecution execution = newMojoExecution( "parameters" ); // TODO dedicated test mojo
 
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleMojo.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleMojo.java
index 3d1bd10..85ea293 100644
--- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleMojo.java
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/SimpleMojo.java
@@ -20,6 +20,7 @@
  */
 
 import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
 
 /**
  * @author Jason van Zyl
@@ -43,6 +44,7 @@
 
     @Override
     public void execute()
+        throws MojoExecutionException
     {
     }
 }
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/TestSilentLog.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/TestSilentLog.java
index 25135ac..4d782df 100644
--- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/TestSilentLog.java
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/TestSilentLog.java
@@ -31,7 +31,7 @@
     public void testLog()
     {
         Log log = new SilentLog();
-        String text = "Text";
+        String text = new String( "Text" );
         Throwable e = new RuntimeException();
         log.debug( text );
         log.debug( text, e );
@@ -54,7 +54,7 @@
     public void testLogger()
     {
         Logger log = new SilentLog();
-        String text = "Text";
+        String text = new String( "Text" );
         Throwable e = new RuntimeException();
 
         log.debug( text );
diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/resources/TestResourcesTest.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/resources/TestResourcesTest.java
index 36c4b65..c7a78e3 100644
--- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/resources/TestResourcesTest.java
+++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/plugin/testing/resources/TestResourcesTest.java
@@ -23,7 +23,7 @@
 
 public class TestResourcesTest
 {
-    public final TestResources resources = new TestResources();
+    public TestResources resources = new TestResources();
 
     @Test( expected = IllegalStateException.class )
     public void testNoRuleAnnotation()
diff --git a/pom.xml b/pom.xml
index 58864ee..a09f9e2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,7 +31,7 @@
 
   <groupId>org.apache.maven.plugin-testing</groupId>
   <artifactId>maven-plugin-testing</artifactId>
-  <version>3.4.0-SNAPSHOT</version>
+  <version>4.0.0-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>Maven Plugin Testing</name>
@@ -67,7 +67,7 @@
 
   <properties>
     <surefire.version>3.0.0-M7</surefire.version>
-    <mavenVersion>3.2.5</mavenVersion>
+    <mavenVersion>4.0.0-alpha-2</mavenVersion>
     <maven.site.path>plugin-testing-archives/LATEST</maven.site.path>
     <javaVersion>8</javaVersion>
     <project.build.outputTimestamp>2020-04-07T21:04:00Z</project.build.outputTimestamp>