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/11/01 23:37:34 UTC

[maven-surefire] 01/01: [SUREFIRE-1705] new config parameter Exclude Environment Variables

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

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

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

    [SUREFIRE-1705] new config parameter Exclude Environment Variables
---
 maven-failsafe-plugin/pom.xml                      |   5 +
 .../maven/plugin/failsafe/IntegrationTestMojo.java |  22 +++-
 .../plugin/failsafe/IntegrationTestMojoTest.java   |   2 +-
 .../maven/plugin/failsafe/RunResultTest.java       |  11 --
 .../plugin/surefire/AbstractSurefireMojo.java      |  15 +++
 .../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 +++++---
 .../AbstractSurefireMojoJava7PlusTest.java         |   5 +
 .../plugin/surefire/AbstractSurefireMojoTest.java  |  50 ++++++++
 .../maven/plugin/surefire/MojoMocklessTest.java    |   5 +
 .../booterclient/DefaultForkConfigurationTest.java |  34 +++---
 .../booterclient/ForkConfigurationTest.java        |   3 +-
 .../ModularClasspathForkConfigurationTest.java     |   7 +-
 .../OutputStreamFlushableCommandlineTest.java      | 129 +++++++++++++++++++++
 maven-surefire-plugin/pom.xml                      |   5 +
 .../maven/plugin/surefire/SurefirePlugin.java      |  21 ++++
 .../maven/plugin/surefire/SurefirePluginTest.java  |  17 +++
 pom.xml                                            |   1 +
 .../maven/surefire/its/EnvironmentVariablesIT.java |  21 +++-
 25 files changed, 387 insertions(+), 81 deletions(-)

diff --git a/maven-failsafe-plugin/pom.xml b/maven-failsafe-plugin/pom.xml
index 7928762..4dc97b3 100644
--- a/maven-failsafe-plugin/pom.xml
+++ b/maven-failsafe-plugin/pom.xml
@@ -87,6 +87,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-reflect</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jacoco</groupId>
             <artifactId>org.jacoco.agent</artifactId>
             <classifier>runtime</classifier>
diff --git a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
index 35b0d41..f88ed6f 100644
--- a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
+++ b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
@@ -31,11 +31,9 @@ import org.apache.maven.surefire.suite.RunResult;
 
 import java.io.File;
 import java.io.IOException;
-import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 
 import static org.apache.maven.plugin.failsafe.util.FailsafeSummaryXmlUtils.writeSummary;
 
@@ -386,6 +384,21 @@ public class IntegrationTestMojo
     @Parameter( property = "failsafe.useModulePath", defaultValue = "true" )
     private boolean useModulePath;
 
+    /**
+     * You can selectively exclude individual environment variables by enumerating their keys.
+     * <br>
+     * The environment is a system-dependent mapping from keys to values which is inherited from the Maven process
+     * to the forked Surefire processes. The keys must literally (case sensitive) match in order to exclude
+     * their environment variable.
+     * <br>
+     * Example to exclude three environment variables:
+     * <i>mvn test -Dfailsafe.excludedEnvironmentVariables=ACME1,ACME2,ACME3</i>
+     *
+     * @since 3.0.0-M4
+     */
+    @Parameter( property = "failsafe.excludedEnvironmentVariables" )
+    private String[] excludedEnvironmentVariables;
+
     @Override
     protected int getRerunFailingTestsCount()
     {
@@ -841,8 +854,9 @@ public class IntegrationTestMojo
         return suiteXmlFiles != null && suiteXmlFiles.length != 0;
     }
 
-    static Charset toCharset( String encoding )
+    @Override
+    protected final String[] getExcludedEnvironmentVariables()
     {
-        return Charset.forName( Charset.isSupported( encoding ) ? encoding : encoding.toUpperCase( Locale.ROOT ) );
+        return excludedEnvironmentVariables == null ? new String[0] : excludedEnvironmentVariables;
     }
 }
