Native tests

This commit is contained in:
Peter Palaga
2020-06-04 11:37:38 +02:00
parent 4565698444
commit c4221f8125
19 changed files with 764 additions and 415 deletions

View File

@@ -20,33 +20,57 @@ name: Linux, Windows and MacOS CI
on: [push, pull_request]
jobs:
verify-linux-and-windows:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-18.04, windows-2019 ]
java: [ 8, 11, 14 ]
linux:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- name: Set up JDK
uses: actions/setup-java@v1
- name: setup-graalvm-ce
uses: DeLaGuardo/setup-graalvm@3
with:
java-version: ${{ matrix.java }}
- name: Build with Maven
run: ./mvnw clean verify -B -ntp
verify-macos:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ macos-10.15 ]
java: [ 11 ]
graalvm-version: '20.1.0.java11'
- name: gu install native-image
run: gu install native-image
- name: mvn clean verify
run: ./mvnw clean verify -Pnative -B -ntp -e
windows:
runs-on: windows-2019
steps:
- name: setup-graalvm-ce
uses: DeLaGuardo/setup-graalvm@3
with:
graalvm-version: '20.1.0.java11'
- name: gu install native-image
shell: cmd
run: gu install native-image
- name: choco install visualstudio2017-workload-vctools
run: choco install visualstudio2017-workload-vctools
- name: Compile native-image.cmd to native-image.exe
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
"%JAVA_HOME%\bin\native-image.cmd" -jar "%JAVA_HOME%\lib\graalvm\svm-driver.jar" native-image
- name: move native-image.exe %JAVA_HOME%\bin\
shell: cmd
run: |
move native-image.exe "%JAVA_HOME%\bin\"
- uses: actions/checkout@v1
- name: mvn clean verify
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
./mvnw clean verify -Pnative -B -ntp -e
macos:
runs-on: macos-10.15
steps:
- uses: actions/checkout@v1
- name: Set up JDK
uses: actions/setup-java@v1
- name: setup-graalvm-ce
uses: DeLaGuardo/setup-graalvm@3
with:
java-version: ${{ matrix.java }}
- name: Build with Maven
run: ./mvnw clean verify -B -ntp
graalvm-version: '20.1.0.java11'
- name: gu install native-image
run: gu install native-image
- name: mvn clean verify
run: ./mvnw clean verify -Pnative -B -ntp -e

View File

@@ -67,6 +67,7 @@
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
@@ -82,7 +83,7 @@
</executions>
<configuration>
<skip>false</skip>
<mainClass>org.jboss.fuse.mvnd.client.Client</mainClass>
<mainClass>org.jboss.fuse.mvnd.client.DefaultClient</mainClass>
<imageName>mvnd</imageName>
<buildArgs>
--no-server

View File

