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 @@ -35,6 +35,10 @@ public class MultiReleaseTests extends BuilderTests {
private static final String JAVA9_SRC_FOLDER = "src9";
private static final String DEFAULT_SRC_FOLDER = "src";

// static {
// TESTS_NAMES = new String[] { "testMultiReleaseModuleInfoPerRelease" };
// }

public MultiReleaseTests(String name) {
Comment thread
laeubi marked this conversation as resolved.
super(name);
}
Expand Down Expand Up @@ -319,6 +323,92 @@ public String print() {
expectingNoProblems();
}

/**
* Test multi-release compilation with different module-info.java per release.
* This verifies that each source folder with a different release uses its own
* module-info.java for compilation, not a shared module from the project.
* See issue https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4268
*/
public void testMultiReleaseModuleInfoPerRelease() throws JavaModelException, IOException {
// Create modular project with Java 11 as base
IPath projectPath = createMRProject(CompilerOptions.VERSION_11);
IPath defaultSrc = env.getPackageFragmentRootPath(projectPath, DEFAULT_SRC_FOLDER);

// Base module-info requires no extra modules
env.addClass(defaultSrc, "", "module-info",
"""
module MRmodular {
}
"""
);

// Base Test.java - should have errors for both java.desktop and java.xml types
IPath classDefault = env.addClass(defaultSrc, "p", "Test",
"""
package p;
public class Test {
java.awt.Window w11;
org.w3c.dom.Element element11;
}
"""
);

// Java 17 source with module-info requiring java.desktop
IClasspathAttribute[] attributes17 = new IClasspathAttribute[] {
JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "17") };
IPath src17 = env.addPackageFragmentRoot(projectPath, "src17", attributes17);
env.addClass(src17, "", "module-info",
"""
module MRmodular {
requires java.desktop;
}
"""
);
env.addClass(src17, "p", "Test",
"""
package p;
public class Test {
java.awt.Window w17;
org.w3c.dom.Element element17;
}
"""
);

// Java 21 source with module-info requiring java.xml
IClasspathAttribute[] attributes21 = new IClasspathAttribute[] {
JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "21") };
IPath src21 = env.addPackageFragmentRoot(projectPath, "src21", attributes21);
env.addClass(src21, "", "module-info",
"""
module MRmodular {
requires java.xml;
}
"""
);
IPath class21 = env.addClass(src21, "p", "Test",
"""
package p;
public class Test {
java.awt.Window w21;
org.w3c.dom.Element element21;
}
"""
);

fullBuild();
//As our default module descriptor does not import anything both should give an error
expectingSpecificProblemsFor(defaultSrc, new Problem[] { //
new Problem("", "The type java.awt.Window is not accessible", classDefault, 32, 47, 40, IMarker.SEVERITY_ERROR),
new Problem("", "The type org.w3c.dom.Element is not accessible", classDefault, 54, 73, 40, IMarker.SEVERITY_ERROR)
});
//java.desktop includes java.xml so no errors to expect here
expectingNoProblemsFor(src17);
//we only import java.xml so desktop should give an error!
expectingSpecificProblemsFor(src21, new Problem[] { //
new Problem("", "The type java.awt.Window is not accessible", class21, 32, 47, 40, IMarker.SEVERITY_ERROR),
});
}

private IPath whenSetupMRRpoject() throws JavaModelException {
return whenSetupMRRpoject(CompilerOptions.VERSION_1_8);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*******************************************************************************
* Copyright (c) 2026 Christoph Läubrich 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
*******************************************************************************/
package org.eclipse.jdt.core.tests.model;

import junit.framework.Test;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;

/**
* Tests that {@link ASTParser#createASTs(ICompilationUnit[], String[], ASTRequestor, org.eclipse.core.runtime.IProgressMonitor)}
* resolves bindings as seen from the release specific source folder the given compilation
* units live in, honoring a release specific {@code module-info.java}.
*
* See https://github.com/eclipse-jdt/eclipse.jdt.core/pull/4534#discussion_r3660872811
*/
public class ASTParserMultiReleaseTests extends AbstractJavaModelTests {

static {
// TESTS_NAMES = new String[] { "testResolveWindowInRelease17" };
}

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

public static Test suite() {
return buildModelTestSuite(ASTParserMultiReleaseTests.class);
}

@Override
public void setUpSuite() throws Exception {
super.setUpSuite();
IJavaProject project = createJava9ProjectWithJREAttributes("ASTParserMR",
new String[] { "src", "src17", "src21" }, null, "21");
IClasspathEntry[] classpath = project.getRawClasspath();
for (int i = 0; i < classpath.length; i++) {
IClasspathEntry entry = classpath[i];
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
if (entry.getPath().toString().endsWith("src17")) {
classpath[i] = JavaCore.newSourceEntry(entry.getPath(), null, null, null,
new IClasspathAttribute[] {
JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "17") });
} else if (entry.getPath().toString().endsWith("src21")) {
classpath[i] = JavaCore.newSourceEntry(entry.getPath(), null, null, null,
new IClasspathAttribute[] {
JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "21") });
}
}
}
project.setRawClasspath(classpath, new NullProgressMonitor());
project.setOption(JavaCore.COMPILER_RELEASE, JavaCore.ENABLED);