diff --git a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/IntegrationTestMojoTest.java b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/IntegrationTestMojoTest.java
index 5e008aa..e145265 100644
--- a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/IntegrationTestMojoTest.java
+++ b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/IntegrationTestMojoTest.java
@@ -1 +1 @@
-package org.apache.maven.plugin.failsafe;

/*
 * 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.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.versioning.Invali
 dVersionSpecificationException;
import org.apache.maven.project.MavenProject;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;

import static org.apache.maven.artifact.versioning.VersionRange.createFromVersionSpec;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.fest.assertions.Assertions.assertThat;

/**
 * @since 2.20
 */
public class IntegrationTestMojoTest
{
    private IntegrationTestMojo mojo;

    @Before
    public void init() throws InvalidVersionSpecificationException, IOException
    {
        Artifact artifact = new DefaultArtifact( "g", "a", createFromVersionSpec( "1.0" ), "compile", "jar", "", null );
        artifact.setFile( new File( "./target/tmp/a-1.0.jar" ) );
        new File( "./target/tmp" ).mkdir();
        artifact.getFile().createNewFile();
        mojo = new IntegrationTestMojo();
        MavenProject project = mock( MavenProject.class );
        when( projec
 t.getArtifact() ).thenReturn( artifact );
        mojo.setProject( project );
    }

    @Test
    public void shouldBeJar()
    {
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "a-1.0.jar" );
    }

    @Test
    public void shouldBeAnotherJar()
    {
        mojo.setClassesDirectory( new File( "./target/another-1.0.jar" ) );
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "another-1.0.jar" );
    }

    @Test
    public void shouldBeClasses()
    {
        mojo.setClassesDirectory( new File( "./target/classes" ) );
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "classes" );
    }
}
\ No newline at end of file
+package org.apache.maven.plugin.failsafe;

/*
 * 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.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.versioning.Invali
 dVersionSpecificationException;
import org.apache.maven.project.MavenProject;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;

import static org.apache.maven.artifact.versioning.VersionRange.createFromVersionSpec;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.powermock.reflect.Whitebox.setInternalState;

/**
 * @since 2.20
 */
public class IntegrationTestMojoTest
{
    private IntegrationTestMojo mojo;

    @Before
    public void init() throws InvalidVersionSpecificationException, IOException
    {
        Artifact artifact = new DefaultArtifact( "g", "a", createFromVersionSpec( "1.0" ), "compile", "jar", "", null );
        artifact.setFile( new File( "./target/tmp/a-1.0.jar" ) );
        new File( "./target/tmp" ).mkdir();
        artifact.getFile().createNewFile();
        mojo = new IntegrationTestMojo();
        MavenPro
 ject project = mock( MavenProject.class );
        when( project.getArtifact() ).thenReturn( artifact );
        mojo.setProject( project );
    }

    @Test
    public void shouldBeJar()
    {
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "a-1.0.jar" );
    }

    @Test
    public void shouldBeAnotherJar()
    {
        mojo.setClassesDirectory( new File( "./target/another-1.0.jar" ) );
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( binaries.getName() ).isEqualTo( "another-1.0.jar" );
    }

    @Test
    public void shouldBeClasses()
    {
        mojo.setClassesDirectory( new File( "./target/classes" ) );
        mojo.setDefaultClassesDirectory( new File( "./target/classes" ) );
        File binaries = mojo.getClassesDirectory();
        assertThat( b
 inaries.getName() ).isEqualTo( "classes" );
    }

    @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" );
    }
}
\ No newline at end of file
diff --git a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java
index 5e75ebf..210e019 100644
--- a/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java
+++ b/maven-failsafe-plugin/src/test/java/org/apache/maven/plugin/failsafe/RunResultTest.java
@@ -24,7 +24,6 @@ import org.apache.maven.surefire.suite.RunResult;
 import org.junit.Test;
 
 import java.io.File;
-import java.nio.charset.Charset;
 
 import static org.fest.assertions.Assertions.assertThat;
 
