Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import javax.annotation.Nullable;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Properties;

Expand Down Expand Up @@ -77,9 +78,11 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory)
throws SurefireBooterForkException {
cli.addEnvironment("CLASSPATH", join(toCompleteClasspath(config).iterator(), File.pathSeparator));
List<String> classpath = toCompleteClasspath(config);
cli.addEnvironment("CLASSPATH", join(classpath.iterator(), File.pathSeparator));
cli.createArg().setValue(booterThatHasMainMethod);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ protected abstract void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory)
throws SurefireBooterForkException;

Expand Down Expand Up @@ -153,7 +154,8 @@ public Commandline createCommandLine(
try {
Commandline cli = new Commandline(getExcludedEnvironmentVariables());

cli.setWorkingDirectory(getWorkingDirectory(forkNumber).getAbsolutePath());
File cwd = getWorkingDirectory(forkNumber);
cli.setWorkingDirectory(cwd.getAbsolutePath());

for (Entry<String, String> entry : getEnvironmentVariables().entrySet()) {
String value = entry.getValue();
Expand All @@ -174,7 +176,7 @@ public Commandline createCommandLine(
cli.createArg().setLine(getDebugLine());
}

resolveClasspath(cli, findStartClass(config), config, dumpLogDirectory);
resolveClasspath(cli, findStartClass(config), config, cwd, dumpLogDirectory);

return cli;
} catch (CommandLineException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,12 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory)
throws SurefireBooterForkException {
try {
File jar = createJar(toCompleteClasspath(config), booterThatHasMainMethod, dumpLogDirectory);
List<String> classpath = toCompleteClasspath(config);
File jar = createJar(classpath, booterThatHasMainMethod, workingDirectory, dumpLogDirectory);
cli.createArg().setValue("-jar");
cli.createArg().setValue(escapeToPlatformPath(jar.getAbsolutePath()));
} catch (IOException e) {
Expand All @@ -116,20 +118,25 @@ protected void resolveClasspath(
* Create a jar with just a manifest containing a Main-Class entry for BooterConfiguration and a Class-Path entry
* for all classpath elements.
*
* @param classPath List&lt;String&gt; of all classpath elements
* @param startClassName the class name to start (main-class)
* @param classPath List&lt;String&gt; of all classpath elements
* @param startClassName the class name to start (main-class)
* @param workingDirectory the fork's working directory; relative classpath elements are resolved against it
* @return file of the jar
* @throws IOException when a file operation fails
*/
@Nonnull
private File createJar(
@Nonnull List<String> classPath, @Nonnull String startClassName, @Nonnull File dumpLogDirectory)
@Nonnull List<String> classPath,
@Nonnull String startClassName,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory)
throws IOException {
File file = TempFileManager.instance(getTempDirectory()).createTempFile("surefirebooter", ".jar");
if (!isDebug()) {
file.deleteOnExit();
}
Path parent = file.getParentFile().toPath();
Path workingDirectoryPath = workingDirectory.toPath().toAbsolutePath().normalize();
OutputStream fos = new BufferedOutputStream(new FileOutputStream(file), 64 * 1024);

try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(fos)) {
Expand All @@ -147,7 +154,13 @@ private File createJar(
// the end of directory entries - otherwise the jvm will ignore them.
StringBuilder cp = new StringBuilder();
for (Iterator<String> it = classPath.iterator(); it.hasNext(); ) {
Path classPathElement = Paths.get(it.next());
Path rawElement = Paths.get(it.next());
// Relative classpath elements are resolved against the fork's working directory so that
// the resulting manifest-JAR entry resolves to the same location as a direct -cp argument
// would (where the JVM resolves relative entries against its working directory).
Path classPathElement = rawElement.isAbsolute()
? rawElement
: workingDirectoryPath.resolve(rawElement).normalize();
ClasspathElementUri classpathElementUri =
toClasspathElementUri(parent, classPathElement, dumpLogDirectory, dumpError);
// too many errors in dump file with the same root cause may slow down the Boot Manifest-JAR startup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String startClass,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory)
throws SurefireBooterForkException {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -144,6 +145,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -176,6 +178,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -208,6 +211,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -241,6 +245,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -273,6 +278,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -305,6 +311,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -337,6 +344,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -407,4 +415,5 @@ private static <T> T invokeMethod(Object target, String methodName, Class<?>[] p
}
throw new NoSuchMethodException(methodName);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.surefire.JdkAttributes;
Expand Down Expand Up @@ -123,6 +125,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -177,6 +180,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -287,6 +291,7 @@ protected void resolveClasspath(
@Nonnull Commandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File workingDirectory,
@Nonnull File dumpLogDirectory) {}
};

Expand Down Expand Up @@ -462,6 +467,83 @@ private static ForkConfiguration getForkConfiguration(File basedir, String argLi
mock(ForkNodeFactory.class));
}

/**
* Verifies that a relative {@code additionalClasspathElement} (e.g. {@code ../classes}, which is
* correct relative to the fork's {@code workingDirectory}) ends up at the right absolute location
* inside the manifest JAR's {@code Class-Path}, regardless of where the manifest JAR itself is
* stored.
*
* <p>Without the fix, Surefire wrote the raw relative token {@code ../classes} into the manifest,
* where the JVM resolved it against the manifest-JAR directory rather than against the fork's
* working directory – silently pointing at the wrong location.
*/
@Test
public void testRelativeClasspathElementResolvedAgainstWorkingDirectory()
throws IOException, SurefireBooterForkException {
// Layout:
// basedir/build-test-dir-1/bin/ <- workingDirectory
// basedir/build-test-dir-1/classes/ <- the directory we want on the classpath
File forkDir = new File(basedir, "build-test-dir-1");
File workingDir = new File(forkDir, "bin");
File classesDir = new File(forkDir, "classes");
assertTrue(workingDir.mkdirs());
assertTrue(classesDir.mkdirs());

// Relative element as a user would write in pom.xml <additionalClasspathElement>../classes
// The JVM resolves -cp entries against its working directory, so ../classes from bin/ == classes/.
File cpElement = classesDir;
List<String> cp = singletonList(cpElement.getAbsolutePath());
ClasspathConfiguration cpConfig =
new ClasspathConfiguration(new Classpath(cp), emptyClasspath(), emptyClasspath(), true, true);
ClassLoaderConfiguration clc = new ClassLoaderConfiguration(true, true);
StartupConfiguration startup =
new StartupConfiguration("", cpConfig, clc, ALL, Collections.<String[]>emptyList());

ForkConfiguration config = getForkConfiguration(workingDir.getCanonicalFile());
org.apache.maven.surefire.shared.utils.cli.Commandline cli =
config.createCommandLine(startup, 1, getTempDirectory());

// The command line must use -jar (manifest-only JAR mode)
String line = join(" ", cli.getCommandline());
assertThat(line).contains("-jar");

// Extract the path of the manifest JAR from the command line
String[] parts = cli.getCommandline();
String jarPath = null;
for (int i = 0; i < parts.length - 1; i++) {
if ("-jar".equals(parts[i])) {
jarPath = parts[i + 1];
break;
}
}
assertThat(jarPath).isNotNull();

// Read the Class-Path from the manifest
try (JarFile jar = new JarFile(new File(jarPath))) {
Manifest manifest = jar.getManifest();
String classPath = manifest.getMainAttributes().getValue("Class-Path");
assertThat(classPath).isNotNull();

// The Class-Path entry for classesDir must, when resolved against the manifest JAR's
// parent directory, yield the canonical path of classesDir.
File manifestJar = new File(jarPath);
for (String entry : classPath.split(" ")) {
if (entry.isEmpty()) {
continue;
}
// entries are URI-encoded; decode and resolve against manifest-jar parent
String decoded = java.net.URLDecoder.decode(entry.replace("+", "%2B"), "UTF-8");
File resolved = new File(manifestJar.getParentFile(), decoded.replace('/', File.separatorChar));
if (resolved.getCanonicalPath().equals(classesDir.getCanonicalPath())
|| resolved.getCanonicalPath().equals(classesDir.getCanonicalPath() + File.separator)) {
return; // found – test passes
}
}
fail("Class-Path in manifest JAR does not resolve to " + classesDir.getCanonicalPath()
+ "; actual Class-Path: " + classPath);
}
}

// based on http://stackoverflow.com/questions/2591083/getting-version-of-java-in-runtime
@SuppressWarnings("checkstyle:magicnumber")
private static boolean isJavaVersionAtLeast7u60() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public void shouldCreateModularArgsFile() throws Exception {
cli,
ForkedBooter.class.getName(),
startupConfiguration,
pwd,
SureFireFileManager.createTempFile("surefire", "surefire-reports"));

assertThat(cli.getArguments()).isNotNull();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.surefire.its;

import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
import org.junit.jupiter.api.Test;

/**
* Integration test for relative {@code additionalClasspathElement} entries resolved against
* the fork's working directory (regression guard for the manifest-JAR path-resolution bug).
*/
public class AdditionalClasspathForkIT extends SurefireJUnit4IntegrationTestCase {

/**
* Verifies that a relative {@code additionalClasspathElement} (e.g. {@code ../cp-extra}) is
* resolved against the fork's {@code workingDirectory}, not against the Maven base directory
* or the location of the manifest-only JAR.
*/
@Test
public void relativeClasspathElementResolvedAgainstWorkingDirectory() {
unpack("/additional-classpath-relative-workdir").executeTest().verifyErrorFree(1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
found-via-relative-cp
Loading