Skip to content

Commit 3b33804

Browse files
committed
feat: add support to run tests externally
either via launching separate jvm or launching native image
1 parent 5c464e8 commit 3b33804

13 files changed

+340
-39
lines changed

cli/build.gradle

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,14 @@ graalvmNative {
9696
}
9797
}
9898
}
99+
100+
tasks.withType(Test).configureEach {
101+
if (it.name == 'testCliProcess') {
102+
it.dependsOn('shadowJar')
103+
it.systemProperty 'spotless.cli.shadowJar', tasks.shadowJar.archiveFile.get().asFile
104+
}
105+
if (it.name == 'testCliNative') {
106+
it.dependsOn('nativeCompile')
107+
it.systemProperty 'spotless.cli.nativeImage', tasks.nativeCompile.outputFile.get().asFile
108+
}
109+
}

cli/src/main/java/com/diffplug/spotless/cli/SpotlessCLI.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package com.diffplug.spotless.cli;
1717

18-
import java.io.File;
1918
import java.nio.charset.Charset;
2019
import java.nio.file.Path;
2120
import java.util.List;
@@ -98,11 +97,11 @@ private ResultType handleResult(Formatter formatter, Result result) {
9897
}
9998

10099
private TargetResolver targetResolver() {
101-
return new TargetResolver(baseDir == null ? Path.of(File.separator) : baseDir, targets);
100+
return new TargetResolver(baseDir(), targets);
102101
}
103102

104103
private Path baseDir() {
105-
return baseDir == null ? Path.of(File.separator) : baseDir;
104+
return baseDir == null ? Path.of(System.getProperty("user.dir")) : baseDir;
106105
}
107106

108107
@Override

cli/src/test/java/com/diffplug/spotless/cli/CLIIntegrationHarness.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.junit.jupiter.api.BeforeEach;
2121

2222
import com.diffplug.spotless.ResourceHarness;
23+
import com.diffplug.spotless.tag.CliNativeTest;
24+
import com.diffplug.spotless.tag.CliProcessTest;
2325

2426
public abstract class CLIIntegrationHarness extends ResourceHarness {
2527

@@ -43,7 +45,19 @@ void gitAttributes() throws IOException {
4345
}
4446

4547
protected SpotlessCLIRunner cliRunner() {
46-
return SpotlessCLIRunner.create()
48+
return createRunnerForTag()
4749
.withWorkingDir(rootFolder());
4850
}
51+
52+
private SpotlessCLIRunner createRunnerForTag() {
53+
CliProcessTest cliProcessTest = getClass().getAnnotation(CliProcessTest.class);
54+
if (cliProcessTest != null) {
55+
return SpotlessCLIRunner.createExternalProcess();
56+
}
57+
CliNativeTest cliNativeTest = getClass().getAnnotation(CliNativeTest.class);
58+
if (cliNativeTest != null) {
59+
return SpotlessCLIRunner.createNative();
60+
}
61+
return SpotlessCLIRunner.create();
62+
}
4963
}

cli/src/test/java/com/diffplug/spotless/cli/SpotlessCLIRunner.java

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
package com.diffplug.spotless.cli;
1717

1818
import java.io.File;
19-
import java.io.PrintWriter;
20-
import java.io.StringWriter;
2119
import java.util.ArrayList;
2220
import java.util.List;
2321
import java.util.Objects;
@@ -29,19 +27,31 @@
2927

3028
import picocli.CommandLine;
3129