@@ -118,16 +117,6 @@ public class RunResultTest
                 .isEqualTo( expected );
     }
 
-    @Test
-    public void shouldAcceptAliasCharset()
-    {
-        Charset charset1 = IntegrationTestMojo.toCharset( "UTF8" );
-        assertThat( charset1.name() ).isEqualTo( "UTF-8" );
-
-        Charset charset2 = IntegrationTestMojo.toCharset( "utf8" );
-        assertThat( charset2.name() ).isEqualTo( "UTF-8" );
-    }
-
     private void writeReadCheck( RunResult expected )
             throws Exception
     {
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 e2c69b6..4ee933b 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
@@ -813,6 +813,8 @@ public abstract class AbstractSurefireMojo
      */
     protected abstract boolean hasSuiteXmlFiles();
 
+    protected abstract String[] getExcludedEnvironmentVariables();
+
     public abstract File[] getSuiteXmlFiles();
 
     public abstract void setSuiteXmlFiles( File[] suiteXmlFiles );
@@ -1197,6 +1199,7 @@ public abstract class AbstractSurefireMojo
             if ( getConsoleLogger().isDebugEnabled() )
             {
                 showMap( getEnvironmentVariables(), "environment variable" );
+                showArray( getExcludedEnvironmentVariables(), "excluded environment variable" );
             }
 
             Properties originalSystemProperties = (Properties) System.getProperties().clone();
@@ -2270,6 +2273,7 @@ public abstract class AbstractSurefireMojo
                     getProject().getModel().getProperties(),
                     getArgLine(),
                     getEnvironmentVariables(),
+                    getExcludedEnvironmentVariables(),
                     getConsoleLogger().isDebugEnabled(),
                     getEffectiveForkCount(),
                     reuseForks,
@@ -2285,6 +2289,7 @@ public abstract class AbstractSurefireMojo
                     getProject().getModel().getProperties(),
                     getArgLine(),
                     getEnvironmentVariables(),
+                    getExcludedEnvironmentVariables(),
                     getConsoleLogger().isDebugEnabled(),
                     getEffectiveForkCount(),
                     reuseForks,
@@ -2300,6 +2305,7 @@ public abstract class AbstractSurefireMojo
                     getProject().getModel().getProperties(),
                     getArgLine(),
                     getEnvironmentVariables(),
+                    getExcludedEnvironmentVariables(),
                     getConsoleLogger().isDebugEnabled(),
                     getEffectiveForkCount(),
                     reuseForks,
@@ -2507,6 +2513,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() );
@@ -2629,6 +2636,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() );
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 dda6d3e..78e915a 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
@@ -66,12 +66,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/AbstractSurefireMojoJava7PlusTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
index b178d64..8dbab23 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java
@@ -545,6 +545,11 @@ public class AbstractSurefireMojoJava7PlusTest
         }
 
         @Override
