You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2019/08/04 17:32:18 UTC

[maven-surefire] 01/01: A new feature 'excludedEnvironmentVariables' (pending: MOJO system properties with prefix 'surefire' and 'failsafe').

This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch release/3.0.0-M4
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git

commit 0c81a567fae58e67bf6eb409831f42beb544a2e1
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Aug 3 22:15:38 2019 +0200

    A new feature 'excludedEnvironmentVariables' (pending: MOJO system properties with prefix 'surefire' and 'failsafe').
---
 Jenkinsfile                                        |   2 +-
 .../plugin/surefire/AbstractSurefireMojo.java      |  33 ++++++
 .../AbstractClasspathForkConfiguration.java        |   3 +-
 .../booterclient/ClasspathForkConfiguration.java   |   6 +-
 .../booterclient/DefaultForkConfiguration.java     |  13 ++-
 .../surefire/booterclient/ForkConfiguration.java   |   1 +
 .../booterclient/JarManifestForkConfiguration.java |   6 +-
 .../ModularClasspathForkConfiguration.java         |   3 +-
 .../OutputStreamFlushReceiver.java                 |  36 +++---
 .../OutputStreamFlushableCommandline.java          |  47 +++++---
 .../plugin/surefire/AbstractSurefireMojoTest.java  |  60 ++++++++++
 .../booterclient/DefaultForkConfigurationTest.java |  34 +++---
 .../booterclient/ForkConfigurationTest.java        |   3 +-
 .../ModularClasspathForkConfigurationTest.java     |   7 +-
 .../OutputStreamFlushableCommandlineTest.java      | 129 +++++++++++++++++++++
 .../maven/surefire/its/EnvironmentVariablesIT.java |  21 +++-
 16 files changed, 338 insertions(+), 66 deletions(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index 4b906d6..0efb698 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -112,7 +112,7 @@ timeout(time: 12, unit: 'HOURS') {
         currentBuild.result = 'FAILURE'
         throw e
     } finally {
-        jenkinsNotify()
+        //jenkinsNotify()
     }
 }
 
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index 2f179e8..801c984 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -445,6 +445,21 @@ public abstract class AbstractSurefireMojo
     private Map<String, String> environmentVariables = new HashMap<>();
 
     /**
+     * You can selectively exclude individual environment variables by enumerating their keys.
+     * <br>
+     * All environment variables of the plugin process are inherited to a Surefire sub-process by default.
+     * The keys must literally (case sensitive) match in order to exclude their environment variable.
+     * <br>
+     * The environment is a system-dependent mapping from keys to values which is passed from parent plugin process
+     * to the forked Surefire processes.<br>
+     * Example to exclude two environment variables: <i>mvn test -DexcludedEnvironmentVariables=ACME1,ACME2</i>
+     *
+     * @since 3.0.0-M4
+     */
+    @Parameter( property = "excludedEnvironmentVariables" )
+    private String[] excludedEnvironmentVariables;
+
+    /**
      * Command line working directory.
      *
      * @since 2.1.3
@@ -1195,6 +1210,7 @@ public abstract class AbstractSurefireMojo
             if ( getConsoleLogger().isDebugEnabled() )
             {
                 showMap( getEnvironmentVariables(), "environment variable" );
+                showArray( getExcludedEnvironmentVariables(), "excluded environment variable" );
             }
 
             Properties originalSystemProperties = (Properties) System.getProperties().clone();
@@ -2268,6 +2284,7 @@ public abstract class AbstractSurefireMojo
                     getProject().getModel().getProperties(),
                     getArgLine(),
                     getEnvironmentVariables(),
+                    getExcludedEnvironmentVariables(),
                     getConsoleLogger().isDebugEnabled(),
                     getEffectiveForkCount(),
                     reuseForks,
@@ -2283,6 +2300,7 @@ public abstract class AbstractSurefireMojo
                     getProject().getModel().getProperties(),
                     getArgLine(),
                     getEnvironmentVariables(),
+                    getExcludedEnvironmentVariables(),
                     getConsoleLogger().isDebugEnabled(),
                     getEffectiveForkCount(),
                     reuseForks,
@@ -2298,6 +2316,7 @@ public abstract class AbstractSurefireMojo
                     getProject().getModel().getProperties(),
                     getArgLine(),
                     getEnvironmentVariables(),
+                    getExcludedEnvironmentVariables(),
                     getConsoleLogger().isDebugEnabled(),
                     getEffectiveForkCount(),
                     reuseForks,
@@ -2505,6 +2524,7 @@ public abstract class AbstractSurefireMojo
         checksum.add( getParallelTestsTimeoutInSeconds() );
         checksum.add( getParallelTestsTimeoutForcedInSeconds() );
         checksum.add( getEnvironmentVariables() );
+        checksum.add( getExcludedEnvironmentVariables() );
         checksum.add( getWorkingDirectory() );
         checksum.add( isChildDelegation() );
         checksum.add( getGroups() );
@@ -2627,6 +2647,14 @@ public abstract class AbstractSurefireMojo
         }
     }
 
+    private <T> void showArray( T[] array, String setting )
+    {
+        for ( T e : array )
+        {
+            getConsoleLogger().debug( "Setting " + setting + " [" + e + "]" );
+        }
+    }
+
     private Classpath getArtifactClasspath( Artifact surefireArtifact )
     {
         Classpath existing = ClasspathCache.getCachedClassPath( surefireArtifact.getArtifactId() );
@@ -3446,6 +3474,11 @@ public abstract class AbstractSurefireMojo
         return environmentVariables;
     }
 
+    protected String[] getExcludedEnvironmentVariables()
+    {
+        return excludedEnvironmentVariables == null ? new String[0] : excludedEnvironmentVariables;
+    }
+
     @SuppressWarnings( "UnusedDeclaration" )
     public void setEnvironmentVariables( Map<String, String> environmentVariables )
     {
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
index 6c57ebc..692f486 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
@@ -44,6 +44,7 @@ abstract class AbstractClasspathForkConfiguration
                                         @Nonnull Properties modelProperties,
                                         @Nullable String argLine,
                                         @Nonnull Map<String, String> environmentVariables,
+                                        @Nonnull String[] excludedEnvironmentVariables,
                                         boolean debug,
                                         int forkCount,
                                         boolean reuseForks,
@@ -51,7 +52,7 @@ abstract class AbstractClasspathForkConfiguration
                                         @Nonnull ConsoleLogger log )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
index cf6c67e..72aab98 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
@@ -44,12 +44,14 @@ public final class ClasspathForkConfiguration
     public ClasspathForkConfiguration( @Nonnull Classpath bootClasspath, @Nonnull File tempDirectory,
                                        @Nullable String debugLine, @Nonnull File workingDirectory,
                                        @Nonnull Properties modelProperties, @Nullable String argLine,
-                                       @Nonnull Map<String, String> environmentVariables, boolean debug, int forkCount,
+                                       @Nonnull Map<String, String> environmentVariables,
+                                       @Nonnull String[] excludedEnvironmentVariables,
+                                       boolean debug, int forkCount,
                                        boolean reuseForks, @Nonnull Platform pluginPlatform,
                                        @Nonnull ConsoleLogger log )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
index fa99451..4ab4435 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
@@ -59,6 +59,7 @@ public abstract class DefaultForkConfiguration
     @Nonnull private final Properties modelProperties;
     @Nullable private final String argLine;
     @Nonnull private final Map<String, String> environmentVariables;
+    @Nonnull private final String[] excludedEnvironmentVariables;
     private final boolean debug;
     private final int forkCount;
     private final boolean reuseForks;
@@ -73,6 +74,7 @@ public abstract class DefaultForkConfiguration
                                      @Nonnull Properties modelProperties,
                                      @Nullable String argLine,
                                      @Nonnull Map<String, String> environmentVariables,
+                                     @Nonnull String[] excludedEnvironmentVariables,
                                      boolean debug,
                                      int forkCount,
                                      boolean reuseForks,
@@ -86,6 +88,7 @@ public abstract class DefaultForkConfiguration
         this.modelProperties = modelProperties;
         this.argLine = argLine;
         this.environmentVariables = toImmutable( environmentVariables );
+        this.excludedEnvironmentVariables = excludedEnvironmentVariables;
         this.debug = debug;
         this.forkCount = forkCount;
         this.reuseForks = reuseForks;
@@ -119,7 +122,8 @@ public abstract class DefaultForkConfiguration
                                                                @Nonnull File dumpLogDirectory )
             throws SurefireBooterForkException
     {
-        OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
+        OutputStreamFlushableCommandline cli =
+                new OutputStreamFlushableCommandline( getExcludedEnvironmentVariables() );
 
         cli.setWorkingDirectory( getWorkingDirectory( forkNumber ).getAbsolutePath() );
 
@@ -289,6 +293,13 @@ public abstract class DefaultForkConfiguration
         return environmentVariables;
     }
 
+    @Nonnull
+    @Override
+    protected String[] getExcludedEnvironmentVariables()
+    {
+        return excludedEnvironmentVariables;
+    }
+
     @Override
     protected boolean isDebug()
     {
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
index d45d13d..92bebd0 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
@@ -45,6 +45,7 @@ public abstract class ForkConfiguration
     @Nonnull protected abstract Properties getModelProperties();
     @Nullable protected abstract String getArgLine();
     @Nonnull protected abstract Map<String, String> getEnvironmentVariables();
+    @Nonnull protected abstract String[] getExcludedEnvironmentVariables();
     protected abstract boolean isDebug();
     protected abstract int getForkCount();
     protected abstract boolean isReuseForks();
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
index 1a3dd4f..fb4dd4a 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
@@ -62,12 +62,14 @@ public final class JarManifestForkConfiguration
     public JarManifestForkConfiguration( @Nonnull Classpath bootClasspath, @Nonnull File tempDirectory,
                                          @Nullable String debugLine, @Nonnull File workingDirectory,
                                          @Nonnull Properties modelProperties, @Nullable String argLine,
-                                         @Nonnull Map<String, String> environmentVariables, boolean debug,
+                                         @Nonnull Map<String, String> environmentVariables,
+                                         @Nonnull String[] excludedEnvironmentVariables,
+                                         boolean debug,
                                          int forkCount, boolean reuseForks, @Nonnull Platform pluginPlatform,
                                          @Nonnull ConsoleLogger log )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
     }
 
     @Override
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java
index 48e74d9..55818a2 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java
@@ -67,6 +67,7 @@ public class ModularClasspathForkConfiguration
                                               @Nonnull Properties modelProperties,
                                               @Nullable String argLine,
                                               @Nonnull Map<String, String> environmentVariables,
+                                              @Nonnull String[] excludedEnvironmentVariables,
                                               boolean debug,
                                               @Nonnegative int forkCount,
                                               boolean reuseForks,
@@ -74,7 +75,7 @@ public class ModularClasspathForkConfiguration
                                               @Nonnull ConsoleLogger log )
     {
         super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
-                environmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
+                environmentVariables, excludedEnvironmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
     }
 
     @Override
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/EnvironmentVariablesIT.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushReceiver.java
similarity index 51%
copy from surefire-its/src/test/java/org/apache/maven/surefire/its/EnvironmentVariablesIT.java
copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushReceiver.java
index 651200f..131661d 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/EnvironmentVariablesIT.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushReceiver.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.its;
+package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,29 +19,29 @@ package org.apache.maven.surefire.its;
  * under the License.
  */
 
