jdk/test/langtools/tools/javac/IncrementalComp/TestIncrementalComp.java
Jorn Vernee 299a42b3ce 8383882: javac: incremental compilation using --module misses classes
Reviewed-by: jlahoda, mcimadamore, vromero
2026-06-29 21:50:33 +00:00

294 lines
11 KiB
Java

/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Test javac incremental compilation with modules
* @run junit TestIncrementalComp
*/
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.spi.ToolProvider;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TestIncrementalComp {
static final ToolProvider JAVAC = ToolProvider.findFirst("javac")
.orElseThrow();
record TestCase(String srcDir, Map<Path, String> sources, Set<String> modules, String mainModule, Map<String, String> addReadsEdges) {
TestCase(String srcDir, Map<Path, String> sources, Set<String> modules, String mainModule) {
this(srcDir, sources, modules, mainModule, Map.of());
}
}
@ParameterizedTest
@MethodSource("cases")
public void test(TestCase testCase) throws Throwable {
Path workDir = Path.of(testCase.srcDir());
// set up test sources
Path localTestModules = workDir.resolve("test_modules");
Path outDir = workDir.resolve("mods");
for (Map.Entry<Path, String> sourceFile : testCase.sources().entrySet()) {
Path filePath = localTestModules.resolve(sourceFile.getKey());
Files.createDirectories(filePath.getParent());
Files.writeString(filePath, sourceFile.getValue(), CREATE_NEW);
}
Path libPath = localTestModules.resolve(LIB_PATH);
Files.createDirectories(libPath.getParent());
Files.writeString(libPath, ALT_LIB_INT, CREATE_NEW);
List<String> javacCommand = new ArrayList<>(List.of(
"-d", outDir.toString(),
"--module-source-path=" + localTestModules,
"--module", String.join(",", testCase.modules())
));
for (Map.Entry<String, String> addReads : testCase.addReadsEdges().entrySet()) {
String reader = addReads.getKey();
String read = addReads.getValue();
javacCommand.add(String.format("--add-reads=%s=%s", reader, read));
}
// compile both modules
compile(javacCommand);
String mainClass = testCase.mainModule() + ".app.Main";
invokeMainMethod(outDir, testCase.mainModule(), mainClass, testCase.addReadsEdges());
// modify sources. Dep is not modified
Files.writeString(libPath, ALT_LIB_LONG, TRUNCATE_EXISTING);
// recompile. Any dependency on the changed file should be recompiled as well
compile(javacCommand);
// should work
// if this fails because of incremental compilation issues, we can expect to see a NoSuchMethodError
invokeMainMethod(outDir, testCase.mainModule(), mainClass, testCase.addReadsEdges());
}
private static void invokeMainMethod(Path modulePath, String moduleName, String mainClassName,
Map<String, String> addReadsEdges)
throws ReflectiveOperationException {
// define module layer
// note that we need to explicitly add any read module to the set of roots
ModuleLayer boot = ModuleLayer.boot();
Set<String> allRoots = Stream.concat(Stream.of(moduleName), addReadsEdges.values().stream())
.collect(Collectors.toSet());
Configuration config = boot.configuration()
.resolve(ModuleFinder.of(modulePath), ModuleFinder.of(), allRoots);
ModuleLayer.Controller controller = ModuleLayer.defineModulesWithOneLoader(
config, List.of(boot), ClassLoader.getSystemClassLoader());
// add extra reads edges
for (Map.Entry<String, String> addReads : addReadsEdges.entrySet()) {
Module reader = controller.layer().findModule(addReads.getKey()).orElseThrow();
Module read = controller.layer().findModule(addReads.getValue()).orElseThrow();
controller.addReads(reader, read);
}
// invoke main
Class<?> main1 = controller.layer().findLoader(moduleName).loadClass(mainClassName);
Method m = main1.getMethod("main", String[].class);
m.invoke(null, new Object[]{ new String[0] });
}
private static void compile(List<String> args) {
System.err.println("compile: " + args);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
int rc = JAVAC.run(pw, pw, args.toArray(String[]::new));
pw.close();
System.err.println(sw);
assertEquals(0, rc);
}
private static final Path LIB_PATH = Path.of("org.moda/org/moda/lib/Lib.java");
private static final String ALT_LIB_INT = """
package org.moda.lib;
public class Lib {
public static int getVal() {
return 42;
}
}
""";
private static final String ALT_LIB_LONG = """
package org.moda.lib;
public class Lib {
public static long getVal() {
return 42;
}
}
""";
static Stream<TestCase> cases() {
return Stream.of(
new TestCase("single", Map.of(
Path.of("org.moda/module-info.java"),
"""
module org.moda {
// for reflective access
exports org.moda.app;
}
""",
Path.of("org.moda/org/moda/lib/Dep.java"),
"""
package org.moda.lib;
public class Dep {
public static long getVal() {
return Lib.getVal();
}
}
""",
Path.of("org.moda/org/moda/app/Main.java"),
"""
package org.moda.app;
import org.moda.lib.Dep;
public class Main {
public static void main(String[] args) {
System.out.println(Dep.getVal());
}
}
"""
), Set.of("org.moda"), "org.moda"),
new TestCase("multi", Map.of(
Path.of("org.moda/module-info.java"),
"""
module org.moda {
exports org.moda.lib;
}
""",
Path.of("org.modb/module-info.java"),
"""
module org.modb {
requires org.moda;
// for reflective access
exports org.modb.app;
}
""",
Path.of("org.modb/org/modb/app/Main.java"),
"""
package org.modb.app;
import org.moda.lib.Lib;
public class Main {
public static void main(String[] args) {
System.out.println(Lib.getVal());
}
}
"""
), Set.of("org.moda", "org.modb"), "org.modb"),
new TestCase("transitive", Map.of(
Path.of("org.moda/module-info.java"),
"""
module org.moda {
exports org.moda.lib;
}
""",
Path.of("org.modb/module-info.java"),
"""
module org.modb {
// for org.modc
requires transitive org.moda;
}
""",
Path.of("org.modc/module-info.java"),
"""
module org.modc {
requires org.modb;
// for reflective access
exports org.modc.app;
}
""",
Path.of("org.modc/org/modc/app/Main.java"),
"""
package org.modc.app;
import org.moda.lib.Lib;
public class Main {
public static void main(String[] args) {
System.out.println(Lib.getVal());
}
}
"""
), Set.of("org.moda", "org.modb", "org.modc"), "org.modc"),
new TestCase("add_reads", Map.of(
Path.of("org.moda/module-info.java"),
"""
module org.moda {
exports org.moda.lib;
}
""",
Path.of("org.modb/module-info.java"),
"""
module org.modb {
// no explicit requires
// for reflective access
exports org.modb.app;
}
""",
Path.of("org.modb/org/modb/app/Main.java"),
"""
package org.modb.app;
import org.moda.lib.Lib;
public class Main {
public static void main(String[] args) {
System.out.println(Lib.getVal());
}
}
"""
), Set.of("org.moda", "org.modb"), "org.modb", Map.of("org.modb", "org.moda"))
);
}
}