+        protected String[] getExcludedEnvironmentVariables() {
+            return new String[0];
+        }
+
+        @Override
         public File[] getSuiteXmlFiles()
         {
             return new File[0];
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 bd77143..50d24cc 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,48 @@ 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 shouldRetainInPluginArtifacts() throws Exception
     {
         Artifact provider = new DefaultArtifact( "g", "a", createFromVersionSpec( "1" ), "compile", "jar", "", null );
@@ -1966,6 +2010,12 @@ public class AbstractSurefireMojoTest
         }
 
         @Override
+        protected String[] getExcludedEnvironmentVariables()
+        {
+            return new String[0];
+        }
+
+        @Override
         public File[] getSuiteXmlFiles()
         {
             return new File[0];
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
index 4371638..f9f598d 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/MojoMocklessTest.java
@@ -675,6 +675,11 @@ public class MojoMocklessTest
         }
 
         @Override
+        protected String[] getExcludedEnvironmentVariables() {
+            return new String[0];
+        }
+
+        @Override
         public File[] getSuiteXmlFiles()
         {
             return new File[0];
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/maven-surefire-plugin/pom.xml b/maven-surefire-plugin/pom.xml
index de00aeb..4df0fdd 100644
--- a/maven-surefire-plugin/pom.xml
+++ b/maven-surefire-plugin/pom.xml
@@ -59,6 +59,11 @@
             <artifactId>maven-plugin-annotations</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-reflect</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
index aad9230..ed83493 100644
--- a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
+++ b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
@@ -365,6 +365,21 @@ public class SurefirePlugin
     @Parameter( property = "surefire.useModulePath", defaultValue = "true" )
     private boolean useModulePath;
 
+    /**
+     * You can selectively exclude individual environment variables by enumerating their keys.
+     * <br>
+     * The environment is a system-dependent mapping from keys to values which is inherited from the Maven process
+     * to the forked Surefire processes. The keys must literally (case sensitive) match in order to exclude
+     * their environment variable.
+     * <br>
+     * Example to exclude three environment variables:
+     * <i>mvn test -Dsurefire.excludedEnvironmentVariables=ACME1,ACME2,ACME3</i>
+     *
+     * @since 3.0.0-M4
+     */
+    @Parameter( property = "surefire.excludedEnvironmentVariables" )
+    private String[] excludedEnvironmentVariables;
+
     @Override
     protected int getRerunFailingTestsCount()
     {
@@ -741,4 +756,10 @@ public class SurefirePlugin
     {
         return suiteXmlFiles != null && suiteXmlFiles.length != 0;
     }
+
+    @Override
+    protected final String[] getExcludedEnvironmentVariables()
+    {
+        return excludedEnvironmentVariables == null ? new String[0] : excludedEnvironmentVariables;
+    }
 }
diff --git a/maven-surefire-plugin/src/test/java/org/apache/maven/plugin/surefire/SurefirePluginTest.java b/maven-surefire-plugin/src/test/java/org/apache/maven/plugin/surefire/SurefirePluginTest.java
index 6d7b4a3..3c2dd75 100644
--- a/maven-surefire-plugin/src/test/java/org/apache/maven/plugin/surefire/SurefirePluginTest.java
+++ b/maven-surefire-plugin/src/test/java/org/apache/maven/plugin/surefire/SurefirePluginTest.java
@@ -23,6 +23,7 @@ import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.surefire.suite.RunResult;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.powermock.reflect.Whitebox.setInternalState;
 
 public class SurefirePluginTest extends TestCase
 {
@@ -84,4 +85,20 @@ public class SurefirePluginTest extends TestCase
         assertThat( new SurefirePlugin().getPluginName() )
                 .isEqualTo( "surefire" );
     }
+
+    public void testShouldGetNullEnv()
+    {
+        SurefirePlugin plugin = new SurefirePlugin();
+        assertThat( plugin.getExcludedEnvironmentVariables() )
+                .hasSize( 0 );
+    }
+
+    public void testShouldGetEnv()
+    {
+        SurefirePlugin plugin = new SurefirePlugin();
+        setInternalState( plugin, "excludedEnvironmentVariables", new String[] { "ABC", "KLM" } );
+        assertThat( plugin.getExcludedEnvironmentVariables() )
+                .hasSize( 2 )
+                .contains( "ABC", "KLM" );
+    }
 }
diff --git a/pom.xml b/pom.xml
index 1cc826b..cb87b3c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -646,6 +646,7 @@
               <rules>
                 <enforceBytecodeVersion>
                   <maxJdkVersion>${maven.compiler.target}</maxJdkVersion>
+                  <ignoredScopes>test</ignoredScopes>
                   <excludes>
                     <exclude>org.junit.platform:junit-platform-commons</exclude>
                   </excludes>
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..97342dc 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( "surefire.excludedEnvironmentVariables", "UNDEFINED_VAR" )
+                .executeTest();
     }
 
 }
\ No newline at end of file