Skip to content

Commit 629438a

Browse files
authored
Migrate tests/buildTools to JUnit5 (#914)
1 parent a241f90 commit 629438a

71 files changed

Lines changed: 749 additions & 1085 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.sbt

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -567,18 +567,31 @@ lazy val unit = project
567567

568568
lazy val buildTools = project
569569
.in(file("tests/buildTools"))
570+
.enablePlugins(KotlinPlugin)
570571
.settings(
571-
testSettings,
572-
Test / envVars ++=
573-
Map(
574-
"SCIP_JAVA_CLI" -> ((cli / pack).value / "bin" / "scip-java").toString
575-
),
572+
crossPaths := false,
573+
autoScalaLibrary := false,
574+
publish / skip := true,
575+
kotlinVersion := V.kotlinVersion,
576+
kotlincJvmTarget := "11",
576577
Test / fork := true,
577578
// Our CI set up is a couple of measly vCPUs so parallelising tests there makes
578579
// everything worse
579-
Test / testForkedParallel := !sys.env.contains("CI")
580+
Test / testForkedParallel := !sys.env.contains("CI"),
581+
// The SCIP build tool drives javac in-process; on JDK 17+ this requires
582+
// opening the JDK-internal javac packages.
583+
Test / javaOptions ++= javacModuleOptions.map(_.stripPrefix("-J")),
584+
// Pin the JDK version embedded in stdlib SCIP symbols so output is stable.
585+
Test / javaOptions += "-Dscip.jdk.version=11",
586+
libraryDependencies ++=
587+
Seq(
588+
"org.jetbrains.kotlin" % "kotlin-test" % V.kotlinVersion % Test,
589+
"org.jetbrains.kotlin" % "kotlin-test-junit5" % V.kotlinVersion % Test,
590+
"com.github.sbt.junit" % "jupiter-interface" %
591+
JupiterKeys.jupiterVersion.value % Test
592+
)
580593
)
581-
.dependsOn(unit)
594+
.dependsOn(cli)
582595

583596
lazy val snapshots = project
584597
.in(file("tests/snapshots"))

docs/getting-started.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,6 @@ curl -fLo coursier https://git.io/coursier-cli \
6868
&& chmod +x coursier \
6969
&& ./coursier launch com.sourcegraph:scip-java_2.13:@STABLE_VERSION@ -- --help
7070

71-
# Windows
72-
bitsadmin /transfer downloadCoursierCli https://git.io/coursier-cli "%cd%\coursier"
73-
bitsadmin /transfer downloadCoursierBat https://git.io/coursier-bat "%cd%\coursier.bat"
74-
./coursier launch com.sourcegraph:scip-java_2.13:@STABLE_VERSION@ -- --help
75-
7671
# macOS Homebrew
7772
brew install coursier/formulas/coursier \
7873
&& coursier launch com.sourcegraph:scip-java_2.13:@STABLE_VERSION@ -- --help
@@ -99,12 +94,6 @@ curl -fLo coursier https://git.io/coursier-cli \
9994
&& chmod +x coursier \
10095
&& ./coursier bootstrap --standalone -o scip-java com.sourcegraph:scip-java_2.13:@STABLE_VERSION@ --main com.sourcegraph.scip_java.ScipJava
10196
./scip-java --help
102-
103-
# Windows
104-
bitsadmin /transfer downloadCoursierCli https://git.io/coursier-cli "%cd%\coursier"
105-
bitsadmin /transfer downloadCoursierBat https://git.io/coursier-bat "%cd%\coursier.bat"
106-
./coursier bootstrap --standalone -o scip-java com.sourcegraph:scip-java_2.13:@STABLE_VERSION@
107-
./scip-java --help
10897
```
10998

11099
The fat jar uses the [Coursier](https://get-coursier.io/docs/cli-installation)

flake.nix

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,26 @@
5353
javafmt = pkgs.runCommand "check-javafmt" { } ''
5454
cd ${./.}
5555
# Exclude minimized fixtures and generated SCIP snapshot goldens
56-
# (coupled to exact line/column annotations) plus the standalone
57-
# example projects: their layout must not be reformatted here.
56+
# (coupled to exact line/column annotations), the standalone example
57+
# projects, and the buildTools test fixtures: these all mirror
58+
# real-world project layouts that must not be reformatted here.
5859
find . -name '*.java' \
5960
-not -path './examples/*' \
6061
-not -path './tests/minimized/*' \
6162
-not -path './tests/snapshots/*' \
63+
-not -path './tests/buildTools/src/test/resources/fixtures/*' \
6264
-not -path './scip-kotlinc/minimized/*' \
6365
-exec ${pkgs.google-java-format}/bin/google-java-format --dry-run --set-exit-if-changed {} +
6466
touch $out
6567
'';
6668
ktfmt = pkgs.runCommand "check-ktfmt" { } ''
6769
cd ${./.}
68-
# Exclude minimized Kotlin snapshots: the input fixtures are coupled
69-
# to generated SCIP goldens with exact line/column annotations, so
70-
# their layout must not be reformatted here.
70+
# Exclude minimized Kotlin snapshots (coupled to generated SCIP
71+
# goldens with exact line/column annotations) and the buildTools test
72+
# fixtures (real-world project layouts): neither may be reformatted.
7173
find . -name '*.kt' \
7274
-not -path './scip-kotlinc/minimized/*' \
75+
-not -path './tests/buildTools/src/test/resources/fixtures/*' \
7376
-exec ${pkgs.ktfmt}/bin/ktfmt --kotlinlang-style --dry-run --set-exit-if-changed {} +
7477
touch $out
7578
'';

scip-aggregator/src/main/java/com/sourcegraph/scip_aggregator/OperatingSystem.java

Lines changed: 0 additions & 7 deletions
This file was deleted.

scip-aggregator/src/main/java/com/sourcegraph/scip_aggregator/ScipWriter.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,11 @@ public class ScipWriter implements AutoCloseable {
1616
private final ScipAggregatorOptions options;
1717

1818
public ScipWriter(ScipAggregatorOptions options) throws IOException {
19-
if (OperatingSystem.isWindows()) {
20-
this.tmp = Files.createTempFile("scip-aggregator", "index.scip");
21-
} else {
22-
this.tmp =
23-
Files.createTempFile(
24-
"scip-aggregator",
25-
"index.scip",
26-
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-r--r--")));
27-
}
19+
this.tmp =
20+
Files.createTempFile(
21+
"scip-aggregator",
22+
"index.scip",
23+
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-r--r--")));
2824
this.output = new ScipOutputStream(new BufferedOutputStream(Files.newOutputStream(tmp)));
2925
this.options = options;
3026
}

scip-java/src/main/kotlin/com/sourcegraph/scip_java/buildtools/GradleBuildTool.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,7 @@ This means our SCIP compiler plugin was not attached to one or more JavaCompile
7474
private val defaultTargetroot: Path = Paths.get("build", "scip-targetroot")
7575

7676
private fun runBuild(): ProcessResult {
77-
val gradleWrapperName =
78-
if (System.getProperty("os.name").lowercase().contains("win")) "gradlew.bat"
79-
else "gradlew"
80-
val gradleWrapper = index.workingDirectory.resolve(gradleWrapperName)
77+
val gradleWrapper = index.workingDirectory.resolve("gradlew")
8178
val gradleCommand =
8279
if (Files.isRegularFile(gradleWrapper) && Files.isExecutable(gradleWrapper))
8380
gradleWrapper.toString()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package tests
2+
3+
import org.junit.jupiter.api.DynamicTest
4+
import org.junit.jupiter.api.TestFactory
5+
6+
class AutoBuildToolTest : BuildToolHarness() {
7+
8+
@TestFactory
9+
fun tests(): List<DynamicTest> =
10+
listOf(
11+
checkErrorOutput(
12+
"no-tools-found",
13+
listOf("index", "--build-tool", "auto"),
14+
expectedOutput =
15+
"error: Build tool mode set to `auto`, but no supported build tools " +
16+
"were detected",
17+
)
18+
)
19+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package tests
2+
3+
import com.sourcegraph.scip_java.CliEnvironment
4+
import com.sourcegraph.scip_java.ScipJavaApp
5+
import com.sourcegraph.scip_java.buildtools.ClasspathEntry
6+
import java.io.ByteArrayOutputStream
7+
import java.io.PrintStream
8+
import java.nio.charset.StandardCharsets
9+
import java.nio.file.Files
10+
import java.nio.file.Path
11+
import java.nio.file.Paths
12+
import java.nio.file.StandardCopyOption
13+
import java.util.stream.Collectors
14+
import kotlin.test.assertEquals
15+
import kotlin.test.assertNotEquals
16+
import kotlin.test.fail
17+
import org.junit.jupiter.api.Assumptions.assumeTrue
18+
import org.junit.jupiter.api.DynamicTest
19+
import org.junit.jupiter.api.DynamicTest.dynamicTest
20+
21+
/**
22+
* Base class for build-tool integration tests. Each `check*` helper returns a JUnit 5
23+
* [DynamicTest]; suites expose them from a `@TestFactory` method.
24+
*/
25+
abstract class BuildToolHarness {
26+
27+
/** Run `scip-java` in-process with stdout/stderr redirected into a buffer. */
28+
private fun runScipJava(workingDirectory: Path, arguments: List<String>): Pair<Int, String> {
29+
val buffer = ByteArrayOutputStream()
30+
val stream = PrintStream(buffer, true, StandardCharsets.UTF_8.name())
31+
val app = ScipJavaApp()
32+
app.env =
33+
CliEnvironment(
34+
workingDirectory = workingDirectory,
35+
standardOutput = stream,
36+
standardError = stream,
37+
)
38+
val exit = app.run(arguments)
39+
return exit to buffer.toString(StandardCharsets.UTF_8.name())
40+
}
41+
42+
private fun listScipShards(targetroot: Path): List<Path> {
43+
if (!Files.isDirectory(targetroot)) return emptyList()
44+
Files.walk(targetroot).use { stream ->
45+
return stream.filter { it.toString().endsWith(".scip") }.collect(Collectors.toList())
46+
}
47+
}
48+
49+
/**
50+
* Fresh temp directory for one test, with symlinks resolved. `toRealPath` matters on macOS
51+
* where the system temp dir lives under `/var` (a symlink to `/private/var`): build tools write
52+
* canonical paths into their output, so the sourceroot must be canonical too or path prefix
53+
* checks fail.
54+
*/
55+
private fun newTempBase(): Path = Files.createTempDirectory("buildtools").toRealPath()
56+
57+
/**
58+
* Materialize a test project from the `fixtures/<name>` directory on the test classpath into
59+
* [target], overwriting existing files (e.g. a stub `build.gradle` left behind by wrapper
60+
* generation). When [substitutions] is non-empty, each token is textually replaced in every
61+
* file, which lets a single fixture cover parametrized variants (e.g. the JDK in a toolchain).
62+
*/
63+
protected fun copyFixture(
64+
fixture: String,
65+
target: Path,
66+
substitutions: Map<String, String> = emptyMap(),
67+
) {
68+
val resource = "/fixtures/$fixture"
69+
val url =
70+
BuildToolHarness::class.java.getResource(resource)
71+
?: error("Fixture not found on test classpath: $resource")
72+
val source = Paths.get(url.toURI())
73+
Files.walk(source).use { stream ->
74+
stream.forEach { path ->
75+
val dest = target.resolve(source.relativize(path).toString())
76+
if (Files.isDirectory(path)) {
77+
Files.createDirectories(dest)
78+
} else {
79+
Files.createDirectories(dest.parent)
80+
if (substitutions.isEmpty()) {
81+
Files.copy(path, dest, StandardCopyOption.REPLACE_EXISTING)
82+
} else {
83+
var text = Files.readString(path)
84+
for ((token, value) in substitutions) {
85+
text = text.replace(token, value)
86+
}
87+
Files.writeString(dest, text)
88+
}
89+
}
90+
}
91+
}
92+
}
93+
94+
/** Compare two strings ignoring trailing whitespace. */
95+
protected fun assertNoDiff(obtained: String, expected: String) {
96+
if (obtained.trimEnd() != expected.trimEnd()) {
97+
fail(
98+
"diff:\n--- obtained ---\n${obtained.trimEnd()}\n" +
99+
"--- expected ---\n${expected.trimEnd()}"
100+
)
101+
}
102+
}
103+
104+
protected fun checkBuild(
105+
name: String,
106+
fixture: String,
107+
expectedScipFiles: Int = 0,
108+
extraArguments: List<String> = emptyList(),
109+
expectedError: ((String) -> Unit)? = null,
110+
expectedPackages: String = "",
111+
initCommand: (Path) -> List<String> = { emptyList() },
112+
prepare: (Path) -> Unit = {},
113+
targetRoot: String? = null,
114+
maxJdk: Int? = null,
115+
substitutions: Map<String, String> = emptyMap(),
116+
): DynamicTest =
117+
dynamicTest(name) {
118+
// Some build tools cap the JDK they support (e.g. Gradle 8.10 tops
119+
// out at JDK 21); skip rather than fail when the JDK is too new.
120+
assumeTrue(
121+
maxJdk == null || currentJavaVersion <= maxJdk,
122+
"Test $name ignored: JDK $currentJavaVersion exceeds max $maxJdk",
123+
)
124+
125+
val base = newTempBase()
126+
try {
127+
val workingDirectory = Files.createDirectories(base.resolve("workingDirectory"))
128+
val cacheDirectory = Files.createDirectories(base.resolve("cache"))
129+
130+
val init = initCommand(workingDirectory)
131+
if (init.isNotEmpty()) exec(init, workingDirectory)
132+
133+
copyFixture(fixture, workingDirectory, substitutions)
134+
prepare(workingDirectory)
135+
136+
val targetroot = workingDirectory.resolve(targetRoot ?: "targetroot")
137+
val arguments =
138+
listOf(
139+
"index",
140+
"--temporary-directory",
141+
cacheDirectory.toString(),
142+
"--targetroot",
143+
targetroot.toString(),
144+
) + extraArguments
145+
146+
val (exit, output) = runScipJava(workingDirectory, arguments)
147+
148+
if (expectedError != null) {
149+
assertNotEquals(0, exit, "expected failure, output:\n$output")
150+
expectedError(output)
151+
} else {
152+
assertEquals(0, exit, "expected success, output:\n$output")
153+
}
154+
155+
val shards = listScipShards(targetroot)
156+
if (shards.size != expectedScipFiles) {
157+
fail(
158+
"Expected $expectedScipFiles SCIP shard(s), got ${shards.size}: " +
159+
"$shards\noutput:\n$output"
160+
)
161+
}
162+
163+
if (expectedPackages.isNotEmpty()) {
164+
val obtained =
165+
ClasspathEntry.fromTargetroot(targetroot, workingDirectory)
166+
.map { it.mavenCoordinate() }
167+
.sorted()
168+
.distinct()
169+
.joinToString("\n")
170+
assertNoDiff(obtained, expectedPackages)
171+
}
172+
} finally {
173+
base.toFile().deleteRecursively()
174+
}
175+
}
176+
177+
protected fun checkErrorOutput(
178+
name: String,
179+
arguments: List<String>,
180+
expectedOutput: String,
181+
fixture: String? = null,
182+
): DynamicTest =
183+
dynamicTest(name) {
184+
val base = newTempBase()
185+
try {
186+
val workingDirectory = Files.createDirectories(base.resolve("workingDirectory"))
187+
if (fixture != null) {
188+
copyFixture(fixture, workingDirectory, emptyMap())
189+
}
190+
val (exit, rawOutput) = runScipJava(workingDirectory, arguments)
191+
val output = rawOutput.replace(base.toString(), "")
192+
assertEquals(1, exit, "output:\n$output")
193+
assertNoDiff(output, expectedOutput)
194+
} finally {
195+
base.toFile().deleteRecursively()
196+
}
197+
}
198+
199+
companion object {
200+
/** Major version of the JVM running these tests (e.g. 17, 21). */
201+
private val currentJavaVersion: Int = Runtime.version().feature()
202+
203+
/** Run an external command, inheriting stdio. Throws if it exits non-zero. */
204+
internal fun exec(command: List<String>, cwd: Path) {
205+
val exit = ProcessBuilder(command).directory(cwd.toFile()).inheritIO().start().waitFor()
206+
check(exit == 0) { "Command failed ($exit): ${command.joinToString(" ")}" }
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)