mirror of
https://github.com/apache/maven-mvnd.git
synced 2025-09-10 04:59:54 +00:00
JUnit 5 extension for testing mvnd
This commit is contained in:
@@ -18,7 +18,6 @@
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-embedder</artifactId>
|
||||
<version>${mavenVersion}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.takari.aether</groupId>
|
||||
@@ -135,7 +134,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${mavenSurefirePluginVersion}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>integration-test</id>
|
||||
|
@@ -15,23 +15,22 @@
|
||||
*/
|
||||
package org.jboss.fuse.mvnd.daemon;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
|
||||
|
||||
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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.cli.UnrecognizedOptionException;
|
||||
import org.apache.maven.cli.CLIReportingUtils;
|
||||
import org.jboss.fuse.mvnd.daemon.ClientOutput.TerminalOutput;
|
||||
import org.jboss.fuse.mvnd.daemon.Message.BuildEvent;
|
||||
import org.jboss.fuse.mvnd.daemon.Message.BuildException;
|
||||
import org.jboss.fuse.mvnd.daemon.Message.BuildMessage;
|
||||
@@ -39,26 +38,46 @@ import org.jboss.fuse.mvnd.daemon.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.jline.terminal.Size;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStringBuilder;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
import org.jline.utils.Display;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
|
||||
|
||||
public class Client {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
|
||||
public static final String DAEMON_DEBUG = "daemon.debug";
|
||||
private final Layout layout;
|
||||
|
||||
public static void main(String[] argv) throws Exception {
|
||||
LOGGER.debug("Starting client");
|
||||
List<String> args = new ArrayList<>(Arrays.asList(argv));
|
||||
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(Layout.getEnvInstance()).execute(output, args);
|
||||
}
|
||||
}
|
||||
|
||||
public Client(Layout layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
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.remove("-v") || args.remove("-version") || args.remove("--version");
|
||||
@@ -72,148 +91,81 @@ public class Client {
|
||||
String v = buffer().strong( "Maven Daemon " + props.getProperty("version") ).toString()
|
||||
+ System.getProperty( "line.separator" )
|
||||
+ CLIReportingUtils.showVersion();
|
||||
System.out.println(v);
|
||||
output.log(v);
|
||||
if (version) {
|
||||
return;
|
||||
return new ClientResult<O>(argv, true, output);
|
||||
}
|
||||
}
|
||||
|
||||
Path javaHome = Layout.javaHome();
|
||||
DaemonRegistry registry = DaemonRegistry.getDefault();
|
||||
|
||||
boolean status = args.remove("--status");
|
||||
if (status) {
|
||||
System.out.println(String.format(" %36s %5s %5s %7s %s",
|
||||
"UUID", "PID", "Port", "Status", "Timestamp"));
|
||||
registry.getAll().forEach(d ->
|
||||
System.out.println(String.format(" %36s %5s %5s %7s %s",
|
||||
d.getUid(), d.getPid(), d.getAddress(), d.getState(),
|
||||
new Date(Math.max(d.getLastIdle(), d.getLastBusy())).toString())));
|
||||
return;
|
||||
}
|
||||
boolean stop = args.remove("--stop");
|
||||
if (stop) {
|
||||
DaemonInfo[] dis = registry.getAll().toArray(new DaemonInfo[0]);
|
||||
if (dis.length > 0) {
|
||||
System.out.println("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());
|
||||
final Path javaHome = layout.javaHome();
|
||||
try (DaemonRegistry registry = new DaemonRegistry(layout.registry())) {
|
||||
boolean status = args.remove("--status");
|
||||
if (status) {
|
||||
output.log(String.format(" %36s %5s %5s %7s %s",
|
||||
"UUID", "PID", "Port", "Status", "Timestamp"));
|
||||
registry.getAll().forEach(d -> output.log(String.format(" %36s %5s %5s %7s %s",
|
||||
d.getUid(), d.getPid(), d.getAddress(), d.getState(),
|
||||
new Date(Math.max(d.getLastIdle(), d.getLastBusy())).toString())));
|
||||
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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String 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 = args.get(i + 1);
|
||||
args.remove(i);
|
||||
args.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
setDefaultArgs(args);
|
||||
|
||||
setDefaultArgs(args);
|
||||
DaemonConnector connector = new DaemonConnector(layout, registry, this::startDaemon, new MessageSerializer());
|
||||
List<String> opts = new ArrayList<>();
|
||||
DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome.toString(), opts));
|
||||
|
||||
DaemonConnector connector = new DaemonConnector(registry, Client::startDaemon, new MessageSerializer());
|
||||
List<String> opts = new ArrayList<>();
|
||||
DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome.toString(), opts));
|
||||
daemon.dispatch(new Message.BuildRequest(
|
||||
args,
|
||||
layout.userDir().toString(),
|
||||
layout.multiModuleProjectDirectory().toString()));
|
||||
|
||||
daemon.dispatch(new Message.BuildRequest(
|
||||
args,
|
||||
Layout.getProperty("user.dir"),
|
||||
Layout.getProperty("maven.multiModuleProjectDirectory")));
|
||||
|
||||
List<String> log = new ArrayList<>();
|
||||
LinkedHashMap<String, String> projects = new LinkedHashMap<>();
|
||||
Terminal terminal = TerminalBuilder.terminal();
|
||||
Display display = new Display(terminal, false);
|
||||
boolean exit = false;
|
||||
BuildException error = null;
|
||||
long lastUpdate = 0;
|
||||
while (!exit) {
|
||||
Message m = daemon.receive();
|
||||
if (m instanceof BuildException) {
|
||||
error = (BuildException) m;
|
||||
exit = true;
|
||||
} else if (m instanceof BuildEvent) {
|
||||
BuildEvent be = (BuildEvent) m;
|
||||
switch (be.getType()) {
|
||||
case BuildStarted:
|
||||
break;
|
||||
case BuildStopped:
|
||||
exit = true;
|
||||
break;
|
||||
case ProjectStarted:
|
||||
case MojoStarted:
|
||||
case MojoStopped:
|
||||
projects.put(be.projectId, be.display);
|
||||
break;
|
||||
case ProjectStopped:
|
||||
projects.remove(be.projectId);
|
||||
}
|
||||
// no need to refresh the display at every single step
|
||||
long curTime = System.currentTimeMillis();
|
||||
if (curTime - lastUpdate >= 10) {
|
||||
Size size = terminal.getSize();
|
||||
display.resize(size.getRows(), size.getColumns());
|
||||
List<AttributedString> lines = new ArrayList<>();
|
||||
projects.values().stream()
|
||||
.map(AttributedString::fromAnsi)
|
||||
.map(s -> s.columnSubSequence(0, size.getColumns() - 1))
|
||||
.forEachOrdered(lines::add);
|
||||
// Make sure we don't try to display more lines than the terminal height
|
||||
int rem = 0;
|
||||
while (lines.size() >= terminal.getHeight()) {
|
||||
lines.remove(0);
|
||||
rem++;
|
||||
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);
|
||||
}
|
||||
lines.add(0, new AttributedString("Building..." + (rem > 0 ? " (" + rem + " more)" : "")));
|
||||
display.update(lines, -1);
|
||||
lastUpdate = curTime;
|
||||
}
|
||||
} else if (m instanceof BuildMessage) {
|
||||
BuildMessage bm = (BuildMessage) m;
|
||||
log.add(bm.getMessage());
|
||||
}
|
||||
}
|
||||
display.update(Collections.emptyList(), 0);
|
||||
if (error != null) {
|
||||
AttributedStyle s = new AttributedStyle().bold().foreground(AttributedStyle.RED);
|
||||
String msg;
|
||||
if (UnrecognizedOptionException.class.getName().equals(error.getClassName())) {
|
||||
msg = "Unable to parse command line options: " + error.getMessage();
|
||||
} else {
|
||||
msg = error.getClassName() + ": " + error.getMessage();
|
||||
}
|
||||
terminal.writer().println(new AttributedString(msg, s).toAnsi());
|
||||
}
|
||||
terminal.flush();
|
||||
|
||||
LOGGER.debug("Done receiving, printing log");
|
||||
|
||||
if (logFile != null) {
|
||||
try (BufferedWriter bw = Files.newBufferedWriter(Paths.get(logFile))) {
|
||||
for (String l : log) {
|
||||
bw.write(l);
|
||||
bw.newLine();
|
||||
} else if (m instanceof BuildMessage) {
|
||||
BuildMessage bm = (BuildMessage) m;
|
||||
output.log(bm.getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.forEach(terminal.writer()::println);
|
||||
terminal.flush();
|
||||
}
|
||||
|
||||
LOGGER.debug("Done !");
|
||||
}
|
||||
|
||||
static void setDefaultArgs(List<String> args) {
|
||||
@@ -225,7 +177,7 @@ public class Client {
|
||||
}
|
||||
}
|
||||
|
||||
public static String startDaemon() {
|
||||
String startDaemon() {
|
||||
// DaemonParameters parms = new DaemonParameters();
|
||||
// for (String arg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
|
||||
//
|
||||
@@ -237,9 +189,9 @@ public class Client {
|
||||
// args.add(classpath);
|
||||
|
||||
String uid = UUID.randomUUID().toString();
|
||||
Path mavenHome = Layout.mavenHome();
|
||||
Path javaHome = Layout.javaHome();
|
||||
Path workingDir = Layout.userDir();
|
||||
Path mavenHome = layout.mavenHome();
|
||||
Path javaHome = layout.javaHome();
|
||||
Path workingDir = layout.userDir();
|
||||
String command = "";
|
||||
try {
|
||||
String url = ServerMain.class.getClassLoader().getResource(Server.class.getName().replace('.', '/') + ".class").toString();
|
||||
@@ -259,6 +211,7 @@ public class Client {
|
||||
if (System.getProperty(Server.DAEMON_IDLE_TIMEOUT) != null) {
|
||||
args.add("-D" + Server.DAEMON_IDLE_TIMEOUT + "=" + System.getProperty(Server.DAEMON_IDLE_TIMEOUT));
|
||||
}
|
||||
args.add("\"-Dmaven.multiModuleProjectDirectory=" + layout.multiModuleProjectDirectory().toString() + "\"");
|
||||
|
||||
args.add(ServerMain.class.getName());
|
||||
command = String.join(" ", args);
|
||||
|
@@ -0,0 +1,192 @@
|
||||
package org.jboss.fuse.mvnd.daemon;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.cli.UnrecognizedOptionException;
|
||||
import org.jboss.fuse.mvnd.daemon.Message.BuildException;
|
||||
import org.jline.terminal.Size;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
import org.jline.utils.Display;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A sink for various kinds of events sent by the daemon.
|
||||
*/
|
||||
public interface ClientOutput extends AutoCloseable {
|
||||
|
||||
public void projectStateChanged(String projectId, String display);
|
||||
|
||||
public void projectFinished(String projectId);
|
||||
|
||||
public void log(String message);
|
||||
|
||||
public void error(BuildException m);
|
||||
|
||||
public void debug(String string);
|
||||
|
||||
/**
|
||||
* A terminal {@link ClientOutput} based on JLine.
|
||||
*/
|
||||
static class TerminalOutput implements ClientOutput {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TerminalOutput.class);
|
||||
|
||||
private final Terminal terminal;
|
||||
private final Display display;
|
||||
private final LinkedHashMap<String, String> projects = new LinkedHashMap<>();
|
||||
private long lastUpdate = 0;
|
||||
private final Log log;
|
||||
|
||||
public TerminalOutput(Path logFile) throws IOException {
|
||||
this.terminal = TerminalBuilder.terminal();
|
||||
this.display = new Display(terminal, false);
|
||||
this.log = logFile == null ? new ClientOutput.Log.MessageCollector(terminal)
|
||||
: new ClientOutput.Log.FileLog(logFile);
|
||||
}
|
||||
|
||||
public void projectStateChanged(String projectId, String task) {
|
||||
projects.put(projectId, task);
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
// no need to refresh the display at every single step
|
||||
long curTime = System.currentTimeMillis();
|
||||
if (curTime - lastUpdate >= 10) {
|
||||
Size size = terminal.getSize();
|
||||
display.resize(size.getRows(), size.getColumns());
|
||||
List<AttributedString> lines = new ArrayList<>();
|
||||
projects.values().stream()
|
||||
.map(AttributedString::fromAnsi)
|
||||
.map(s -> s.columnSubSequence(0, size.getColumns() - 1))
|
||||
.forEachOrdered(lines::add);
|
||||
// Make sure we don't try to display more lines than the terminal height
|
||||
int rem = 0;
|
||||
while (lines.size() >= terminal.getHeight()) {
|
||||
lines.remove(0);
|
||||
rem++;
|
||||
}
|
||||
lines.add(0, new AttributedString("Building..." + (rem > 0 ? " (" + rem + " more)" : "")));
|
||||
display.update(lines, -1);
|
||||
lastUpdate = curTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void projectFinished(String projectId) {
|
||||
projects.remove(projectId);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String message) {
|
||||
log.accept(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
display.update(Collections.emptyList(), 0);
|
||||
LOGGER.debug("Done receiving, printing log");
|
||||
log.close();
|
||||
LOGGER.debug("Done !");
|
||||
terminal.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(BuildException error) {
|
||||
display.update(Collections.emptyList(), 0);
|
||||
final AttributedStyle s = new AttributedStyle().bold().foreground(AttributedStyle.RED);
|
||||
final String msg;
|
||||
if (UnrecognizedOptionException.class.getName().equals(error.getClassName())) {
|
||||
msg = "Unable to parse command line options: " + error.getMessage();
|
||||
} else {
|
||||
msg = error.getClassName() + ": " + error.getMessage();
|
||||
}
|
||||
terminal.writer().println(new AttributedString(msg, s).toAnsi());
|
||||
terminal.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String msg) {
|
||||
LOGGER.debug(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A closeable string message consumer.
|
||||
*/
|
||||
interface Log extends Consumer<String>, AutoCloseable {
|
||||
|
||||
/**
|
||||
* A {@link Log} backed by a file.
|
||||
*/
|
||||
public static class FileLog implements Log {
|
||||
|
||||
private final Writer out;
|
||||
private Path logFile;
|
||||
|
||||
public FileLog(Path logFile) throws IOException {
|
||||
super();
|
||||
this.out = Files.newBufferedWriter(logFile, StandardCharsets.UTF_8);
|
||||
this.logFile = logFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(String message) {
|
||||
try {
|
||||
out.write(message);
|
||||
out.write('\n');
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not write to " + logFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Log} that first collects all incoming messages in a {@link List} and outputs them to a JLine
|
||||
* {@link Terminal} upon {@link #close()}.
|
||||
*/
|
||||
public static class MessageCollector implements Log {
|
||||
|
||||
private final List<String> messages = new ArrayList<>();
|
||||
private final Terminal terminal;
|
||||
|
||||
public MessageCollector(Terminal terminal) {
|
||||
super();
|
||||
this.terminal = terminal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(String message) {
|
||||
messages.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
messages.forEach(terminal.writer()::println);
|
||||
terminal.flush();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
package org.jboss.fuse.mvnd.daemon;
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
@@ -43,10 +43,12 @@ public class DaemonConnector {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DaemonConnector.class);
|
||||
|
||||
private final DaemonRegistry registry;
|
||||
private final Layout layout;
|
||||
private final DaemonStarter daemonStarter;
|
||||
private final Serializer<Message> serializer;
|
||||
|
||||
public DaemonConnector(DaemonRegistry registry, DaemonStarter daemonStarter, Serializer<Message> serializer) {
|
||||
public DaemonConnector(Layout layout, DaemonRegistry registry, DaemonStarter daemonStarter, Serializer<Message> serializer) {
|
||||
this.layout = layout;
|
||||
this.registry = registry;
|
||||
this.daemonStarter = daemonStarter;
|
||||
this.serializer = serializer;
|
||||
@@ -214,7 +216,7 @@ public class DaemonConnector {
|
||||
throw new DaemonException.InterruptedException(e);
|
||||
}
|
||||
} while (System.currentTimeMillis() - start < DEFAULT_CONNECT_TIMEOUT);
|
||||
DaemonDiagnostics diag = new DaemonDiagnostics(daemon);
|
||||
DaemonDiagnostics diag = new DaemonDiagnostics(daemon, layout.daemonLog(daemon));
|
||||
throw new DaemonException.ConnectException("Timeout waiting to connect to the Maven daemon.\n" + diag.describe());
|
||||
}
|
||||
|
||||
@@ -225,7 +227,7 @@ public class DaemonConnector {
|
||||
try {
|
||||
return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo, false));
|
||||
} catch (DaemonException.ConnectException e) {
|
||||
DaemonDiagnostics diag = new DaemonDiagnostics(daemon);
|
||||
DaemonDiagnostics diag = new DaemonDiagnostics(daemon, layout.daemonLog(daemon));
|
||||
throw new DaemonException.ConnectException("Could not connect to the Maven daemon.\n" + diag.describe(), e);
|
||||
}
|
||||
}
|
||||
|
@@ -32,9 +32,9 @@ public class DaemonDiagnostics {
|
||||
private final String uid;
|
||||
private final Path daemonLog;
|
||||
|
||||
public DaemonDiagnostics(String uid) {
|
||||
public DaemonDiagnostics(String uid, Path daemonLog) {
|
||||
this.uid = uid;
|
||||
this.daemonLog = Layout.daemonLog(uid);
|
||||
this.daemonLog = daemonLog;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -89,10 +89,6 @@ public class DaemonRegistry implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
public static DaemonRegistry getDefault() {
|
||||
return new DaemonRegistry(Layout.registry());
|
||||
}
|
||||
|
||||
public Path getRegistryFile() {
|
||||
return registryFile;
|
||||
}
|
||||
|
@@ -17,34 +17,61 @@ package org.jboss.fuse.mvnd.daemon;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Layout {
|
||||
|
||||
public static Path javaHome() {
|
||||
return Paths.get(getProperty("java.home")).toAbsolutePath().normalize();
|
||||
private static Layout ENV_INSTANCE;
|
||||
|
||||
private final Path javaHome;
|
||||
private final Path mavenHome;
|
||||
private final Path userDir;
|
||||
private final Path multiModuleProjectDirectory;
|
||||
|
||||
public Layout(Path javaHome, Path mavenHome, Path userDir, Path multiModuleProjectDirectory) {
|
||||
super();
|
||||
this.javaHome = javaHome;
|
||||
this.mavenHome = mavenHome;
|
||||
this.userDir = userDir;
|
||||
this.multiModuleProjectDirectory = multiModuleProjectDirectory;
|
||||
}
|
||||
|
||||
public static Path mavenHome() {
|
||||
return Paths.get(getProperty("maven.home")).toAbsolutePath().normalize();
|
||||
public Path javaHome() {
|
||||
return javaHome;
|
||||
}
|
||||
|
||||
public static Path userDir() {
|
||||
return Paths.get(getProperty("user.dir")).toAbsolutePath().normalize();
|
||||
public Path mavenHome() {
|
||||
return mavenHome;
|
||||
}
|
||||
|
||||
public static Path registry() {
|
||||
return mavenHome().resolve("daemon/registry.bin");
|
||||
public Path userDir() {
|
||||
return userDir;
|
||||
}
|
||||
|
||||
public static Path daemonLog(String daemon) {
|
||||
return mavenHome().resolve("daemon/daemon-" + daemon + ".log");
|
||||
public Path registry() {
|
||||
return mavenHome.resolve("daemon/registry.bin");
|
||||
}
|
||||
|
||||
public static String getProperty(String key) {
|
||||
public Path daemonLog(String daemon) {
|
||||
return mavenHome.resolve("daemon/daemon-" + daemon + ".log");
|
||||
}
|
||||
|
||||
public Path multiModuleProjectDirectory() {
|
||||
return multiModuleProjectDirectory;
|
||||
}
|
||||
|
||||
private static String getProperty(String key) {
|
||||
return Objects.requireNonNull(System.getProperty(key), "Undefined system property: " + key);
|
||||
}
|
||||
|
||||
public static Layout getEnvInstance() {
|
||||
if (ENV_INSTANCE == null) {
|
||||
ENV_INSTANCE = new Layout(Paths.get(getProperty("java.home")).toAbsolutePath().normalize(),
|
||||
Paths.get(getProperty("maven.home")).toAbsolutePath().normalize(),
|
||||
Paths.get(getProperty("user.dir")).toAbsolutePath().normalize(),
|
||||
Paths.get(getProperty("maven.multiModuleProjectDirectory")).toAbsolutePath().normalize());
|
||||
}
|
||||
return ENV_INSTANCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -70,6 +70,7 @@ public class Server implements AutoCloseable, Runnable {
|
||||
private DaemonMavenCli cli;
|
||||
private DaemonInfo info;
|
||||
private DaemonRegistry registry;
|
||||
private final Layout layout;
|
||||
|
||||
private ScheduledExecutorService executor;
|
||||
private DaemonExpirationStrategy strategy;
|
||||
@@ -79,9 +80,11 @@ public class Server implements AutoCloseable, Runnable {
|
||||
|
||||
public Server(String uid) throws IOException {
|
||||
this.uid = uid;
|
||||
this.layout = Layout.getEnvInstance();
|
||||
try {
|
||||
cli = new DaemonMavenCli();
|
||||
registry = DaemonRegistry.getDefault();
|
||||
|
||||
registry = new DaemonRegistry(layout.registry());
|
||||
socket = ServerSocketChannel.open().bind(new InetSocketAddress(0));
|
||||
|
||||
int idleTimeout;
|
||||
@@ -95,7 +98,7 @@ public class Server implements AutoCloseable, Runnable {
|
||||
|
||||
List<String> opts = new ArrayList<>();
|
||||
long cur = System.currentTimeMillis();
|
||||
info = new DaemonInfo(uid, Layout.javaHome().toString(), Layout.mavenHome().toString(),
|
||||
info = new DaemonInfo(uid, layout.javaHome().toString(), layout.mavenHome().toString(),
|
||||
DaemonRegistry.getProcessId(), socket.socket().getLocalPort(),
|
||||
idleTimeout, Locale.getDefault().toLanguageTag(), opts,
|
||||
Busy, cur, cur);
|
||||
|
@@ -23,7 +23,7 @@
|
||||
exec 1>/dev/null
|
||||
exec 2>/dev/null
|
||||
if [ "x${dir}" != "x" ]; then
|
||||
cd ${dir}
|
||||
cd "${dir}"
|
||||
fi
|
||||
nohup ${command} &
|
||||
echo $! > "${pid.file}"
|
||||
|
62
integration-tests/pom.xml
Normal file
62
integration-tests/pom.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.jboss.fuse.mvnd</groupId>
|
||||
<artifactId>mvnd</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mvnd-integration-tests</artifactId>
|
||||
|
||||
<name>Maven Daemon - Integration Tests</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jakarta.inject</groupId>
|
||||
<artifactId>jakarta.inject-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.fuse.mvnd</groupId>
|
||||
<artifactId>mvnd-daemon</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-model</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<project.version>${project.version}</project.version>
|
||||
<mvnd.home>${project.basedir}/../daemon/target/maven-distro</mvnd.home>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@@ -0,0 +1,40 @@
|
||||
package org.jboss.fuse.mvnd.assertj;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
|
||||
/**
|
||||
* An AssertJ {@link Condition} to assert that each item of a collection of regular expressions matches some item in
|
||||
* a list of strings exactly once in the order given by the pattern collection. The input list may contain other
|
||||
* non-matching items.
|
||||
*
|
||||
* @param <T> the type of the tested {@link List}.
|
||||
*/
|
||||
public class MatchInOrderAmongOthers<T extends List<? extends String>> extends Condition<T> {
|
||||
|
||||
public MatchInOrderAmongOthers(String... expectedItems) {
|
||||
this(Stream.of(expectedItems).map(Pattern::compile).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public MatchInOrderAmongOthers(final Collection<Pattern> patterns) {
|
||||
super(
|
||||
messages -> messages.stream()
|
||||
/* map each message to the matching pattern or null of none matches */
|
||||
.map(m -> patterns.stream()
|
||||
.filter(pat -> pat.matcher(m).find())
|
||||
.findFirst()
|
||||
.orElse(null))
|
||||
.filter(pat -> pat != null) /* remove null patterns */
|
||||
.collect(Collectors.toList())
|
||||
/* if the mapped patterns equal the input patterns then each pattern matched exactly once */
|
||||
.equals(patterns),
|
||||
"Match in order: " + patterns.stream().map(Pattern::pattern).collect(Collectors.joining(", ")),
|
||||
patterns);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package org.jboss.fuse.mvnd.it;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
|
||||
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
|
||||
|
||||
public class MvndTestUtil {
|
||||
|
||||
private MvndTestUtil() {
|
||||
}
|
||||
|
||||
public static String plugin(Properties props, String artifactId) {
|
||||
return artifactId + ":" + props.getProperty(artifactId + ".version");
|
||||
}
|
||||
|
||||
public static Properties properties(Path pomXmlPath) {
|
||||
try (Reader runtimeReader = Files.newBufferedReader(pomXmlPath, StandardCharsets.UTF_8)) {
|
||||
final MavenXpp3Reader rxppReader = new MavenXpp3Reader();
|
||||
return rxppReader.read(runtimeReader).getProperties();
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
throw new RuntimeException("Could not read or parse " + pomXmlPath);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
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.daemon.Client;
|
||||
import org.jboss.fuse.mvnd.daemon.ClientOutput;
|
||||
import org.jboss.fuse.mvnd.daemon.Layout;
|
||||
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
|
||||
Layout layout;
|
||||
|
||||
@Test
|
||||
void cleanTest() throws IOException {
|
||||
final Path helloFilePath = layout.multiModuleProjectDirectory().resolve("target/hello.txt");
|
||||
if (Files.exists(helloFilePath)) {
|
||||
Files.delete(helloFilePath);
|
||||
}
|
||||
|
||||
final ClientOutput output = Mockito.mock(ClientOutput.class);
|
||||
client.execute(output, "clean", "test").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",
|
||||
"SUCCESS build of project org.jboss.fuse.mvnd.test.single-module:single-module"));
|
||||
|
||||
final Properties props = MvndTestUtil.properties(layout.multiModuleProjectDirectory().resolve("pom.xml"));
|
||||
|
||||
final InOrder inOrder = Mockito.inOrder(output);
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-clean-plugin")
|
||||
+ ":clean {execution: default-clean}");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-resources-plugin")
|
||||
+ ":resources {execution: default-resources}");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-compiler-plugin")
|
||||
+ ":compile {execution: default-compile}");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-resources-plugin")
|
||||
+ ":testResources {execution: default-testResources}");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-compiler-plugin")
|
||||
+ ":testCompile {execution: default-testCompile}");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-surefire-plugin")
|
||||
+ ":test {execution: default-test}");
|
||||
inOrder.verify(output).projectStateChanged(
|
||||
"single-module",
|
||||
":single-module");
|
||||
|
||||
inOrder.verify(output).projectFinished("single-module");
|
||||
|
||||
/* The target/hello.txt is created by HelloTest */
|
||||
Assertions.assertThat(helloFilePath).exists();
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package org.jboss.fuse.mvnd.it;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.jboss.fuse.mvnd.assertj.MatchInOrderAmongOthers;
|
||||
import org.jboss.fuse.mvnd.daemon.Client;
|
||||
import org.jboss.fuse.mvnd.daemon.ClientOutput;
|
||||
import org.jboss.fuse.mvnd.daemon.DaemonInfo;
|
||||
import org.jboss.fuse.mvnd.daemon.DaemonRegistry;
|
||||
import org.jboss.fuse.mvnd.junit.MvndTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@MvndTest(projectDir = "src/test/projects/single-module")
|
||||
public class StopStatusTest {
|
||||
|
||||
@Inject
|
||||
Client client;
|
||||
|
||||
@Inject
|
||||
DaemonRegistry registry;
|
||||
|
||||
@Test
|
||||
void stopStatus() throws IOException {
|
||||
|
||||
/* The registry should be empty before we run anything */
|
||||
Assertions.assertThat(registry.getAll()).isEmpty();
|
||||
|
||||
client.execute(Mockito.mock(ClientOutput.class), "clean").assertSuccess();
|
||||
/* There should be exactly one item in the registry after the first build */
|
||||
Assertions.assertThat(registry.getAll().size()).isEqualTo(1);
|
||||
|
||||
final ClientOutput output = Mockito.mock(ClientOutput.class);
|
||||
client.execute(output, "--status").assertSuccess();
|
||||
final DaemonInfo d = registry.getAll().get(0);
|
||||
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
|
||||
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
|
||||
Assertions.assertThat(logMessage.getAllValues())
|
||||
.is(new MatchInOrderAmongOthers<>(
|
||||
d.getUid() + " +" + d.getPid() + " +" + d.getAddress() + " +" + d.getState()));
|
||||
|
||||
client.execute(Mockito.mock(ClientOutput.class), "clean").assertSuccess();
|
||||
/* There should still be exactly one item in the registry after the second build */
|
||||
Assertions.assertThat(registry.getAll().size()).isEqualTo(1);
|
||||
|
||||
client.execute(Mockito.mock(ClientOutput.class), "--stop").assertSuccess();
|
||||
/* No items in the registry after we have killed all daemons */
|
||||
Assertions.assertThat(registry.getAll()).isEmpty();
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package org.jboss.fuse.mvnd.it;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.jboss.fuse.mvnd.assertj.MatchInOrderAmongOthers;
|
||||
import org.jboss.fuse.mvnd.daemon.Client;
|
||||
import org.jboss.fuse.mvnd.daemon.ClientOutput;
|
||||
import org.jboss.fuse.mvnd.junit.MvndTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@MvndTest(projectDir = "src/test/projects/single-module")
|
||||
public class VersionTest {
|
||||
|
||||
@Inject
|
||||
Client client;
|
||||
|
||||
@Test
|
||||
void version() throws IOException {
|
||||
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());
|
||||
|
||||
Assertions.assertThat(logMessage.getAllValues())
|
||||
.is(new MatchInOrderAmongOthers<>("Maven Daemon " + System.getProperty("project.version")));
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
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.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@ExtendWith(MvndTestExtension.class)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface MvndTest {
|
||||
/**
|
||||
* 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();
|
||||
}
|
@@ -0,0 +1,153 @@
|
||||
package org.jboss.fuse.mvnd.junit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jboss.fuse.mvnd.daemon.Client;
|
||||
import org.jboss.fuse.mvnd.daemon.DaemonInfo;
|
||||
import org.jboss.fuse.mvnd.daemon.DaemonRegistry;
|
||||
import org.jboss.fuse.mvnd.daemon.Layout;
|
||||
import org.jboss.fuse.mvnd.jpm.ProcessImpl;
|
||||
import org.junit.jupiter.api.extension.AfterAllCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeAllCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext.Store;
|
||||
|
||||
public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback, AfterAllCallback {
|
||||
|
||||
private volatile Exception bootException;
|
||||
public MvndTestExtension() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeAll(ExtensionContext context) throws Exception {
|
||||
try {
|
||||
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()));
|
||||
} catch (Exception e) {
|
||||
this.bootException = e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeEach(ExtensionContext context) throws Exception {
|
||||
if (bootException != null) {
|
||||
throw new Exception("Could not init " + context.getRequiredTestClass(), bootException);
|
||||
}
|
||||
final Store store = context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL);
|
||||
final MvndResource resource = (MvndResource) store.get(MvndResource.class.getName());
|
||||
|
||||
final Object testInstance = context.getRequiredTestInstance();
|
||||
Class<?> c = testInstance.getClass();
|
||||
while (c != Object.class) {
|
||||
for (Field f : c.getDeclaredFields()) {
|
||||
javax.inject.Inject inject = f.getAnnotation(javax.inject.Inject.class);
|
||||
if (inject != null) {
|
||||
f.setAccessible(true);
|
||||
if (f.getType() == DaemonRegistry.class) {
|
||||
f.set(testInstance, resource.registry);
|
||||
} else if (f.getType() == Layout.class) {
|
||||
f.set(testInstance, resource.layout);
|
||||
} else if (f.getType() == Client.class) {
|
||||
f.set(testInstance, new Client(resource.layout));
|
||||
}
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAll(ExtensionContext context) throws Exception {
|
||||
final Store store = context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL);
|
||||
final MvndResource resource = (MvndResource) store.remove(MvndResource.class.getName());
|
||||
if (resource != null) {
|
||||
resource.close();
|
||||
}
|
||||
}
|
||||
|
||||
static class MvndResource implements ExtensionContext.Store.CloseableResource {
|
||||
|
||||
private final Layout layout;
|
||||
private final DaemonRegistry registry;
|
||||
|
||||
public static MvndResource create(String className, String rawProjectDir) throws IOException {
|
||||
if (rawProjectDir == null) {
|
||||
throw new IllegalStateException("rawProjectDir of @MvndTest must be set");
|
||||
}
|
||||
final Path mvndTestSrcDir = Paths.get(rawProjectDir).toAbsolutePath().normalize();
|
||||
if (!Files.exists(mvndTestSrcDir)) {
|
||||
throw new IllegalStateException("@MvndTest(projectDir = \""+ rawProjectDir +"\") points at a path that does not exist: " + mvndTestSrcDir);
|
||||
}
|
||||
|
||||
final Path testDir = Paths.get("target/mvnd-tests/" + className).toAbsolutePath();
|
||||
try (Stream<Path> files = Files.walk(mvndTestSrcDir)) {
|
||||
files.forEach(source -> {
|
||||
final Path dest = testDir.resolve(mvndTestSrcDir.relativize(source));
|
||||
try {
|
||||
if (Files.isDirectory(source)) {
|
||||
Files.createDirectories(dest);
|
||||
} else {
|
||||
Files.createDirectories(dest.getParent());
|
||||
Files.copy(source, dest);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Path mvndHome = Paths.get(Objects.requireNonNull(System.getProperty("mvnd.home"), "System property mvnd.home must be set")).normalize().toAbsolutePath();
|
||||
if (!Files.isDirectory(mvndHome)) {
|
||||
throw new IllegalStateException("The value of mvnd.home system property points at a path that does not exist or is not a directory");
|
||||
}
|
||||
final Layout layout = new Layout(Paths.get(System.getProperty("java.home")).toAbsolutePath().normalize(),
|
||||
mvndHome,
|
||||
testDir,
|
||||
testDir);
|
||||
final DaemonRegistry registry = new DaemonRegistry(layout.registry());
|
||||
return new MvndResource(layout, registry);
|
||||
}
|
||||
|
||||
public MvndResource(Layout layout, DaemonRegistry registry) {
|
||||
super();
|
||||
this.layout = layout;
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
List<DaemonInfo> daemons;
|
||||
final int timeout = 5000;
|
||||
final long deadline = System.currentTimeMillis() + timeout;
|
||||
while (!(daemons = registry.getAll()).isEmpty()) {
|
||||
for (DaemonInfo di : daemons) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
if (deadline < System.currentTimeMillis() && !registry.getAll().isEmpty()) {
|
||||
throw new RuntimeException("Could not stop all mvnd daemons within " + timeout + " ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
54
integration-tests/src/test/projects/single-module/pom.xml
Normal file
54
integration-tests/src/test/projects/single-module/pom.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.jboss.fuse.mvnd.test.single-module</groupId>
|
||||
<artifactId>single-module</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven-clean-plugin.version>2.5</maven-clean-plugin.version>
|
||||
<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
|
||||
<maven-resources-plugin.version>2.6</maven-resources-plugin.version>
|
||||
<maven-surefire-plugin.version>3.0.0-M4</maven-surefire-plugin.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.6.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>${maven-clean-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>${maven-resources-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${maven-surefire-plugin.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
</project>
|
@@ -0,0 +1,9 @@
|
||||
package org.jboss.fuse.mvnd.test.single.module;
|
||||
|
||||
public class Hello {
|
||||
|
||||
public String sayHello() {
|
||||
return "Hello";
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package org.jboss.fuse.mvnd.test.single.module;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class HelloTest {
|
||||
@Test
|
||||
void hello() throws IOException {
|
||||
final String actual = new Hello().sayHello();
|
||||
Files.write(Paths.get("target/hello.txt"), actual.getBytes(StandardCharsets.UTF_8));
|
||||
Assertions.assertEquals("Hello", actual);
|
||||
}
|
||||
}
|
68
pom.xml
68
pom.xml
@@ -12,23 +12,89 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
|
||||
<assertj.version>3.16.1</assertj.version>
|
||||
<groovyVersion>3.0.0</groovyVersion>
|
||||
<jakarta.inject.version>1.0</jakarta.inject.version>
|
||||
<jlineVersion>3.12.1</jlineVersion>
|
||||
<junitVersion>4.12</junitVersion>
|
||||
<junit.jupiter.version>5.6.0</junit.jupiter.version>
|
||||
<logbackVersion>1.2.3</logbackVersion>
|
||||
<mavenVersion>3.6.3</mavenVersion>
|
||||
<mockito.version>3.3.3</mockito.version>
|
||||
<pluginTestingVersion>2.9.2</pluginTestingVersion>
|
||||
<slf4jVersion>1.7.25</slf4jVersion>
|
||||
<takariLifecycleVersion>1.13.9</takariLifecycleVersion>
|
||||
<takariProvisioVersion>0.1.56</takariProvisioVersion>
|
||||
<takariLocalRepositoryVersion>0.11.2</takariLocalRepositoryVersion>
|
||||
<mavenCompilerPluginVersion>3.8.1</mavenCompilerPluginVersion>
|
||||
<mavenSurefirePluginVersion>2.22.2</mavenSurefirePluginVersion>
|
||||
<surefire.version>3.0.0-M4</surefire.version>
|
||||
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>daemon</module>
|
||||
<module>integration-tests</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<version>${junit.jupiter.version}</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.inject</groupId>
|
||||
<artifactId>jakarta.inject-api</artifactId>
|
||||
<version>${jakarta.inject.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-model</artifactId>
|
||||
<version>${mavenVersion}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.fuse.mvnd</groupId>
|
||||
<artifactId>mvnd-daemon</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.fuse.mvnd</groupId>
|
||||
<artifactId>mvnd-junit5</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${surefire.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
Reference in New Issue
Block a user