@@ -1,261 +1,14 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.fuse.mvnd.client;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.fusesource.jansi.Ansi;
import org.jboss.fuse.mvnd.client.ClientOutput.TerminalOutput;
import org.jboss.fuse.mvnd.client.Message.BuildEvent;
import org.jboss.fuse.mvnd.client.Message.BuildException;
import org.jboss.fuse.mvnd.client.Message.BuildMessage;
import org.jboss.fuse.mvnd.client.Message.MessageSerializer;
import org.jboss.fuse.mvnd.jpm.Process;
import org.jboss.fuse.mvnd.jpm.ProcessImpl;
import org.jboss.fuse.mvnd.jpm.ScriptUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public interface Client {
public class Client {
ExecutionResult execute(ClientOutput output, List<String> args) throws InterruptedException;
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
public static final String DAEMON_DEBUG = "daemon.debug";
public static final String DAEMON_IDLE_TIMEOUT = "daemon.idleTimeout";
public static final int DEFAULT_IDLE_TIMEOUT = (int) TimeUnit.HOURS.toMillis(3);
public static final int DEFAULT_PERIODIC_CHECK_INTERVAL_MILLIS = 10 * 1000;
public static final int CANCEL_TIMEOUT = 10 * 1000;
private final ClientLayout layout;
private final Properties buildProperties;
public static void main(String[] argv) throws Exception {
final List<String> args = new ArrayList<>(Arrays.asList(argv));
Path logFile = null;
for (int i = 0; i < args.size() - 2; i++) {
String arg = args.get(i);
if ("-l".equals(arg) || "--log-file".equals(arg)) {
logFile = Paths.get(args.get(i + 1));
args.remove(i);
args.remove(i);
break;
}
}
try (TerminalOutput output = new TerminalOutput(logFile)) {
new Client(ClientLayout.getEnvInstance()).execute(output, args);
}
}
public Client(ClientLayout layout) {
this.layout = layout;
this.buildProperties = new Properties();
try (InputStream is = Client.class.getResourceAsStream("build.properties")) {
buildProperties.load(is);
} catch (IOException e) {
throw new RuntimeException("Could not read build.properties");
}
}
public <O extends ClientOutput> ClientResult<O> execute(O output, String... argv) throws IOException {
return execute(output, Arrays.asList(argv));
}
public <O extends ClientOutput> ClientResult<O> execute(O output, List<String> argv) throws IOException {
output.debug("Starting client");
final List<String> args = new ArrayList<>(argv);
// Print version if needed
boolean version = args.contains("-v") || args.contains("-version") || args.contains("--version");
boolean showVersion = args.contains("-V") || args.contains("--show-version");
boolean debug = args.contains("-X") || args.contains("--debug");
if (version || showVersion || debug) {
final String nativeSuffix = Layout.isNative() ? " (native)" : "";
final String v = Ansi.ansi().bold().a("Maven Daemon " + buildProperties.getProperty("version") + nativeSuffix).reset().toString();
output.log(v);
/* Do not return, rather pass -v to the server so that the client module does not need to depend on any Maven artifacts */
}
final Path javaHome = layout.javaHome();
try (DaemonRegistry registry = new DaemonRegistry(layout.registry())) {
boolean status = args.remove("--status");
if (status) {
output.log(String.format(" %36s %7s %5s %7s %s",
"UUID", "PID", "Port", "Status", "Last activity"));
registry.getAll().forEach(d -> output.log(String.format(" %36s %7s %5s %7s %s",
d.getUid(), d.getPid(), d.getAddress(), d.getState(),
LocalDateTime.ofInstant(
Instant.ofEpochMilli(Math.max(d.getLastIdle(), d.getLastBusy())),
ZoneId.systemDefault()))));
return new ClientResult<O>(argv, true, output);
}
boolean stop = args.remove("--stop");
if (stop) {
DaemonInfo[] dis = registry.getAll().toArray(new DaemonInfo[0]);
if (dis.length > 0) {
output.log("Stopping " + dis.length + " running daemons");
for (DaemonInfo di : dis) {
try {
new ProcessImpl(di.getPid()).destroy();
} catch (IOException t) {
System.out.println("Daemon " + di.getUid() + ": " + t.getMessage());
} catch (Exception t) {
System.out.println("Daemon " + di.getUid() + ": " + t);
} finally {
registry.remove(di.getUid());
}
}
}
return new ClientResult<O>(argv, true, output);
}
setDefaultArgs(args);
final Path settings = layout.getSettings();
if (settings != null && !args.stream().anyMatch(arg -> arg.equals("-s") || arg.equals("--settings"))) {
args.add("-s");
args.add(settings.toString());
}
final Path localMavenRepository = layout.getLocalMavenRepository();
if (localMavenRepository != null && !args.stream().anyMatch(arg -> arg.startsWith("-Dmaven.repo.local"))) {
args.add("-Dmaven.repo.local=" + localMavenRepository.toString());
}
DaemonConnector connector = new DaemonConnector(layout, registry, this::startDaemon, new MessageSerializer());
List<String> opts = new ArrayList<>();
DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome, opts));
daemon.dispatch(new Message.BuildRequest(
args,
layout.userDir().toString(),
layout.multiModuleProjectDirectory().toString()));
while (true) {
Message m = daemon.receive();
if (m instanceof BuildException) {
output.error((BuildException) m);
return new ClientResult<O>(argv, false, output);
} else if (m instanceof BuildEvent) {
BuildEvent be = (BuildEvent) m;
switch (be.getType()) {
case BuildStarted:
break;
case BuildStopped:
return new ClientResult<O>(argv, true, output);
case ProjectStarted:
case MojoStarted:
case MojoStopped:
output.projectStateChanged(be.projectId, be.display);
break;
case ProjectStopped:
output.projectFinished(be.projectId);
}
} else if (m instanceof BuildMessage) {
BuildMessage bm = (BuildMessage) m;
output.log(bm.getMessage());
}
}
}
}
static void setDefaultArgs(List<String> args) {
if (!args.stream().anyMatch(arg -> arg.startsWith("-T") || arg.equals("--threads"))) {
args.add("-T1C");
}
if (!args.stream().anyMatch(arg -> arg.startsWith("-b") || arg.equals("--builder"))) {
args.add("-bsmart");
}
}
String startDaemon() {
// DaemonParameters parms = new DaemonParameters();
// for (String arg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
//
// }
// List<String> args = new ArrayList<>();
// args.add(javaHome.resolve(java).toString());
// args.addAll(parms.getEffectiveJvmArgs());
// args.add("-cp");
// args.add(classpath);
final String uid = UUID.randomUUID().toString();
final Path mavenHome = layout.mavenHome();
final Path workingDir = layout.userDir();
String command = "";
try {
String classpath = findClientJar(mavenHome).toString();
final String java = ScriptUtils.isWindows() ? "bin\\java.exe" : "bin/java";
List<String> args = new ArrayList<>();
args.add("\"" + layout.javaHome().resolve(java) + "\"");
args.add("-classpath");
args.add("\"" + classpath + "\"");
if (Boolean.getBoolean(DAEMON_DEBUG)) {
args.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000");
}
args.add("-Dmaven.home=\"" + mavenHome + "\"");
args.add("-Dlogback.configurationFile=logback.xml");
args.add("-Ddaemon.uid=" + uid);
args.add("-Xmx4g");
final String timeout = System.getProperty(DAEMON_IDLE_TIMEOUT);
if (timeout != null) {
args.add("-D" + DAEMON_IDLE_TIMEOUT + "=" + timeout);
}
args.add("\"-Dmaven.multiModuleProjectDirectory=" + layout.multiModuleProjectDirectory().toString() + "\"");
args.add(ServerMain.class.getName());
command = String.join(" ", args);
LOGGER.debug("Starting daemon process: uid = {}, workingDir = {}, daemonArgs: {}", uid, workingDir, command);
Process.create(workingDir.toFile(), command);
return uid;
} catch (Exception e) {
throw new DaemonException.StartException(
String.format("Error starting daemon: uid = %s, workingDir = %s, daemonArgs: %s",
uid, workingDir, command), e);
}
}
Path findClientJar(Path mavenHome) {
final Path ext = mavenHome.resolve("lib/ext");
final String clientJarName = "mvnd-client-"+ buildProperties.getProperty("version") + ".jar";
try (Stream<Path> files = Files.list(ext)) {
return files
.filter(f -> f.getFileName().toString().equals(clientJarName))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find " + clientJarName + " in " + ext));
} catch (IOException e) {
throw new RuntimeException("Could not find " + clientJarName + " in " + ext, e);
}
default ExecutionResult execute(ClientOutput output, String... args) throws InterruptedException {
return execute(output, Arrays.asList(args));
}
}