-import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
-import org.junit.Test;
+import java.io.IOException;
+import java.io.OutputStream;
 
 /**
- * SUREFIRE-763 Asserts that environment variables are correctly populated using "useSystemClassLoader=false"
- * SUREFIRE-963 Asserts that empty environment variables are read as "".
- * 
- * @author Kristian Rosenvold
- * @author Christophe Deneux
+ * Facade flushing {@link OutputStream} and isolating the stream in client.
  */
-public class EnvironmentVariablesIT
-    extends SurefireJUnit4IntegrationTestCase
+final class OutputStreamFlushReceiver
+        implements FlushReceiver
 {
-    @Test
-    public void testWhenUseSystemClassLoader()
+    private final OutputStream outputStream;
+
+    /**
+     * Wraps an output stream in order to delegate a flush.
+     */
+    OutputStreamFlushReceiver( OutputStream outputStream )
     {
-        unpack( "/environment-variables" ).addGoal( "-DuseSystemClassLoader=true" ).executeTest();
+        this.outputStream = outputStream;
     }
 
-    @Test
-    public void testWhenDontUseSystemClassLoader()
+    @Override
+    public void flush()
+            throws IOException
     {
-        unpack( "/environment-variables" ).addGoal( "-DuseSystemClassLoader=false" ).executeTest();
+        outputStream.flush();
     }
-
-}
\ No newline at end of file
+}
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandline.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandline.java
index eb1ab5b..e22b8df 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandline.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandline.java
@@ -19,9 +19,13 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  * under the License.
  */
 
