Skip to content

Commit

Permalink
Evaluate "without_test_code" when resolving the runtime classpath #327
Browse files Browse the repository at this point in the history
When resolving the runtime classpath for project dependencies the
attribute "without_test_code" has to be evaluated so that paths marked
as test code from the included project do not contribute to the
classpath. The contained classes or resources should not be accessible
(e.g. via reflection).

Fixes #327
  • Loading branch information
oleosterhagen committed Oct 27, 2023
1 parent 267e301 commit c12d9f7
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Contributors:
* IBM Corporation - initial API and implementation
* SAP SE - Support hyperlinks for stack entries with method signature
* Ole Osterhagen - Issue 327 - Attribute "Without test code" is ignored in the launcher
*******************************************************************************/
package org.eclipse.jdt.debug.tests;

Expand Down Expand Up @@ -84,6 +85,7 @@
import org.eclipse.jdt.debug.tests.core.LocalVariableTests;
import org.eclipse.jdt.debug.tests.core.ModuleOptionsTests;
import org.eclipse.jdt.debug.tests.core.ProcessTests;
import org.eclipse.jdt.debug.tests.core.ResolveRuntimeClasspathTests;
import org.eclipse.jdt.debug.tests.core.RuntimeClasspathEntryTests;
import org.eclipse.jdt.debug.tests.core.StaticVariableTests;
import org.eclipse.jdt.debug.tests.core.StratumTests;
Expand Down Expand Up @@ -250,6 +252,7 @@ public AutomatedSuite() {
addTest(new TestSuite(ClasspathProviderTests.class));
addTest(new TestSuite(BootpathTests.class));
addTest(new TestSuite(EEDefinitionTests.class));
addTest(new TestSuite(ResolveRuntimeClasspathTests.class));
if (JavaProjectHelper.isJava9Compatible()) {
addTest(new TestSuite(ModuleOptionsTests.class));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*******************************************************************************
* Copyright (c) 2023 Ole Osterhagen and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Ole Osterhagen - Issue 327 - Attribute "Without test code" is ignored in the launcher
*******************************************************************************/
package org.eclipse.jdt.debug.tests.core;

import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.debug.testplugin.JavaProjectHelper;
import org.eclipse.jdt.debug.tests.AbstractDebugTest;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.JavaRuntime;

/**
* Tests for resolving the runtime classpath
*/
public class ResolveRuntimeClasspathTests extends AbstractDebugTest {

private IProject projectA;
private IProject projectB;

public ResolveRuntimeClasspathTests(String name) {
super(name);
}

@Override
protected void setUp() throws Exception {
super.setUp();
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
projectA = root.getProject("projectA");
projectB = root.getProject("projectB");
}

@Override
protected void tearDown() throws Exception {
try {
projectA.delete(true, null);
} catch (CoreException e) {
// ignore
}
try {
projectB.delete(true, null);
} catch (CoreException e) {
// ignore
}
super.tearDown();
}

public void testInclusionOfTestCode() throws CoreException {
IJavaProject project = createProjectWithProjectDependency(false);
IRuntimeClasspathEntry[] unresolved = JavaRuntime.computeUnresolvedRuntimeClasspath(project);
// same as "excludeTestCode=false"
IRuntimeClasspathEntry[] resolved = JavaRuntime.resolveRuntimeClasspathEntry(unresolved[0], project);

assertTrue(isOnRuntimeClasspath(resolved, "/projectA/bin/main"));
assertTrue(isOnRuntimeClasspath(resolved, "/projectA/bin/test"));
}

public void testExclusionOfTestCode() throws CoreException {
IJavaProject project = createProjectWithProjectDependency(false);
IRuntimeClasspathEntry[] unresolved = JavaRuntime.computeUnresolvedRuntimeClasspath(project);
IRuntimeClasspathEntry[] resolved = JavaRuntime.resolveRuntimeClasspathEntry(unresolved[0], project, true);

assertTrue(isOnRuntimeClasspath(resolved, "/projectA/bin/main"));
assertFalse(isOnRuntimeClasspath(resolved, "/projectA/bin/test"));
}

public void testExclusionOfTestCodeFromDependencyForProject() throws CoreException {
IJavaProject project = createProjectWithProjectDependency(true);
IRuntimeClasspathEntry[] unresolved = JavaRuntime.computeUnresolvedRuntimeClasspath(project);
IRuntimeClasspathEntry[] resolved = JavaRuntime.resolveRuntimeClasspathEntry(unresolved[0], project, false);

assertTrue(isOnRuntimeClasspath(resolved, "/projectA/bin/main"));
// even with "excludeTestCode=false" test code from project A is not accessible
assertFalse(isOnRuntimeClasspath(resolved, "/projectA/bin/test"));
}

public void testExclusionOfTestCodeFromDependencyForLaunchConfiguration() throws Exception {
IJavaProject project = createProjectWithProjectDependency(true);
IRuntimeClasspathEntry[] unresolved = JavaRuntime.computeUnresolvedRuntimeClasspath(project);
IRuntimeClasspathEntry[] resolved = JavaRuntime.resolveRuntimeClasspathEntry(unresolved[0], createLaunchConfiguration("ResolveRuntimeClasspathTests"));

assertTrue(isOnRuntimeClasspath(resolved, "/projectA/bin/main"));
assertFalse(isOnRuntimeClasspath(resolved, "/projectA/bin/test"));
}

private boolean isOnRuntimeClasspath(IRuntimeClasspathEntry[] runtimeClasspathEntries, String path) {
for (IRuntimeClasspathEntry runtimeClasspathEntry : runtimeClasspathEntries) {
if (runtimeClasspathEntry.getPath().equals(new Path(path))) {
return true;
}
}
return false;
}

/**
* Creates two Java projects A and B. Project A contains a folder with non-test code and a folder with test code. Project B depends on project A.
*
* <ol>
* <li>Classpath of project A
* <ul>
* <li>path="main", output="bin/main"
* <li>path="test", output="bin/test", test=true
* </ul>
* <li>Classpath of project B
* <ul>
* <li>path="/projectA", without_test_code=true or false
* </ul>
* </ol>
*
* @param withoutTestCode
* {@code true} if project B can access test code from project A, otherwise {@code false}.
* @return Java project B
* @throws CoreException
* if this method fails.
*/
private IJavaProject createProjectWithProjectDependency(boolean withoutTestCode) throws CoreException {
// create and configure project A
IJavaProject javaProjectA = JavaProjectHelper.createJavaProject(projectA.getName());
IFolder mainFolder = createFolders(projectA, "main");
IFolder testFolder = createFolders(projectA, "test");
IFolder binMainFolder = createFolders(projectA, "bin/main");
IFolder binTestFolder = createFolders(projectA, "bin/test");
JavaProjectHelper.addToClasspath(javaProjectA, JavaCore.newSourceEntry(mainFolder.getFullPath(), new IPath[0], new IPath[0], binMainFolder.getFullPath(), new IClasspathAttribute[0]));
JavaProjectHelper.addToClasspath(javaProjectA, JavaCore.newSourceEntry(testFolder.getFullPath(), new IPath[0], new IPath[0], binTestFolder.getFullPath(), new IClasspathAttribute[] {
JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true") }));

// create and configure project B
IJavaProject javaProjectB = JavaProjectHelper.createJavaProject(projectB.getName());
JavaProjectHelper.addToClasspath(javaProjectB, JavaCore.newProjectEntry(projectA.getFullPath(), new IAccessRule[0], true, new IClasspathAttribute[] {
JavaCore.newClasspathAttribute(IClasspathAttribute.WITHOUT_TEST_CODE, Boolean.toString(withoutTestCode)) }, false));

return javaProjectB;
}

private IFolder createFolders(IProject project, String path) throws CoreException {
IFolder folder = null;
for (String segment : path.split("/")) {
folder = folder != null ? folder.getFolder(segment) : project.getFolder(segment);
if (!folder.exists()) {
folder.create(true, true, null);
}
}
return folder;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* IBM Corporation - initial API and implementation
* Frits Jalvingh - Contribution for Bug 459831 - [launching] Support attaching
* external annotations to a JRE container
* Ole Osterhagen - Issue 327 - Attribute "Without test code" is ignored in the launcher
*******************************************************************************/
package org.eclipse.jdt.launching;

Expand Down Expand Up @@ -1232,7 +1233,9 @@ public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClas
return new IRuntimeClasspathEntry[0];
}
IClasspathAttribute[] attributes = entry.getClasspathEntry().getExtraAttributes();
IRuntimeClasspathEntry[] entries = resolveOutputLocations(project, entry.getClasspathProperty(), attributes, excludeTestCode);
boolean withoutTestCode = entry.getClasspathEntry().isWithoutTestCode();
IRuntimeClasspathEntry[] entries = resolveOutputLocations(project, entry.getClasspathProperty(), attributes, excludeTestCode
|| withoutTestCode);
if (entries != null) {
return entries;
}
Expand Down Expand Up @@ -1477,7 +1480,9 @@ public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClas
IJavaProject jp = JavaCore.create(p);
if (jp != null && p.isOpen() && jp.exists()) {
IClasspathAttribute[] attributes = entry.getClasspathEntry().getExtraAttributes();
IRuntimeClasspathEntry[] entries = resolveOutputLocations(jp, entry.getClasspathProperty(), attributes, excludeTestCode);
boolean withoutTestCode = entry.getClasspathEntry().isWithoutTestCode();
IRuntimeClasspathEntry[] entries = resolveOutputLocations(jp, entry.getClasspathProperty(), attributes, excludeTestCode
|| withoutTestCode);
if (entries != null) {
return entries;
}
Expand Down

0 comments on commit c12d9f7

Please sign in to comment.