Skip to content

Commit e9898f5

Browse files
authored
Add tests for Bazel sourceroot inference (#967)
1 parent c1aa242 commit e9898f5

3 files changed

Lines changed: 169 additions & 1 deletion

File tree

‎scip-javac/build.gradle.kts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ tasks.named<JavaCompile>("compileJava") {
2222
}
2323
}
2424

25+
tasks.named<JavaCompile>("compileTestJava") {
26+
// Tests use `@ClientCodeWrapper.Trusted` (an internal javac API) to construct file objects
27+
// that mimic Bazel's compiler. `--add-exports` is incompatible with `--release`.
28+
options.release.set(null as Int?)
29+
options.compilerArgs.addAll(JavacInternals.jvmOptions(rootDir))
30+
}
31+
2532
tasks.named<Test>("test") {
2633
jvmArgs(JavacInternals.jvmOptions(rootDir))
2734
}

‎scip-javac/src/main/java/org/scip_code/scip_java/javac/ScipTaskListener.java‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ public static Path absolutePathFromUri(ScipJavacOptions options, JavaFileObject
177177
}
178178

179179
// Infers the `-sourceroot:` flag from the provided file.
180-
// FIXME: add unit tests https://github.com/scip-code/scip-java/issues/444
181180
private void inferBazelSourceroot(JavaFileObject file) {
182181
if (options.uriScheme != UriScheme.BAZEL || options.sourceroot != null) return;
183182
Path absolutePath = absolutePathFromUri(options, file);
@@ -195,6 +194,13 @@ private void inferBazelSourceroot(JavaFileObject file) {
195194
if (!uriName.equals(pathName)) break;
196195
relativePathDepth++;
197196
}
197+
if (relativePathDepth == absolutePathDepth) {
198+
// Every name of absolutePath is a suffix of uriPath (for example, when both paths are
199+
// identical because the file object's toString() didn't match a known Bazel pattern).
200+
// There is no prefix left to use as the sourceroot, so fall back to the filesystem root.
201+
options.sourceroot = absolutePath.getRoot();
202+
return;
203+
}
198204
options.sourceroot =
199205
absolutePath
200206
.getRoot()
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package org.scip_code.scip_java.javac;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import com.sun.tools.javac.api.ClientCodeWrapper;
7+
import java.io.File;
8+
import java.io.IOException;
9+
import java.io.StringWriter;
10+
import java.io.UncheckedIOException;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.util.Arrays;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import javax.tools.JavaCompiler;
17+
import javax.tools.JavaFileObject;
18+
import javax.tools.SimpleJavaFileObject;
19+
import javax.tools.ToolProvider;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.io.TempDir;
22+
import org.scip_code.scip.Document;
23+
import org.scip_code.scip.Index;
24+
25+
/**
26+
* Tests the automatic sourceroot inference of {@code -build-tool:bazel} compilations (see {@code
27+
* ScipTaskListener.inferBazelSourceroot}) by driving the real compiler with file objects that mimic
28+
* the ones constructed by Bazel's Java compiler.
29+
*/
30+
class BazelSourcerootTest {
31+
32+
/**
33+
* Mimics the {@code com.sun.tools.javac.file.PathFileObject.SimpleFileObject} instances that the
34+
* plugin sees under Bazel: {@code toUri()} points to the file in a sandbox/temporary directory
35+
* while {@code toString()} renders a "human-readable" path pointing to the original source file.
36+
*
37+
* <p>The {@link ClientCodeWrapper.Trusted} annotation stops javac from wrapping this file object
38+
* in a {@code WrappedJavaFileObject}, which would change the result of {@code toString()}.
39+
* Bazel's file objects are never wrapped because javac-internal classes are trusted.
40+
*/
41+
@ClientCodeWrapper.Trusted
42+
private static final class BazelSourceFile extends SimpleJavaFileObject {
43+
private final Path humanReadablePath;
44+
private final String content;
45+
46+
BazelSourceFile(Path sandboxPath, Path humanReadablePath, String content) {
47+
super(sandboxPath.toUri(), Kind.SOURCE);
48+
this.humanReadablePath = humanReadablePath;
49+
this.content = content;
50+
}
51+
52+
@Override
53+
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
54+
return content;
55+
}
56+
57+
@Override
58+
public String toString() {
59+
return "SimpleFileObject[" + humanReadablePath + "]";
60+
}
61+
}
62+
63+
private static void compile(Path targetroot, List<JavaFileObject> compilationUnits) {
64+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
65+
SimpleFileManager fileManager =
66+
new SimpleFileManager(compiler.getStandardFileManager(null, null, null), targetroot);
67+
StringWriter output = new StringWriter();
68+
List<String> arguments =
69+
Arrays.asList(
70+
"-processorpath",
71+
TestCompiler.PROCESSOR_PATH,
72+
"-classpath",
73+
TestCompiler.PROCESSOR_PATH,
74+
"-Xplugin:scip -build-tool:bazel");
75+
JavaCompiler.CompilationTask task =
76+
compiler.getTask(output, fileManager, null, arguments, null, compilationUnits);
77+
boolean isSuccess = task.call();
78+
assertTrue(isSuccess, () -> "compilation should succeed, compiler output:\n" + output);
79+
}
80+
81+
private static Document readShardDocument(Path shardPath) {
82+
assertTrue(Files.isRegularFile(shardPath), () -> "expected SCIP shard at " + shardPath);
83+
try {
84+
Index shard = Index.parseFrom(Files.readAllBytes(shardPath));
85+
assertEquals(1, shard.getDocumentsCount());
86+
return shard.getDocuments(0);
87+
} catch (IOException e) {
88+
throw new UncheckedIOException(e);
89+
}
90+
}
91+
92+
@Test
93+
void sandboxedCompilation(
94+
@TempDir Path workspace, @TempDir Path sandbox, @TempDir Path targetroot) {
95+
// The typical case: javac sees the source files in a sandbox directory while their
96+
// human-readable paths point into the workspace. The sourceroot is inferred from the
97+
// first compilation unit (the longest common suffix of the two paths is
98+
// src/com/example/Hello.java, what remains is the workspace directory) and shards are
99+
// written under the workspace-relative path of each source file.
100+
Path execroot = sandbox.resolve("execroot").resolve("_main");
101+
compile(
102+
targetroot,
103+
Arrays.asList(
104+
new BazelSourceFile(
105+
execroot.resolve("src/com/example/Hello.java"),
106+
workspace.resolve("src/com/example/Hello.java"),
107+
"package com.example;\npublic class Hello {}"),
108+
new BazelSourceFile(
109+
execroot.resolve("src/com/example/inner/World.java"),
110+
workspace.resolve("src/com/example/inner/World.java"),
111+
"package com.example.inner;\npublic class World {}")));
112+
Path scipRoot = targetroot.resolve("META-INF").resolve("scip");
113+
Document hello = readShardDocument(scipRoot.resolve("src/com/example/Hello.java.scip"));
114+
assertEquals("src/com/example/Hello.java", hello.getRelativePath());
115+
Document world = readShardDocument(scipRoot.resolve("src/com/example/inner/World.java.scip"));
116+
assertEquals("src/com/example/inner/World.java", world.getRelativePath());
117+
}
118+
119+
@Test
120+
void onlyFileNameInCommon(
121+
@TempDir Path workspace, @TempDir Path sandbox, @TempDir Path targetroot) {
122+
// When the sandbox layout shares nothing with the workspace layout except the file
123+
// name, the inferred sourceroot is the parent directory of the source file.
124+
compile(
125+
targetroot,
126+
Collections.singletonList(
127+
new BazelSourceFile(
128+
sandbox.resolve("Hello.java"),
129+
workspace.resolve("nested/dir/Hello.java"),
130+
"public class Hello {}")));
131+
Document document =
132+
readShardDocument(
133+
targetroot.resolve("META-INF").resolve("scip").resolve("Hello.java.scip"));
134+
assertEquals("Hello.java", document.getRelativePath());
135+
}
136+
137+
@Test
138+
void unrecognizedFileObjectsFallBackToFilesystemRoot(
139+
@TempDir Path sandbox, @TempDir Path targetroot) {
140+
// SimpleSourceFile's toString() doesn't match any of the known Bazel file object
141+
// patterns, so the plugin falls back to using the URI path as the human-readable path.
142+
// The two paths are then identical and the inferred sourceroot degenerates to the
143+
// filesystem root: shards are written under the full path of each source file.
144+
Path source = sandbox.resolve("com/example/Hello.java");
145+
compile(
146+
targetroot,
147+
Collections.singletonList(
148+
new SimpleSourceFile(source, "package com.example;\npublic class Hello {}")));
149+
Path relative = source.getRoot().relativize(source);
150+
Document document =
151+
readShardDocument(
152+
targetroot.resolve("META-INF").resolve("scip").resolve(relative + ".scip"));
153+
assertEquals(relative.toString().replace(File.separatorChar, '/'), document.getRelativePath());
154+
}
155+
}

0 commit comments

Comments
 (0)