-import java.io.IOException;
-import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
 import org.apache.maven.shared.utils.cli.CommandLineException;
+import org.apache.maven.shared.utils.cli.CommandLineUtils;
 import org.apache.maven.shared.utils.cli.Commandline;
 
 /**
@@ -35,29 +39,37 @@ public class OutputStreamFlushableCommandline
     extends Commandline
     implements FlushReceiverProvider
 {
+    private final Collection<String> excludedEnvironmentVariables;
+    private volatile FlushReceiver flushReceiver;
+
     /**
-     * Wraps an output stream in order to delegate a flush.
+     * for testing purposes only
      */
-    private final class OutputStreamFlushReceiver
-        implements FlushReceiver
+    public OutputStreamFlushableCommandline()
     {
-        private final OutputStream outputStream;
+        this( new String[0] );
+    }
 
-        private OutputStreamFlushReceiver( OutputStream outputStream )
-        {
-            this.outputStream = outputStream;
-        }
+    public OutputStreamFlushableCommandline( String[] excludedEnvironmentVariables )
+    {
+        this.excludedEnvironmentVariables = new ConcurrentLinkedDeque<>();
+        Collections.addAll( this.excludedEnvironmentVariables, excludedEnvironmentVariables );
+    }
+
+    @Override
+    public final void addSystemEnvironment()
+    {
+        Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
 
-        @Override
-        public void flush()
-            throws IOException
+        for ( String key : systemEnvVars.stringPropertyNames() )
         {
-            outputStream.flush();
+            if ( !excludedEnvironmentVariables.contains( key ) )
+            {
+                addEnvironment( key, systemEnvVars.getProperty( key ) );
+            }
         }
     }
 
