I’m implementing a Gradle Plugin that executes a 3rd party application. In my functional tests, I decided to simply mock that application to obtain its expected output for further procession (parser etc).
But I ran into a trouble that makes me question my own understanding of the GradleRunner
.
I managed to get the minimum reproducible setup for my issue below (the duplicate code is deliberate).
To reproduce it, just create a new Gradle plugin from init
and paste this:
class ReproCommandTestIssuePluginFunctionalTest {
@TempDir
File projectDir;
@Test void canRunTask() throws IOException {
System.out.println("\n\n##### RUNNING: " + new Object(){}.getClass().getEnclosingMethod().getName());
writeString(new File(projectDir, "settings.gradle"), "");
writeString(new File(projectDir, "build.gradle"),
"task myToolRun {\n" +
" doLast {\n" +
" exec {\n" +
" commandLine 'printenv'\n" +
" }\n" +
" exec {\n" +
" commandLine 'whereis', 'myTool'\n" +
" }\n" +
" exec {\n" +
" commandLine 'myTool'\n" +
" }\n" +
" }\n" +
"}\n");
// Create Tools folder
File toolsFolder = new File(projectDir, "tools");
Files.createDirectories(toolsFolder.toPath());
// Create Tool
File mockTool = new File(toolsFolder, "myTool");
writeString(mockTool, "echo Hello World!");
mockTool.setExecutable(true);
// Run the build
GradleRunner runner = GradleRunner.create();
runner.forwardOutput();
runner.withPluginClasspath();
runner.withEnvironment(Map.of("PATH", toolsFolder.getAbsolutePath() + System.getProperty("path.separator") + "/usr/bin"));
runner.withArguments("myToolRun");
runner.withProjectDir(projectDir);
BuildResult result = runner.build();
// Verify the result
assertTrue(result.getOutput().contains("Hello World!"));
}
@Test void canRunTask2() throws IOException {
System.out.println("\n\n##### RUNNING: " + new Object(){}.getClass().getEnclosingMethod().getName());
writeString(new File(projectDir, "settings.gradle"), "");
writeString(new File(projectDir, "build.gradle"),
"task myToolRun {\n" +
" doLast {\n" +
" exec {\n" +
" commandLine 'printenv'\n" +
" }\n" +
" exec {\n" +
" commandLine 'whereis', 'myTool'\n" +
" }\n" +
" exec {\n" +
" commandLine 'myTool'\n" +
" }\n" +
" }\n" +
"}\n");
// Create Tools folder
File toolsFolder = new File(projectDir, "tools");
Files.createDirectories(toolsFolder.toPath());
// Create Tool
File mockTool = new File(toolsFolder, "myTool");
writeString(mockTool, "echo Hello World!");
mockTool.setExecutable(true);
// Run the build
GradleRunner runner = GradleRunner.create();
runner.forwardOutput();
runner.withPluginClasspath();
runner.withEnvironment(Map.of("PATH", toolsFolder.getAbsolutePath() + System.getProperty("path.separator") + "/usr/bin"));
runner.withArguments("myToolRun");
runner.withProjectDir(projectDir);
BuildResult result = runner.build();
// Verify the result
assertTrue(result.getOutput().contains("Hello World!"));
}
private void writeString(File file, String string) throws IOException {
try (Writer writer = new FileWriter(file)) {
writer.write(string);
}
}
}
Both methods canRunTask()
and canRunTask2()
are identical.
They create a simple build.gradle
with a printenv
and a whereis myTool
just for debug, and at last the commandLine
that executes myTool
.
Next, it creates a dummy tools\myTool
under the projectDir
.
Finally, I’ve setup a withEnvironment(...)
that overwrites the PATH
.
Here’s the output of this test:
##### RUNNING: canRunTask
> Task :myToolRun
PATH=/tmp/junit3891473089882920638/tools:/usr/bin
myTool: /tmp/junit3891473089882920638/tools/myTool
Hello World!
BUILD SUCCESSFUL in 4s
1 actionable task: 1 executed
##### RUNNING: canRunTask2
> Task :myToolRun FAILED
PATH=/tmp/junit17090999217294325759/tools:/usr/bin
myTool: /tmp/junit17090999217294325759/tools/myTool
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':myToolRun'.
> A problem occurred starting process 'command 'myTool''
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 208ms
1 actionable task: 1 executed
Notice that each test got its own projectDir
as expected, and even the whereis myTool
gave the correct location in each respective test. But the execution only works for the first test executed, while all the remaining ones fail as if the myTool
couldn’t be found (in my actual project I have tens of tests).
This also happens across test classes.
What’s more, if I annotate the projectDir
with @TempDir(cleanup = CleanupMode.NEVER)
then all tests succeed! It’s as if all subsequent tests were looking at the executable from the first test.
My expectation was that each GradleRunner.create().build()
would be isolated, but it seems I’m wrong.
Why is that? Any idea how to solve this issue? Thanks!