mirror of
https://github.com/apache/maven-mvnd.git
synced 2025-09-09 15:09:23 +00:00
Native tests
This commit is contained in:
72
.github/workflows/verify.yaml
vendored
72
.github/workflows/verify.yaml
vendored
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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();
|
||||
|
||||
}
|
@@ -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 {
|
||||
|
@@ -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>
|
@@ -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)
|
||||
|
@@ -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*/
|
||||
}
|
||||
}
|
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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()))
|
||||
|
@@ -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<>(
|
||||
|
@@ -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;
|
||||
}
|
@@ -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
|
||||
|
@@ -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
11
pom.xml
@@ -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>
|
||||
|
Reference in New Issue
Block a user