createFolder("/ASTParserMR/src/p");
createFolder("/ASTParserMR/src17/p");
createFolder("/ASTParserMR/src21/p");

// base module requires nothing
createFile("/ASTParserMR/src/module-info.java", """
module MRastparser {
}
""");
createFile("/ASTParserMR/src/p/Test.java", """
package p;
public class Test {
java.awt.Window w;
org.w3c.dom.Element element;
}
""");

// release 17 requires java.desktop (which transitively reads java.xml)
createFile("/ASTParserMR/src17/module-info.java", """
module MRastparser {
requires java.desktop;
}
""");
createFile("/ASTParserMR/src17/p/Test.java", """
package p;
public class Test {
java.awt.Window w;
org.w3c.dom.Element element;
}
""");

// release 21 requires java.xml
createFile("/ASTParserMR/src21/module-info.java", """
module MRastparser {
requires java.xml;
}
""");
createFile("/ASTParserMR/src21/p/Test.java", """
package p;
public class Test {
java.awt.Window w;
org.w3c.dom.Element element;
}
""");
}

@Override
public void tearDownSuite() throws Exception {
deleteProject("ASTParserMR");
super.tearDownSuite();
}

private ITypeBinding resolveFieldType(String unitPath, String fieldName) throws Exception {
ICompilationUnit unit = getCompilationUnit(unitPath);
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
parser.setResolveBindings(true);
parser.setProject(getJavaProject("ASTParserMR"));
final ITypeBinding[] result = new ITypeBinding[1];
parser.createASTs(new ICompilationUnit[] { unit }, new String[0], new ASTRequestor() {
@Override
public void acceptAST(ICompilationUnit source, CompilationUnit ast) {
TypeDeclaration type = (TypeDeclaration) ast.types().get(0);
for (Object bodyDecl : type.bodyDeclarations()) {
if (bodyDecl instanceof FieldDeclaration field) {
VariableDeclarationFragment fragment = (VariableDeclarationFragment) field.fragments().get(0);
if (fragment.getName().getIdentifier().equals(fieldName)) {
result[0] = field.getType().resolveBinding();
}
}
}
}
}, null);
return result[0];
}

// org.w3c.dom.Element is reachable in src21 via 'requires java.xml': resolving its
// field type must succeed with a non-recovered binding for exactly that type.
public void testResolveElementInRelease21() throws Exception {
ITypeBinding binding = resolveFieldType("/ASTParserMR/src21/p/Test.java", "element");
assertNotNull("Field 'element' not resolved", binding);
assertFalse("Binding should not be recovered/unresolved", binding.isRecovered());
assertEquals("org.w3c.dom.Element", binding.getQualifiedName());
}

// java.awt.Window is reachable in src17 via 'requires java.desktop'.
public void testResolveWindowInRelease17() throws Exception {
ITypeBinding binding = resolveFieldType("/ASTParserMR/src17/p/Test.java", "w");
assertNotNull("Field 'w' not resolved", binding);
assertFalse("Binding should not be recovered/unresolved", binding.isRecovered());
assertEquals("java.awt.Window", binding.getQualifiedName());
}

// java.desktop reads java.xml transitively, so org.w3c.dom.Element is reachable in src17 too.
public void testResolveElementInRelease17() throws Exception {
ITypeBinding binding = resolveFieldType("/ASTParserMR/src17/p/Test.java", "element");
assertNotNull("Field 'element' not resolved", binding);
assertFalse("Binding should not be recovered/unresolved", binding.isRecovered());
assertEquals("org.w3c.dom.Element", binding.getQualifiedName());
}

// The base module-info requires neither java.desktop nor java.xml, so java.awt.Window
// is not accessible and its field type must fail to resolve to a proper binding.
public void testResolveWindowInBaseReleaseIsUnresolved() throws Exception {
ITypeBinding binding = resolveFieldType("/ASTParserMR/src/p/Test.java", "w");
assertTrue("Binding for inaccessible java.awt.Window should not resolve properly",
binding == null || binding.isRecovered());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ private static Class[] getAllTestClasses() {
ReconcilerTests21.class,
ReconcilerStatementsRecoveryTests.class,
ReconcilerMultiReleaseTests.class,
ReconcilerModuleMultiReleaseTests.class,
SelectionMultiReleaseTests.class,
ASTParserMultiReleaseTests.class,
HierarchyMultiReleaseTests.class,

// Copy and move operation tests
CopyMoveElementsTests.class,
Expand Down
Loading
Loading