View File

@@ -7,7 +7,7 @@ import java.util.Objects;
import java.util.Properties;
/**
* Local paths relevant for the {@link Client}.
* Local paths relevant for the {@link DefaultClient}.
*/
public class ClientLayout extends Layout {
@@ -28,7 +28,7 @@ public class ClientLayout extends Layout {
pwd,
findMultiModuleProjectDirectory(pwd),
findJavaHome(mvndProperties),
null,
findLocalRepo(),
null);
}
return ENV_INSTANCE;
@@ -60,6 +60,11 @@ public class ClientLayout extends Layout {
return javaHome;
}
static Path findLocalRepo() {
final String rawValue = System.getProperty("maven.repo.local");
return rawValue != null ? Paths.get(rawValue) : null;
}
static Path findJavaHome(Properties mvndProperties) {
String rawValue = System.getenv("JAVA_HOME");
if (rawValue == null) {

View File

@@ -29,18 +29,17 @@ import org.slf4j.LoggerFactory;
/**
* A sink for various kinds of events sent by the daemon.
*/
public interface ClientOutput extends AutoCloseable {
public interface ClientOutput extends AutoCloseable, Consumer<String> {
public void projectStateChanged(String projectId, String display);
public void projectFinished(String projectId);
public void log(String message);
/** Receive a log message */
public void accept(String message);
public void error(BuildException m);
public void debug(String string);
/**
* A terminal {@link ClientOutput} based on JLine.
*/
@@ -70,7 +69,7 @@ public interface ClientOutput extends AutoCloseable {
}
@Override
public void log(String message) {
public void accept(String message) {
try {
queue.put(new AbstractMap.SimpleImmutableEntry<>(TerminalUpdater.LOG, message));
} catch (InterruptedException e) {
@@ -98,11 +97,6 @@ public interface ClientOutput extends AutoCloseable {
}
}
@Override
public void debug(String msg) {
LOGGER.debug(msg);
}
static class TerminalUpdater implements AutoCloseable {
private static final String LOG = "<log>";
private static final String ERROR = "<error>";
@@ -180,12 +174,18 @@ public interface ClientOutput extends AutoCloseable {
private void update() {
// no need to refresh the display at every single step
final Size size = terminal.getSize();
display.resize(size.getRows(), size.getColumns());
final int displayableProjectCount = size.getRows() - 1;
final int rows = size.getRows();
display.resize(rows, size.getColumns());
if (rows <= 0) {
display.update(Collections.emptyList(), 0);
return;
}
final int displayableProjectCount = rows - 1;
final int skipRows = projects.size() > displayableProjectCount ? projects.size() - displayableProjectCount : 0;
final List<AttributedString> lines = new ArrayList<>(projects.size() - skipRows);
final int lineMaxLength = size.getColumns();
int i = 0;
lines.add(new AttributedString("Building..." + (skipRows > 0 ? " (" + skipRows + " more)" : "")));
for (String line : projects.values()) {
if (i < skipRows) {
i++;
@@ -193,7 +193,6 @@ public interface ClientOutput extends AutoCloseable {
lines.add(shortenIfNeeded(AttributedString.fromAnsi(line), lineMaxLength));
}
}
lines.add(0, new AttributedString("Building..." + (skipRows > 0 ? " (" + skipRows + " more)" : "")));
display.update(lines, -1);
}

View File

@@ -1,54 +0,0 @@
package org.jboss.fuse.mvnd.client;
import java.util.ArrayList;
import java.util.List;
/**
* A result of a {@code mvnd} build.
*
* @param <O> the type of the {@link ClientOutput}.
*/
public class ClientResult<O extends ClientOutput> {
private final boolean success;
private final O clientOutput;
private final List<String> args;
public ClientResult(List<String> args, boolean success, O clientOutput) {
super();
this.args = new ArrayList<>(args);
this.success = success;
this.clientOutput = clientOutput;
}
public ClientResult<O> assertSuccess() {
if (!this.success) {
throw new AssertionError(appendCommand(new StringBuilder("Build failed: ")));
}
return this;
}
public ClientResult<O> assertFailure() {
if (this.success) {
throw new AssertionError(appendCommand(new StringBuilder("Build did not fail: ")));
}
return this;
}
public O getClientOutput() {
return clientOutput;
}
public boolean isSuccess() {
return success;
}
StringBuilder appendCommand(StringBuilder sb) {
sb.append("mvnd");
for (String arg : args) {
sb.append(" \"").append(arg).append('"');
}
return sb;
}
}

View File

@@ -0,0 +1,298 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.fuse.mvnd.client;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.fusesource.jansi.Ansi;
import org.jboss.fuse.mvnd.client.ClientOutput.TerminalOutput;
import org.jboss.fuse.mvnd.client.Message.BuildEvent;
import org.jboss.fuse.mvnd.client.Message.BuildException;
import org.jboss.fuse.mvnd.client.Message.BuildMessage;
import org.jboss.fuse.mvnd.client.Message.MessageSerializer;
import org.jboss.fuse.mvnd.jpm.Process;
import org.jboss.fuse.mvnd.jpm.ProcessImpl;
import org.jboss.fuse.mvnd.jpm.ScriptUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultClient implements Client {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultClient.class);
public static final String DAEMON_DEBUG = "daemon.debug";
public static final String DAEMON_IDLE_TIMEOUT = "daemon.idleTimeout";
public static final int DEFAULT_IDLE_TIMEOUT = (int) TimeUnit.HOURS.toMillis(3);
public static final int DEFAULT_PERIODIC_CHECK_INTERVAL_MILLIS = 10 * 1000;
public static final int CANCEL_TIMEOUT = 10 * 1000;
private final ClientLayout layout;
private final Properties buildProperties;
public static void main(String[] argv) throws Exception {
final List<String> args = new ArrayList<>(Arrays.asList(argv));
Path logFile = null;
for (int i = 0; i < args.size() - 2; i++) {
String arg = args.get(i);
if ("-l".equals(arg) || "--log-file".equals(arg)) {
logFile = Paths.get(args.get(i + 1));
args.remove(i);
args.remove(i);
break;
}
}
try (TerminalOutput output = new TerminalOutput(logFile)) {
new DefaultClient(ClientLayout.getEnvInstance()).execute(output, args);
}
}
public DefaultClient(ClientLayout layout) {
this.layout = layout;
this.buildProperties = new Properties();
try (InputStream is = DefaultClient.class.getResourceAsStream("build.properties")) {
buildProperties.load(is);
} catch (IOException e) {
throw new RuntimeException("Could not read build.properties");
}
}
@Override
public ExecutionResult execute(ClientOutput output, List<String> argv) {
LOGGER.debug("Starting client");
final List<String> args = new ArrayList<>(argv);
// Print version if needed
boolean version = args.contains("-v") || args.contains("-version") || args.contains("--version");
boolean showVersion = args.contains("-V") || args.contains("--show-version");
boolean debug = args.contains("-X") || args.contains("--debug");
if (version || showVersion || debug) {
final String nativeSuffix = Layout.isNative() ? " (native)" : "";
final String v = Ansi.ansi().bold().a("Maven Daemon " + buildProperties.getProperty("version") + nativeSuffix).reset().toString();
output.accept(v);
/* Do not return, rather pass -v to the server so that the client module does not need to depend on any Maven artifacts */
}
final Path javaHome = layout.javaHome();
try (DaemonRegistry registry = new DaemonRegistry(layout.registry())) {
boolean status = args.remove("--status");
if (status) {
output.accept(String.format(" %36s %7s %5s %7s %s",
"UUID", "PID", "Port", "Status", "Last activity"));
registry.getAll().forEach(d -> output.accept(String.format(" %36s %7s %5s %7s %s",
d.getUid(), d.getPid(), d.getAddress(), d.getState(),
LocalDateTime.ofInstant(
Instant.ofEpochMilli(Math.max(d.getLastIdle(), d.getLastBusy())),
ZoneId.systemDefault()))));
return new DefaultResult(argv, true);
}
boolean stop = args.remove("--stop");
if (stop) {
DaemonInfo[] dis = registry.getAll().toArray(new DaemonInfo[0]);
if (dis.length > 0) {
output.accept("Stopping " + dis.length + " running daemons");
for (DaemonInfo di : dis) {
try {
new ProcessImpl(di.getPid()).destroy();
} catch (IOException t) {
System.out.println("Daemon " + di.getUid() + ": " + t.getMessage());
} catch (Exception t) {
System.out.println("Daemon " + di.getUid() + ": " + t);
} finally {
registry.remove(di.getUid());
}
}
}
return new DefaultResult(argv, true);
}
setDefaultArgs(args);
final Path settings = layout.getSettings();
if (settings != null && !args.stream().anyMatch(arg -> arg.equals("-s") || arg.equals("--settings"))) {
args.add("-s");
args.add(settings.toString());
}
final Path localMavenRepository = layout.getLocalMavenRepository();
if (localMavenRepository != null) {
args.add("-Dmaven.repo.local=" + localMavenRepository.toString());
}
DaemonConnector connector = new DaemonConnector(layout, registry, this::startDaemon, new MessageSerializer());
List<String> opts = new ArrayList<>();
DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome, opts));
daemon.dispatch(new Message.BuildRequest(
args,
layout.userDir().toString(),
layout.multiModuleProjectDirectory().toString()));
while (true) {
Message m = daemon.receive();
if (m instanceof BuildException) {
output.error((BuildException) m);
return new DefaultResult(argv, false);
} else if (m instanceof BuildEvent) {
BuildEvent be = (BuildEvent) m;
switch (be.getType()) {
case BuildStarted:
break;
case BuildStopped:
return new DefaultResult(argv, true);
case ProjectStarted:
case MojoStarted:
case MojoStopped:
output.projectStateChanged(be.projectId, be.display);
break;
case ProjectStopped:
output.projectFinished(be.projectId);
}
} else if (m instanceof BuildMessage) {
BuildMessage bm = (BuildMessage) m;
output.accept(bm.getMessage());
}
}
}
}
static void setDefaultArgs(List<String> args) {
if (!args.stream().anyMatch(arg -> arg.startsWith("-T") || arg.equals("--threads"))) {
args.add("-T1C");
}
if (!args.stream().anyMatch(arg -> arg.startsWith("-b") || arg.equals("--builder"))) {
args.add("-bsmart");
}
}
String startDaemon() {
// DaemonParameters parms = new DaemonParameters();
// for (String arg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
//
// }
// List<String> args = new ArrayList<>();
// args.add(javaHome.resolve(java).toString());
// args.addAll(parms.getEffectiveJvmArgs());
// args.add("-cp");
// args.add(classpath);
final String uid = UUID.randomUUID().toString();
final Path mavenHome = layout.mavenHome();
final Path workingDir = layout.userDir();
String command = "";
try {
String classpath = findClientJar(mavenHome).toString();
final String java = ScriptUtils.isWindows() ? "bin\\java.exe" : "bin/java";
List<String> args = new ArrayList<>();
args.add("\"" + layout.javaHome().resolve(java) + "\"");
args.add("-classpath");
args.add("\"" + classpath + "\"");
if (Boolean.getBoolean(DAEMON_DEBUG)) {
args.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000");
}
args.add("-Dmaven.home=\"" + mavenHome + "\"");
args.add("-Dlogback.configurationFile=logback.xml");
args.add("-Ddaemon.uid=" + uid);
args.add("-Xmx4g");
final String timeout = System.getProperty(DAEMON_IDLE_TIMEOUT);
if (timeout != null) {
args.add("-D" + DAEMON_IDLE_TIMEOUT + "=" + timeout);
}
args.add("\"-Dmaven.multiModuleProjectDirectory=" + layout.multiModuleProjectDirectory().toString() + "\"");
args.add(ServerMain.class.getName());
command = String.join(" ", args);
LOGGER.debug("Starting daemon process: uid = {}, workingDir = {}, daemonArgs: {}", uid, workingDir, command);
Process.create(workingDir.toFile(), command);
return uid;
} catch (Exception e) {
throw new DaemonException.StartException(
String.format("Error starting daemon: uid = %s, workingDir = %s, daemonArgs: %s",
uid, workingDir, command), e);
}
}
Path findClientJar(Path mavenHome) {
final Path ext = mavenHome.resolve("lib/ext");
final String clientJarName = "mvnd-client-"+ buildProperties.getProperty("version") + ".jar";
try (Stream<Path> files = Files.list(ext)) {
return files
.filter(f -> f.getFileName().toString().equals(clientJarName))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find " + clientJarName + " in " + ext));
} catch (IOException e) {
throw new RuntimeException("Could not find " + clientJarName + " in " + ext, e);
}
}
private class DefaultResult implements ExecutionResult {
private final boolean success;
private final List<String> args;
private DefaultResult(List<String> args, boolean success) {
super();
this.args = args;
this.success = success;
}
@Override
public ExecutionResult assertSuccess() {
if (!this.success) {
throw new AssertionError(appendCommand(new StringBuilder("Build failed: ")));
}
return this;
}
@Override
public ExecutionResult assertFailure() {
if (this.success) {
throw new AssertionError(appendCommand(new StringBuilder("Build did not fail: ")));
}
return this;
}
@Override
public boolean isSuccess() {
return success;
}
StringBuilder appendCommand(StringBuilder sb) {
sb.append("mvnd");
for (String arg : args) {
sb.append(" \"").append(arg).append('"');
}
return sb;
}
}
}

View File

@@ -0,0 +1,14 @@
package org.jboss.fuse.mvnd.client;
/**
* A result of a {@code mvnd} build.
*/
public interface ExecutionResult {
boolean isSuccess();
ExecutionResult assertFailure();
ExecutionResult assertSuccess();
}

View File

@@ -42,7 +42,7 @@ import java.util.concurrent.locks.ReentrantLock;
import org.apache.maven.cli.CliRequest;
import org.apache.maven.cli.CliRequestBuilder;
import org.apache.maven.cli.DaemonMavenCli;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.DefaultClient;
import org.jboss.fuse.mvnd.client.DaemonConnection;
import org.jboss.fuse.mvnd.client.DaemonException;
import org.jboss.fuse.mvnd.client.DaemonExpirationStatus;
@@ -92,10 +92,10 @@ public class Server implements AutoCloseable, Runnable {
socket = ServerSocketChannel.open().bind(new InetSocketAddress(0));
int idleTimeout;
if (System.getProperty(Client.DAEMON_IDLE_TIMEOUT) != null) {
idleTimeout = Integer.parseInt(System.getProperty(Client.DAEMON_IDLE_TIMEOUT));
if (System.getProperty(DefaultClient.DAEMON_IDLE_TIMEOUT) != null) {
idleTimeout = Integer.parseInt(System.getProperty(DefaultClient.DAEMON_IDLE_TIMEOUT));
} else {
idleTimeout = Client.DEFAULT_IDLE_TIMEOUT;
idleTimeout = DefaultClient.DEFAULT_IDLE_TIMEOUT;
}
executor = Executors.newScheduledThreadPool(1);
strategy = DaemonExpiration.master();
@@ -339,7 +339,7 @@ public class Server implements AutoCloseable, Runnable {
}
private void cancelNow() {
long time = System.currentTimeMillis() + Client.CANCEL_TIMEOUT;
long time = System.currentTimeMillis() + DefaultClient.CANCEL_TIMEOUT;
// LOGGER.debug("Cancel requested: will wait for daemon to become idle.");
// try {

View File

@@ -12,6 +12,10 @@
<name>Maven Daemon - Integration Tests</name>
<properties>
<mvnd.native.executable>${project.basedir}/../client/target/mvnd</mvnd.native.executable>
</properties>
<dependencies>
<dependency>
<groupId>jakarta.inject</groupId>
@@ -59,4 +63,44 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>windows</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<mvnd.native.executable>${project.basedir}/../client/target/mvnd.exe</mvnd.native.executable>
</properties>
</profile>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<project.version>${project.version}</project.version>
<mvnd.home>${project.basedir}/../daemon/target/maven-distro</mvnd.home>
<mvnd.native.executable>${mvnd.native.executable}</mvnd.native.executable>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -28,7 +28,7 @@ public class MultiModuleTest {
ClientLayout layout;
@Test
void cleanInstall() throws IOException {
void cleanInstall() throws IOException, InterruptedException {
final Path[] helloFilePaths = {
layout.multiModuleProjectDirectory().resolve("hello/target/hello.txt"),
layout.multiModuleProjectDirectory().resolve("hi/target/hi.txt")
@@ -53,7 +53,7 @@ public class MultiModuleTest {
client.execute(output, "clean", "install", "-e").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.satisfiesAnyOf( /* Two orderings are possible */
messages -> Assertions.assertThat(messages)

View File

@@ -0,0 +1,64 @@
package org.jboss.fuse.mvnd.it;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
import javax.inject.Inject;
import org.assertj.core.api.Assertions;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.ClientLayout;
import org.jboss.fuse.mvnd.client.ClientOutput;
import org.jboss.fuse.mvnd.junit.MvndNativeTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.mockito.InOrder;
import org.mockito.Mockito;
@MvndNativeTest(projectDir = "src/test/projects/single-module")
public class SingleModuleNativeIT {
@Inject
Client client;
@Inject
ClientLayout layout;
@Test
void cleanInstall(TestInfo testInfo) throws IOException, InterruptedException {
final Path helloFilePath = layout.multiModuleProjectDirectory().resolve("target/hello.txt");
if (Files.exists(helloFilePath)) {
Files.delete(helloFilePath);
}
final Path installedJar = layout.getLocalMavenRepository().resolve("org/jboss/fuse/mvnd/test/single-module/single-module/0.0.1-SNAPSHOT/single-module-0.0.1-SNAPSHOT.jar");
Assertions.assertThat(installedJar).doesNotExist();
final ClientOutput o = Mockito.mock(ClientOutput.class);
client.execute(o, "clean", "install", "-e").assertSuccess();
final Properties props = MvndTestUtil.properties(layout.multiModuleProjectDirectory().resolve("pom.xml"));
final InOrder inOrder = Mockito.inOrder(o);
inOrder.verify(o).accept(Mockito.contains("Building single-module"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-clean-plugin") + ":clean"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":compile"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":testCompile"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-surefire-plugin") + ":test"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-install-plugin") + ":install"));
inOrder.verify(o).accept(Mockito.contains("SUCCESS build of project org.jboss.fuse.mvnd.test.single-module:single-module"));
assertJVM(o, props);
/* The target/hello.txt is created by HelloTest */
Assertions.assertThat(helloFilePath).exists();
Assertions.assertThat(installedJar).exists();
}
protected void assertJVM(ClientOutput o, Properties props) {
/* implemented in the subclass*/
}
}

View File

@@ -1,59 +1,16 @@
package org.jboss.fuse.mvnd.it;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
import javax.inject.Inject;
import org.assertj.core.api.Assertions;
import org.jboss.fuse.mvnd.assertj.MatchInOrderAmongOthers;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.ClientLayout;
import org.jboss.fuse.mvnd.client.ClientOutput;
import org.jboss.fuse.mvnd.junit.MvndTest;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
@MvndTest(projectDir = "src/test/projects/single-module")
public class SingleModuleTest {
@Inject
Client client;
@Inject
ClientLayout layout;
@Test
void cleanInstall() throws IOException {
final Path helloFilePath = layout.multiModuleProjectDirectory().resolve("target/hello.txt");
if (Files.exists(helloFilePath)) {
Files.delete(helloFilePath);
}
final Path installedJar = layout.getLocalMavenRepository().resolve("org/jboss/fuse/mvnd/test/single-module/single-module/0.0.1-SNAPSHOT/single-module-0.0.1-SNAPSHOT.jar");
Assertions.assertThat(installedJar).doesNotExist();
final ClientOutput output = Mockito.mock(ClientOutput.class);
client.execute(output, "clean", "install", "-e").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.is(new MatchInOrderAmongOthers<>(
"Building single-module",
"maven-clean-plugin:[^:]+:clean",
"maven-compiler-plugin:[^:]+:compile",
"maven-compiler-plugin:[^:]+:testCompile",
"maven-surefire-plugin:[^:]+:test",
"maven-install-plugin:[^:]+:install",
"SUCCESS build of project org.jboss.fuse.mvnd.test.single-module:single-module"));
final Properties props = MvndTestUtil.properties(layout.multiModuleProjectDirectory().resolve("pom.xml"));
public class SingleModuleTest extends SingleModuleNativeIT {
protected void assertJVM(ClientOutput output, Properties props) {
final InOrder inOrder = Mockito.inOrder(output);
inOrder.verify(output).projectStateChanged(
"single-module",
@@ -106,11 +63,6 @@ public class SingleModuleTest {
":single-module");
inOrder.verify(output).projectFinished("single-module");
/* The target/hello.txt is created by HelloTest */
Assertions.assertThat(helloFilePath).exists();
Assertions.assertThat(installedJar).exists();
}
}

View File

@@ -27,7 +27,7 @@ public class StopStatusTest {
DaemonRegistry registry;
@Test
void stopStatus() throws IOException {
void stopStatus() throws IOException, InterruptedException {
/* The registry should be empty before we run anything */
Assertions.assertThat(registry.getAll()).isEmpty();
@@ -42,7 +42,7 @@ public class StopStatusTest {
final ClientOutput output = Mockito.mock(ClientOutput.class);
client.execute(output, "--status").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.is(new MatchInOrderAmongOthers<>(
d.getUid() + " +" + d.getPid() + " +" + d.getAddress()));
@@ -73,7 +73,7 @@ public class StopStatusTest {
final ClientOutput output = Mockito.mock(ClientOutput.class);
client.execute(output, "--status").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(logMessage.capture());
Assertions.assertThat(
logMessage.getAllValues().stream()
.filter(m -> m.contains(d.getUid()))

View File

@@ -24,13 +24,13 @@ public class VersionTest {
ClientLayout layout;
@Test
void version() throws IOException {
void version() throws IOException, InterruptedException {
final ClientOutput output = Mockito.mock(ClientOutput.class);
client.execute(output, "-v").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.is(new MatchInOrderAmongOthers<>(

View File

@@ -0,0 +1,26 @@
package org.jboss.fuse.mvnd.junit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.jboss.fuse.mvnd.client.Client;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MvndTestExtension.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MvndNativeTest {
/**
* The path to the root directory of a test project relative to the current maven module directory. E.g.
* <code>@MvndTest(projectDir = "src/test/projects/my-project")</code>
*/
String projectDir();
/**
* Timeout for {@link Client#execute(org.jboss.fuse.mvnd.client.ClientOutput, java.util.List)} in seconds
*/
long timeoutSec() default 30;
}

View File

@@ -9,9 +9,9 @@ import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import org.jboss.fuse.mvnd.client.DefaultClient;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.ClientLayout;
import org.jboss.fuse.mvnd.client.DaemonInfo;
@@ -38,8 +38,14 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
final Store store = context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL);
final Class<?> testClass = context.getRequiredTestClass();
final MvndTest mnvdTest = testClass.getAnnotation(MvndTest.class);
store.put(MvndResource.class.getName(),
MvndResource.create(context.getRequiredTestClass().getSimpleName(), mnvdTest.projectDir()));
if (mnvdTest != null) {
store.put(MvndResource.class.getName(),
MvndResource.create(context.getRequiredTestClass().getSimpleName(), mnvdTest.projectDir(), false, -1L));
} else {
final MvndNativeTest mvndNativeTest = testClass.getAnnotation(MvndNativeTest.class);
store.put(MvndResource.class.getName(),
MvndResource.create(context.getRequiredTestClass().getSimpleName(), mvndNativeTest.projectDir(), true, mvndNativeTest.timeoutSec() * 1000L));
}
} catch (Exception e) {
this.bootException = e;
}
@@ -67,7 +73,16 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
} else if (f.getType() == Layout.class) {
f.set(testInstance, resource.layout);
} else if (f.getType() == Client.class) {
f.set(testInstance, new Client(resource.layout));
if (resource.isNative) {
final Path mvndNativeExecutablePath = Paths.get(System.getProperty("mvnd.native.executable"));
if (!Files.isRegularFile(mvndNativeExecutablePath)) {
throw new IllegalStateException("mvnd executable does not exist: " + mvndNativeExecutablePath);
}
f.set(testInstance, new NativeTestClient(resource.layout, mvndNativeExecutablePath, resource.timeoutMs));
} else {
f.set(testInstance, new DefaultClient(resource.layout));
}
} else if (f.getType() == NativeTestClient.class) {
}
}
}
@@ -88,8 +103,10 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
private final ClientLayout layout;
private final DaemonRegistry registry;
private final boolean isNative;
private final long timeoutMs;
public static MvndResource create(String className, String rawProjectDir) throws IOException {
public static MvndResource create(String className, String rawProjectDir, boolean isNative, long timeoutMs) throws IOException {
if (rawProjectDir == null) {
throw new IllegalStateException("rawProjectDir of @MvndTest must be set");
}
@@ -134,7 +151,7 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
localMavenRepository, settingsPath);
final DaemonRegistry registry = new DaemonRegistry(layout.registry());
return new MvndResource(layout, registry);
return new MvndResource(layout, registry, isNative, timeoutMs);
}
static Path deleteDir(Path dir) {
@@ -171,10 +188,12 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
return settingsPath;
}
public MvndResource(ClientLayout layout, DaemonRegistry registry) {
public MvndResource(ClientLayout layout, DaemonRegistry registry, boolean isNative, long timeoutMs) {
super();
this.layout = layout;
this.registry = registry;
this.isNative = isNative;
this.timeoutMs = timeoutMs;
}
@Override

View File

@@ -0,0 +1,193 @@
package org.jboss.fuse.mvnd.junit;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.ClientLayout;
import org.jboss.fuse.mvnd.client.ClientOutput;
import org.jboss.fuse.mvnd.client.ExecutionResult;
/**
* A wrapper around the native executable.
*/
public class NativeTestClient implements Client {
public static final int TIMEOUT_EXIT_CODE = Integer.MIN_VALUE + 42;
private final ClientLayout layout;
private final Path mvndNativeExecutablePath;
private final long timeoutMs;
public NativeTestClient(ClientLayout layout, Path mvndNativeExecutablePath, long timeoutMs) {
super();
this.layout = layout;
this.mvndNativeExecutablePath = mvndNativeExecutablePath;
this.timeoutMs = timeoutMs;
}
@Override
public ExecutionResult execute(ClientOutput output, List<String> args) throws InterruptedException {
final List<String> cmd = new ArrayList<String>(args.size() + 1);
cmd.add(mvndNativeExecutablePath.toString());
args.stream().forEach(cmd::add);
cmd.add("-Dmaven.repo.local=" + layout.getLocalMavenRepository().toString());
ProcessBuilder builder = new ProcessBuilder(cmd.toArray(new String[0]))
.directory(layout.userDir().toFile()) //
.redirectErrorStream(false);
Map<String, String> env = builder.environment();
env.put("MAVEN_HOME", System.getProperty("mvnd.home"));
env.put("JAVA_HOME", System.getProperty("java.home"));
final String cmdString = cmd.stream().collect(Collectors.joining(" "));
output.accept("Executing " + cmdString);
try (CommandProcess process = new CommandProcess(builder.start(), cmd, output)) {
return process.waitFor(timeoutMs);
} catch (IOException e) {
throw new RuntimeException("Could not execute: " + cmdString, e);
}
}
public static class Result implements ExecutionResult {
private final int exitCode;
private final List<String> args;
public Result(List<String> args, int exitCode) {
super();
this.args = new ArrayList<>(args);
this.exitCode = exitCode;
}
StringBuilder appendCommand(StringBuilder sb) {
sb.append("mvnd");
for (String arg : args) {
sb.append(" \"").append(arg).append('"');
}
return sb;
}
public Result assertFailure() {
if (exitCode == 0) {
throw new AssertionError(appendCommand(
new StringBuilder("mvnd returned ").append(exitCode).append(" instead of non-zero exit code: ")));
}
return this;
}
public Result assertSuccess() {
if (exitCode != 0) {
throw new AssertionError(appendCommand(new StringBuilder("mvnd returned ").append(exitCode).append(": ")));
}
return this;
}
public int getExitCode() {
return exitCode;
}
public boolean isSuccess() {
return exitCode == 0;
}
}
/**
* A simple wrapper over {@link Process} that manages its destroying and offers Java 8-like
* {@link #waitFor(long, TimeUnit, String[])} with timeout.
*/
static class CommandProcess implements AutoCloseable {
private final Process process;
private final Thread shutDownHook;
private final StreamGobbler stdOut;
private List<String> args;
public CommandProcess(Process process, List<String> args, Consumer<String> outputConsumer) {
super();
this.process = process;
this.args = args;
this.stdOut = new StreamGobbler(process.getInputStream(), outputConsumer);
stdOut.start();
this.shutDownHook = new Thread(new Runnable() {
@Override
public void run() {
stdOut.cancel();
CommandProcess.this.process.destroy();
}
});
Runtime.getRuntime().addShutdownHook(shutDownHook);
}
@Override
public void close() {
process.destroy();
}
public ExecutionResult waitFor(long timeoutMs) throws InterruptedException, IOException {
final long deadline = System.currentTimeMillis() + timeoutMs;
final boolean timeouted = !process.waitFor(timeoutMs, TimeUnit.MILLISECONDS);
timeoutMs = Math.max(0, deadline - System.currentTimeMillis());
stdOut.join(timeoutMs);
stdOut.assertSuccess();
try {
Runtime.getRuntime().removeShutdownHook(shutDownHook);
} catch (Exception ignored) {
}
return new Result(args, timeouted ? TIMEOUT_EXIT_CODE : process.exitValue());
}
}
/**
* The usual friend of {@link Process#getInputStream()} / {@link Process#getErrorStream()}.
*/
static class StreamGobbler extends Thread {
private volatile boolean cancelled;
private IOException exception;
private final InputStream in;
private final Consumer<String> out;
private StreamGobbler(InputStream in, Consumer<String> out) {
this.in = in;
this.out = out;
}
public void assertSuccess() throws IOException {
if (exception != null) {
throw exception;
}
}
public void cancel() {
this.cancelled = true;
}
@Override
public void run() {
try (BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
String line;
while (!cancelled && (line = r.readLine()) != null) {
out.accept(line);
}
} catch (IOException e) {
exception = e;
}
}
}
}

11
pom.xml
View File

@@ -57,6 +57,12 @@
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>${graalvm.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
@@ -178,6 +184,11 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>