-    private volatile FlushReceiver flushReceiver;
-
     @Override
     public Process execute()
         throws CommandLineException
@@ -77,5 +89,4 @@ public class OutputStreamFlushableCommandline
     {
         return flushReceiver;
     }
-
-}
\ No newline at end of file
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
index cec6760..af65ec2 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoTest.java
@@ -61,6 +61,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -85,6 +86,7 @@ import static org.powermock.api.mockito.PowerMockito.mock;
 import static org.powermock.api.mockito.PowerMockito.spy;
 import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
 import static org.powermock.reflect.Whitebox.invokeMethod;
+import static org.powermock.reflect.Whitebox.setInternalState;
 
 /**
  * Test for {@link AbstractSurefireMojo}.
@@ -100,6 +102,64 @@ public class AbstractSurefireMojoTest
     private final Mojo mojo = new Mojo();
 
     @Test
+    public void shouldShowArray() throws Exception
+    {
+        Logger logger = mock( Logger.class );
+        when( logger.isDebugEnabled() ).thenReturn( true );
+        doNothing().when( logger ).debug( anyString() );
+
+        AbstractSurefireMojo mojo = spy( this.mojo );
+
+        when( mojo.getConsoleLogger() ).thenReturn( new PluginConsoleLogger( logger ) );
+
+        Object[] array = { "ABC", "XYZ" };
+        invokeMethod( mojo, "showArray", array, "prefix" );
+
+        ArgumentCaptor<String> argument = ArgumentCaptor.forClass( String.class );
+        verify( logger, times( 2 ) ).debug( argument.capture() );
+        assertThat( argument.getAllValues() )
+                .containsExactly( "Setting prefix [ABC]", "Setting prefix [XYZ]" );
+    }
+
+    @Test
+    public void shouldShowMap() throws Exception
+    {
+        Logger logger = mock( Logger.class );
+        when( logger.isDebugEnabled() ).thenReturn( true );
+        doNothing().when( logger ).debug( anyString() );
+
+        AbstractSurefireMojo mojo = spy( this.mojo );
+
+        when( mojo.getConsoleLogger() ).thenReturn( new PluginConsoleLogger( logger ) );
+
+        Map<String, String> map = new LinkedHashMap<>();
+        map.put( "ABC", "123" );
+        map.put( "XYZ", "987" );
+        invokeMethod( mojo, "showMap", map, "prefix" );
+
+        ArgumentCaptor<String> argument = ArgumentCaptor.forClass( String.class );
+        verify( logger, times( 2 ) ).debug( argument.capture() );
+        assertThat( argument.getAllValues() )
+                .containsExactly( "Setting prefix [ABC]=[123]", "Setting prefix [XYZ]=[987]" );
+    }
+
+    @Test
+    public void shouldGetNullEnv()
+    {
+        assertThat( mojo.getExcludedEnvironmentVariables() )
+                .hasSize( 0 );
+    }
+
+    @Test
+    public void shouldGetEnv()
+    {
+        setInternalState( mojo, "excludedEnvironmentVariables", new String[] { "ABC", "KLM" } );
+        assertThat( mojo.getExcludedEnvironmentVariables() )
+                .hasSize( 2 )
+                .contains( "ABC", "KLM" );
+    }
+
+    @Test
     public void shouldRetainInPluginArtifacts() throws Exception
     {
         Artifact provider = new DefaultArtifact( "g", "a", createFromVersionSpec( "1" ), "compile", "jar", "", null );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
index a8f6c69..0d9c3b6 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfigurationTest.java
@@ -69,6 +69,7 @@ public class DefaultForkConfigurationTest
     private Properties modelProperties;
     private String argLine;
     private Map<String, String> environmentVariables;
+    private String[] excludedEnvironmentVariables;
     private boolean debug;
     private int forkCount;
     private boolean reuseForks;
@@ -85,6 +86,7 @@ public class DefaultForkConfigurationTest
         modelProperties = new Properties();
         argLine = null;
         environmentVariables = new HashMap<>();
+        excludedEnvironmentVariables = new String[0];
         debug = true;
         forkCount = 2;
         reuseForks = true;
@@ -96,8 +98,8 @@ public class DefaultForkConfigurationTest
     public void shouldBeNullArgLine() throws Exception
     {
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
-                workingDirectory, modelProperties, argLine, environmentVariables, debug, forkCount, reuseForks,
-                pluginPlatform, log )
+                workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
+                debug, forkCount, reuseForks, pluginPlatform, log )
         {
 
             @Override
@@ -122,8 +124,8 @@ public class DefaultForkConfigurationTest
     {
         argLine = "";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
-                workingDirectory, modelProperties, argLine, environmentVariables, debug, forkCount, reuseForks,
-                pluginPlatform, log )
+                workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
+                debug, forkCount, reuseForks, pluginPlatform, log )
         {
 
             @Override
@@ -148,8 +150,8 @@ public class DefaultForkConfigurationTest
     {
         argLine = "\n\r";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
-                workingDirectory, modelProperties, argLine, environmentVariables, debug, forkCount, reuseForks,
-                pluginPlatform, log )
+                workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
+                debug, forkCount, reuseForks, pluginPlatform, log )
         {
 
             @Override
@@ -174,8 +176,8 @@ public class DefaultForkConfigurationTest
     {
         argLine = "-Dfile.encoding=UTF-8";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
-                workingDirectory, modelProperties, argLine, environmentVariables, debug, forkCount, reuseForks,
-                pluginPlatform, log )
+                workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
+                debug, forkCount, reuseForks, pluginPlatform, log )
         {
 
             @Override
@@ -201,8 +203,8 @@ public class DefaultForkConfigurationTest
         modelProperties.put( "encoding", "UTF-8" );
         argLine = "-Dfile.encoding=@{encoding}";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
-                workingDirectory, modelProperties, argLine, environmentVariables, debug, forkCount, reuseForks,
-                pluginPlatform, log )
+                workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
+                debug, forkCount, reuseForks, pluginPlatform, log )
         {
 
             @Override
@@ -227,8 +229,8 @@ public class DefaultForkConfigurationTest
     {
         argLine = "a\n\rb";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
-                workingDirectory, modelProperties, argLine, environmentVariables, debug, forkCount, reuseForks,
-                pluginPlatform, log )
+                workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
+                debug, forkCount, reuseForks, pluginPlatform, log )
         {
 
             @Override
@@ -253,8 +255,8 @@ public class DefaultForkConfigurationTest
     {
         argLine = "-Dthread=${surefire.threadNumber}";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
-                workingDirectory, modelProperties, argLine, environmentVariables, debug, forkCount, reuseForks,
-                pluginPlatform, log )
+                workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
+                debug, forkCount, reuseForks, pluginPlatform, log )
         {
 
             @Override
@@ -279,8 +281,8 @@ public class DefaultForkConfigurationTest
     {
         argLine = "-Dthread=${surefire.forkNumber}";
         DefaultForkConfiguration config = new DefaultForkConfiguration( booterClasspath, tempDirectory, debugLine,
-                workingDirectory, modelProperties, argLine, environmentVariables, debug, forkCount, reuseForks,
-                pluginPlatform, log )
+                workingDirectory, modelProperties, argLine, environmentVariables, excludedEnvironmentVariables,
+                debug, forkCount, reuseForks, pluginPlatform, log )
         {
 
             @Override
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
index 3fe0ecd..f29c62e 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
@@ -217,7 +217,8 @@ public class ForkConfigurationTest
         FileUtils.deleteDirectory( tmpDir );
         assertTrue( tmpDir.mkdirs() );
         return new JarManifestForkConfiguration( emptyClasspath(), tmpDir, null,
-                cwd, new Properties(), argLine, Collections.<String, String>emptyMap(), false, 1, false,
+                cwd, new Properties(), argLine,
+                Collections.<String, String>emptyMap(), new String[0], false, 1, false,
                 platform, new NullConsoleLogger() );
     }
 
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
index 26410b5..72eafb9 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java
@@ -32,7 +32,7 @@ import org.junit.Test;
 import javax.annotation.Nonnull;
 import java.io.File;
 import java.util.Collection;
-import java.util.HashMap;
+import java.util.Collections;
 import java.util.List;
 import java.util.Properties;
 
@@ -65,8 +65,9 @@ public class ModularClasspathForkConfigurationTest
         File pwd = new File( "." ).getCanonicalFile();
 
         ModularClasspathForkConfiguration config = new ModularClasspathForkConfiguration( booter, tmp, "", pwd,
-                new Properties(), "", new HashMap<String, String>(), true, 1, true, new Platform(),
-                new NullConsoleLogger() )
+                new Properties(), "",
+                Collections.<String, String>emptyMap(), new String[0], true, 1, true,
+                new Platform(), new NullConsoleLogger() )
         {
             @Nonnull
             @Override
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandlineTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandlineTest.java
new file mode 100644
index 0000000..78251a6
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/OutputStreamFlushableCommandlineTest.java
@@ -0,0 +1,129 @@
+package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
+
+/*
+ * 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.shared.utils.cli.CommandLineException;
+import org.fest.assertions.Condition;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.verifyZeroInteractions;
+
+public class OutputStreamFlushableCommandlineTest
+{
+
+    @Test
+    public void shouldGetEnvironmentVariables()
+    {
+        OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
+        String[] env = cli.getEnvironmentVariables();
+
+        assertThat( env )
+                .doesNotHaveDuplicates()
+                .satisfies( new ContainsAnyStartsWith( "JAVA_HOME=" ) );
+
+        String[] excluded = { "JAVA_HOME" };
+        cli = new OutputStreamFlushableCommandline( excluded );
+        env = cli.getEnvironmentVariables();
+
+        assertThat( env )
+                .doesNotHaveDuplicates()
+                .satisfies( new NotContainsAnyStartsWith( "JAVA_HOME=" ) );
+    }
+
+    @Test
+    public void shouldExecute() throws CommandLineException
+    {
+        OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
+        cli.getShell().setWorkingDirectory( System.getProperty( "user.dir" ) );
+        cli.getShell().setExecutable( IS_OS_WINDOWS ? "dir" : "ls" );
+        assertThat( cli.getFlushReceiver() ).isNull();
+        cli.execute();
+        assertThat( cli.getFlushReceiver() ).isNotNull();
+    }
+
+    @Test
+    public void shouldGetFlushReceiver()
+    {
+        OutputStreamFlushableCommandline cli = new OutputStreamFlushableCommandline();
+        assertThat( cli.getFlushReceiver() ).isNull();
+    }
+
+    @Test
+    public void shouldFlush() throws IOException
+    {
+        ByteArrayOutputStream os = mock( ByteArrayOutputStream.class );
+        OutputStreamFlushReceiver flushReceiver = new OutputStreamFlushReceiver( os );
+        verifyZeroInteractions( os );
+        flushReceiver.flush();
+        verify( os, times( 1 ) ).flush();
+    }
+
+    private static final class ContainsAnyStartsWith extends Condition<Object[]>
+    {
+        private final String expected;
+
+        ContainsAnyStartsWith( String expected )
+        {
+            this.expected = expected;
+        }
+
+        @Override
+        public boolean matches( Object[] values )
+        {
+            boolean matches = false;
+            for ( Object value : values )
+            {
+                assertThat( value ).isInstanceOf( String.class );
+                matches |= ( (String) value ).startsWith( expected );
+            }
+            return matches;
+        }
+    }
+
+    private static final class NotContainsAnyStartsWith extends Condition<Object[]>
+    {
+        private final String expected;
+
+        NotContainsAnyStartsWith( String expected )
+        {
+            this.expected = expected;
+        }
+
+        @Override
+        public boolean matches( Object[] values )
+        {
+            boolean matches = false;
+            for ( Object value : values )
+            {
+                assertThat( value ).isInstanceOf( String.class );
+                matches |= ( (String) value ).startsWith( expected );
+            }
+            return !matches;
+        }
+    }
+}
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/EnvironmentVariablesIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/EnvironmentVariablesIT.java
index 651200f..d8e7ba3 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/EnvironmentVariablesIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/EnvironmentVariablesIT.java
@@ -35,13 +35,30 @@ public class EnvironmentVariablesIT
     @Test
     public void testWhenUseSystemClassLoader()
     {
-        unpack( "/environment-variables" ).addGoal( "-DuseSystemClassLoader=true" ).executeTest();
+        unpack( "/environment-variables" )
+                .debugLogging()
+                .addGoal( "-DuseSystemClassLoader=true" )
+                .executeTest();
     }
 
     @Test
     public void testWhenDontUseSystemClassLoader()
     {
-        unpack( "/environment-variables" ).addGoal( "-DuseSystemClassLoader=false" ).executeTest();
+        unpack( "/environment-variables" )
+                .debugLogging()
+                .addGoal( "-DuseSystemClassLoader=false" )
+                .executeTest();
+    }
+
+    @Test
+    public void testExcludedEnv()
+    {
+        unpack( "/environment-variables" )
+                .maven()
+                .debugLogging()
+                .addEnvVar( "UNDEFINED_VAR", "dwdijoi" )
+                .sysProp( "excludedEnvironmentVariables", "UNDEFINED_VAR" )
+                .executeTest();
     }
 
 }
\ No newline at end of file