32-
public class SpotlessCLIRunner {
30+
public abstract class SpotlessCLIRunner {
3331

3432
private File workingDir = new File(".");
3533

3634
private final List<String> args = new ArrayList<>();
3735

3836
public static SpotlessCLIRunner create() {
39-
return new SpotlessCLIRunner();
37+
return new SpotlessCLIRunnerInSameThread();
38+
}
39+
40+
public static SpotlessCLIRunner createExternalProcess() {
41+
return new SpotlessCLIRunnerInExternalJavaProcess();
42+
}
43+
44+
public static SpotlessCLIRunner createNative() {
45+
return new SpotlessCLIRunnerInNativeExternalProcess();
4046
}
4147

4248
public SpotlessCLIRunner withWorkingDir(@NotNull File workingDir) {
4349
this.workingDir = Objects.requireNonNull(workingDir);
44-
return withOption("--basedir", workingDir.getAbsolutePath());
50+
return this;
51+
}
52+
53+
protected File workingDir() {
54+
return workingDir;
4555
}
4656

4757
public SpotlessCLIRunner withOption(@NotNull String option) {
@@ -80,7 +90,7 @@ private String determineStepName(Class<? extends SpotlessCLIFormatterStep> stepC
8090
}
8191

8292
public Result run() {
83-
Result result = executeCommand();
93+
Result result = executeCommand(args);
8494
if (result.executionException() != null) {
8595
throwRuntimeException("Error while executing Spotless CLI command", result);
8696
}
@@ -91,7 +101,7 @@ public Result run() {
91101
}
92102

93103
public Result runAndFail() {
94-
Result result = executeCommand();
104+
Result result = executeCommand(args);
95105
if (result.executionException() != null) {
96106
throwRuntimeException("Error while executing Spotless CLI command", result);
97107
}
@@ -113,31 +123,7 @@ private void throwRuntimeException(String message, Result result) {
113123
throw new RuntimeException(sb.toString());
114124
}
115125

116-
private Result executeCommand() {
117-
SpotlessCLI cli = SpotlessCLI.createInstance();
118-
CommandLine commandLine = SpotlessCLI.createCommandLine(cli);
119-
120-
StringWriter out = new StringWriter();
121-
StringWriter err = new StringWriter();
122-
123-
try (PrintWriter outWriter = new PrintWriter(out);
124-
PrintWriter errWriter = new PrintWriter(err)) {
125-
commandLine.setOut(outWriter);
126-
commandLine.setErr(errWriter);
127-
Exception executionException = null;
128-
Integer exitCode = null;
129-
try {
130-
exitCode = commandLine.execute(args.toArray(new String[0]));
131-
} catch (Exception e) {
132-
executionException = e;
133-
}
134-
135-
// finalize
136-
outWriter.flush();
137-
errWriter.flush();
138-
return new Result(exitCode, executionException, out.toString(), err.toString());
139-
}
140-
}
126+
protected abstract Result executeCommand(List<String> args);
141127

142128
public static class Result {
143129

@@ -146,7 +132,7 @@ public static class Result {
146132
private final String stdErr;
147133
private final Exception executionException;
148134

149-
private Result(@Nullable Integer exitCode, @Nullable Exception executionException, @NotNull String stdOut, @NotNull String stdErr) {
135+
protected Result(@Nullable Integer exitCode, @Nullable Exception executionException, @NotNull String stdOut, @NotNull String stdErr) {
150136
this.exitCode = exitCode;
151137
this.executionException = executionException;
152138
this.stdOut = Objects.requireNonNull(stdOut);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.cli;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import com.diffplug.spotless.ProcessRunner;
22+
import com.diffplug.spotless.ThrowingEx;
23+
24+
public class SpotlessCLIRunnerInExternalJavaProcess extends SpotlessCLIRunner {
25+
26+
private static final String SPOTLESS_CLI_SHADOW_JAR_SYSPROP = "spotless.cli.shadowJar";
27+
28+
public SpotlessCLIRunnerInExternalJavaProcess() {
29+
super();
30+
if (System.getProperty(SPOTLESS_CLI_SHADOW_JAR_SYSPROP) == null) {
31+
throw new IllegalStateException("spotless.cli.shadowJar system property must be set to the path of the shadow jar");
32+
}
33+
}
34+
35+
protected Result executeCommand(List<String> args) {
36+
try (ProcessRunner runner = new ProcessRunner()) {
37+
38+
ProcessRunner.Result pResult = ThrowingEx.get(() -> runner.exec(
39+
workingDir(),
40+
System.getenv(),
41+
null,
42+
processArgs(args)));
43+
44+
return new Result(pResult.exitCode(), null, pResult.stdOutUtf8(), pResult.stdErrUtf8());
45+
}
46+
}
47+
48+
private List<String> processArgs(List<String> args) {
49+
List<String> processArgs = new ArrayList<>();
50+
processArgs.add(currentJavaExecutable());
51+
processArgs.add("-jar");
52+
String jarPath = System.getProperty(SPOTLESS_CLI_SHADOW_JAR_SYSPROP);
53+
processArgs.add(jarPath);
54+
55+
// processArgs.add(SpotlessCLI.class.getProtectionDomain().getCodeSource().getLocation().getPath());
56+
57+
processArgs.addAll(args);
58+
return processArgs;
59+
}
60+
61+
private String currentJavaExecutable() {
62+
return ProcessHandle.current().info().command().orElse("java");
63+
}
64+
65+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.cli;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import com.diffplug.spotless.ProcessRunner;
22+
import com.diffplug.spotless.ThrowingEx;
23+
24+
public class SpotlessCLIRunnerInNativeExternalProcess extends SpotlessCLIRunner {
25+
26+
private static final String SPOTLESS_CLI_NATIVE_IMAGE_SYSPROP = "spotless.cli.nativeImage";
27+
28+
public SpotlessCLIRunnerInNativeExternalProcess() {
29+
super();
30+
if (System.getProperty(SPOTLESS_CLI_NATIVE_IMAGE_SYSPROP) == null) {
31+
throw new IllegalStateException(SPOTLESS_CLI_NATIVE_IMAGE_SYSPROP + " system property must be set to the path of the native binary");
32+
}
33+
System.out.println("SpotlessCLIRunnerInNativeExternalProcess: " + System.getProperty(SPOTLESS_CLI_NATIVE_IMAGE_SYSPROP));
34+
}
35+
36+
protected Result executeCommand(List<String> args) {
37+
try (ProcessRunner runner = new ProcessRunner()) {
38+
39+
ProcessRunner.Result pResult = ThrowingEx.get(() -> runner.exec(
40+
workingDir(),
41+
System.getenv(),
42+
null,
43+
processArgs(args)));
44+
45+
return new Result(pResult.exitCode(), null, pResult.stdOutUtf8(), pResult.stdErrUtf8());
46+
}
47+
}
48+
49+
private List<String> processArgs(List<String> args) {
50+
List<String> processArgs = new ArrayList<>();
51+
processArgs.add(System.getProperty(SPOTLESS_CLI_NATIVE_IMAGE_SYSPROP));
52+
// processArgs.add(SpotlessCLI.class.getProtectionDomain().getCodeSource().getLocation().getPath());
53+
54+
processArgs.addAll(args);
55+
return processArgs;
56+
}
57+
58+
private String currentJavaExecutable() {
59+
return ProcessHandle.current().info().command().orElse("java");
60+
}
61+
62+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.cli;
17+
18+
import java.io.PrintWriter;
19+
import java.io.StringWriter;
20+
import java.util.List;
21+
22+
import picocli.CommandLine;
23+
24+
public class SpotlessCLIRunnerInSameThread extends SpotlessCLIRunner {
25+
26+
protected Result executeCommand(List<String> args) {
27+
SpotlessCLI cli = SpotlessCLI.createInstance();
28+
CommandLine commandLine = SpotlessCLI.createCommandLine(cli);
29+
30+
StringWriter out = new StringWriter();
31+
StringWriter err = new StringWriter();
32+
33+
try (PrintWriter outWriter = new PrintWriter(out);
34+
PrintWriter errWriter = new PrintWriter(err)) {
35+
commandLine.setOut(outWriter);
36+
commandLine.setErr(errWriter);
37+
Exception executionException = null;
38+
Integer exitCode = null;
39+
try {
40+
exitCode = commandLine.execute(argsWithBaseDir(args));
41+
} catch (Exception e) {
42+
executionException = e;
43+
}
44+
45+
// finalize
46+
outWriter.flush();
47+
errWriter.flush();
48+
return new Result(exitCode, executionException, out.toString(), err.toString());
49+
}
50+
}
51+
52+
private String[] argsWithBaseDir(List<String> args) {
53+
// prepend the base dir
54+
String[] argsWithBaseDir = new String[args.size() + 2];
55+
argsWithBaseDir[0] = "--basedir";
56+
argsWithBaseDir[1] = workingDir().getAbsolutePath();
57+
System.arraycopy(args.toArray(new String[0]), 0, argsWithBaseDir, 2, args.size());
58+
return argsWithBaseDir;
59+
}
60+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.cli.steps;
17+
18+
import com.diffplug.spotless.tag.CliProcessTest;
19+
20+
@CliProcessTest
21+
public class LicenseHeaderJavaProcessTest extends LicenseHeaderTest {}

0 commit comments

Comments
 (0)