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 70f95d9532..79db5c6f30 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 @@ -34,6 +34,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -42,6 +43,7 @@ import java.util.jar.JarOutputStream; import java.util.jar.Manifest; +import static java.nio.file.Files.isDirectory; import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath; /** @@ -116,10 +118,10 @@ private File createJar( @Nonnull List classPath, @Nonnull String startCl StringBuilder cp = new StringBuilder(); for ( Iterator it = classPath.iterator(); it.hasNext(); ) { - File classPathElement = new File( it.next() ); + Path classPathElement = Paths.get( it.next() ); String uri = toClasspathElementUri( parent, classPathElement, dumpLogDirectory ); cp.append( uri ); - if ( classPathElement.isDirectory() && !uri.endsWith( "/" ) ) + if ( isDirectory( classPathElement ) && !uri.endsWith( "/" ) ) { cp.append( '/' ); } @@ -143,24 +145,39 @@ private File createJar( @Nonnull List classPath, @Nonnull String startCl } } - private static String toClasspathElementUri( @Nonnull Path parent, - @Nonnull File classPathElement, - @Nonnull File dumpLogDirectory ) + static String relativize( @Nonnull Path parent, @Nonnull Path child ) + throws IllegalArgumentException + { + return parent.relativize( child ) + .toString(); + } + + static String toAbsoluteUri( @Nonnull Path absolutePath ) + { + return absolutePath.toUri() + .toASCIIString(); + } + + static String toClasspathElementUri( @Nonnull Path parent, + @Nonnull Path classPathElement, + @Nonnull File dumpLogDirectory ) throws IOException { try { - return new URI( null, parent.relativize( classPathElement.toPath() ).toString(), null ) + String relativeUriPath = relativize( parent, classPathElement ) + .replace( '\\', '/' ); + + return new URI( null, relativeUriPath, null ) .toASCIIString(); } catch ( IllegalArgumentException e ) { - String error = "Boot Manifest-JAR contains absolute paths in classpath " + classPathElement.getPath(); + String error = "Boot Manifest-JAR contains absolute paths in classpath " + classPathElement; InPluginProcessDumpSingleton.getSingleton() .dumpException( e, error, dumpLogDirectory ); - return classPathElement.toURI() - .toASCIIString(); + return toAbsoluteUri( classPathElement ); } catch ( URISyntaxException e ) { diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfigurationTest.java new file mode 100644 index 0000000000..18b205ab1b --- /dev/null +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfigurationTest.java @@ -0,0 +1,221 @@ +package org.apache.maven.plugin.surefire.booterclient; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; + +import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton; + +import static org.apache.maven.plugin.surefire.booterclient.JarManifestForkConfiguration.relativize; +import static org.apache.maven.plugin.surefire.booterclient.JarManifestForkConfiguration.toAbsoluteUri; +import static org.apache.maven.plugin.surefire.booterclient.JarManifestForkConfiguration.toClasspathElementUri; +import static org.fest.assertions.Assertions.assertThat; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.fest.util.Files.delete; +import static org.fest.util.Files.newTemporaryFolder; +import static org.mockito.ArgumentMatchers.same; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * Unit tests for {@link JarManifestForkConfiguration}. + */ +@RunWith( PowerMockRunner.class ) +@PrepareForTest( { JarManifestForkConfiguration.class, InPluginProcessDumpSingleton.class } ) +public class JarManifestForkConfigurationTest +{ + private static final File TMP = newTemporaryFolder(); + + private static File dumpDirectory; + + @BeforeClass + public static void createSystemTemporaryDir() + { + dumpDirectory = new File( TMP, "dump" ); + assertThat( dumpDirectory.mkdir() ) + .isTrue(); + } + + @AfterClass + public static void deleteSystemTemporaryDir() + { + delete( TMP ); + } + + @Test + public void relativeClasspathUnixSimple() + throws Exception + { + mockStatic( JarManifestForkConfiguration.class ); + Path parent = mock( Path.class ); + when( parent.toString() ).thenReturn( "/home/me/prj/target/surefire" ); + Path classPathElement = mock( Path.class ); + when( classPathElement.toString() ).thenReturn( "/home/me/.m2/repository/grp/art/1.0/art-1.0.jar" ); + when( relativize( parent, classPathElement ) ) + .thenReturn( "../../../.m2/repository/grp/art/1.0/art-1.0.jar" ); + when( toClasspathElementUri( same( parent ), same( classPathElement ), same( dumpDirectory ) ) ) + .thenCallRealMethod(); + assertThat( toClasspathElementUri( parent, classPathElement, dumpDirectory ) ) + .isEqualTo( "../../../.m2/repository/grp/art/1.0/art-1.0.jar" ); + } + + @Test + public void relativeClasspathUnixTricky() + throws Exception + { + mockStatic( JarManifestForkConfiguration.class ); + Path parent = mock( Path.class ); + when( parent.toString() ).thenReturn( "/home/me/prj/target/surefire" ); + Path classPathElement = mock( Path.class ); + when( classPathElement.toString() ).thenReturn( "/the Maven repo/grp/art/1.0/art-1.0.jar" ); + when( relativize( parent, classPathElement ) ) + .thenReturn( "../../../../../the Maven repo/grp/art/1.0/art-1.0.jar" ); + when( toClasspathElementUri( same( parent ), same( classPathElement ), same( dumpDirectory ) ) ) + .thenCallRealMethod(); + assertThat( toClasspathElementUri( parent, classPathElement, dumpDirectory ) ) + .isEqualTo( "../../../../../the%20Maven%20repo/grp/art/1.0/art-1.0.jar" ); + } + + @Test + public void relativeClasspathWindowsSimple() + throws Exception + { + mockStatic( JarManifestForkConfiguration.class ); + Path parent = mock( Path.class ); + when( parent.toString() ).thenReturn( "C:\\Windows\\Temp\\surefire" ); + Path classPathElement = mock( Path.class ); + when( classPathElement.toString() ).thenReturn( "C:\\Users\\me\\.m2\\repository\\grp\\art\\1.0\\art-1.0.jar" ); + when( relativize( parent, classPathElement ) ) + .thenReturn( "..\\..\\..\\Users\\me\\.m2\\repository\\grp\\art\\1.0\\art-1.0.jar" ); + when( toClasspathElementUri( same( parent ), same( classPathElement ), same( dumpDirectory ) ) ) + .thenCallRealMethod(); + assertThat( toClasspathElementUri( parent, classPathElement, dumpDirectory ) ) + .isEqualTo( "../../../Users/me/.m2/repository/grp/art/1.0/art-1.0.jar" ); + } + + @Test + public void relativeClasspathWindowsTricky() + throws Exception + { + mockStatic( JarManifestForkConfiguration.class ); + Path parent = mock( Path.class ); + when( parent.toString() ).thenReturn( "C:\\Windows\\Temp\\surefire" ); + Path classPathElement = mock( Path.class ); + when( classPathElement.toString() ).thenReturn( "C:\\Test User\\me\\.m2\\repository\\grp\\art\\1.0\\art-1.0.jar" ); + when( relativize( parent, classPathElement ) ) + .thenReturn( "..\\..\\..\\Test User\\me\\.m2\\repository\\grp\\art\\1.0\\art-1.0.jar" ); + when( toClasspathElementUri( same( parent ), same( classPathElement ), same( dumpDirectory ) ) ) + .thenCallRealMethod(); + assertThat( toClasspathElementUri( parent, classPathElement, dumpDirectory ) ) + .isEqualTo( "../../../Test%20User/me/.m2/repository/grp/art/1.0/art-1.0.jar" ); + } + + @Test + public void crossDriveWindows() + throws Exception + { + mockStatic( JarManifestForkConfiguration.class ); + mockStatic( InPluginProcessDumpSingleton.class ); + when( InPluginProcessDumpSingleton.getSingleton() ).thenReturn( mock( InPluginProcessDumpSingleton.class ) ); + Path parent = mock( Path.class ); + when( parent.toString() ).thenReturn( "C:\\Windows\\Temp\\surefire" ); + Path classPathElement = mock( Path.class ); + when( classPathElement.toString() ).thenReturn( "X:\\Users\\me\\.m2\\repository\\grp\\art\\1.0\\art-1.0.jar" ); + when( classPathElement.toUri() ) + .thenAnswer( new Answer() + { + @Override + public URI answer( InvocationOnMock invocation ) throws URISyntaxException + { + String path = invocation.getMock().toString(); + return new URI( "file", "", "/" + path.replace( '\\', '/' ), null ); + } + } ); + when( relativize( same( parent ), same( classPathElement ) ) ) + .thenThrow( new IllegalArgumentException() ); + when( toClasspathElementUri( same( parent ), same( classPathElement ), same( dumpDirectory ) ) ) + .thenCallRealMethod(); + when( toAbsoluteUri( same( classPathElement ) ) ) + .thenCallRealMethod(); + assertThat( toClasspathElementUri( parent, classPathElement, dumpDirectory ) ) + .isEqualTo( "file:///X:/Users/me/.m2/repository/grp/art/1.0/art-1.0.jar" ); + } + + @Test + public void shouldRelativizeOnRealPlatform() + { + Path parentDir = new File( TMP, "test-parent-1" ) + .toPath(); + + Path testDir = new File( TMP, "@1 test with white spaces" ) + .toPath(); + + String relativeTestDir = relativize( parentDir, testDir ); + + assertThat( relativeTestDir ) + .isEqualTo( ".." + File.separator + "@1 test with white spaces" ); + } + + @Test + public void shouldMakeAbsoluteUriOnRealPlatform() + throws Exception + { + Path testDir = new File( TMP, "@2 test with white spaces" ) + .toPath(); + + URI testDirUri = new URI( toAbsoluteUri( testDir ) ); + + assertThat( testDirUri.getScheme() ) + .isEqualTo( "file" ); + + assertThat( testDirUri.getRawPath() ) + .isEqualTo( testDir.toUri().getRawPath() ); + } + + @Test + public void shouldMakeRelativeUriOnRealPlatform() + throws Exception + { + Path parentDir = new File( TMP, "test-parent-2" ) + .toPath(); + + Path testDir = new File( TMP, "@3 test with white spaces" ) + .toPath(); + + String testDirUriPath = toClasspathElementUri( parentDir, testDir, dumpDirectory ); + + assertThat( testDirUriPath ) + .isEqualTo( "../@3%20test%20with%20white%20spaces" ); + } +} diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java index 2ca1b39689..2f1f01f2d0 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java @@ -34,6 +34,7 @@ import org.apache.maven.plugin.surefire.booterclient.DefaultForkConfigurationTest; import org.apache.maven.plugin.surefire.booterclient.ForkConfigurationTest; import org.apache.maven.plugin.surefire.booterclient.ForkingRunListenerTest; +import org.apache.maven.plugin.surefire.booterclient.JarManifestForkConfigurationTest; import org.apache.maven.plugin.surefire.booterclient.ModularClasspathForkConfigurationTest; import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStreamBuilderTest; import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest; @@ -85,6 +86,7 @@ public static Test suite() suite.addTest( new JUnit4TestAdapter( SurefireHelperTest.class ) ); suite.addTest( new JUnit4TestAdapter( AbstractSurefireMojoTest.class ) ); suite.addTest( new JUnit4TestAdapter( DefaultForkConfigurationTest.class ) ); + suite.addTest( new JUnit4TestAdapter( JarManifestForkConfigurationTest.class ) ); suite.addTest( new JUnit4TestAdapter( ModularClasspathForkConfigurationTest.class ) ); suite.addTest( new JUnit4TestAdapter( AbstractSurefireMojoJava7PlusTest.class ) ); suite.addTest( new JUnit4TestAdapter( ScannerUtilTest.class ) );