Provide smarter output on the client, fixes #77

All events are directly forwarded to the client.  The client is now responsible for ordering them per project and displaying them if needed.  A thread is now started to read the terminal input with support for '+' to display one more line per project, '-' to display one line less, and 'Ctrl+L' to redraw the display which could become messed if the build messages are a bit unusual (this may require a better fix though).
This commit is contained in:
Guillaume Nodet
2020-10-07 13:44:48 +02:00
parent 41869a7115
commit dd32f41580
13 changed files with 305 additions and 156 deletions

View File

@@ -16,25 +16,28 @@
package org.jboss.fuse.mvnd.client;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.function.Consumer;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.jboss.fuse.mvnd.common.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.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.jline.utils.Display;
import org.slf4j.Logger;
@@ -43,24 +46,50 @@ import org.slf4j.LoggerFactory;
/**
* A sink for various kinds of events sent by the daemon.
*/
public interface ClientOutput extends AutoCloseable, Consumer<String> {
public interface ClientOutput extends AutoCloseable {
int CTRL_L = 'L' & 0x1f;
public void projectStateChanged(String projectId, String display);
public void projectFinished(String projectId);
/** Receive a log message */
public void accept(String message);
public void accept(String projectId, String message);
public void error(BuildException m);
enum EventType {
PROJECT_STATUS,
LOG,
ERROR,
END_OF_STREAM,
INPUT
}
class Event {
public final EventType type;
public final String projectId;
public final String message;
public Event(EventType type, String projectId, String message) {
this.type = type;
this.projectId = projectId;
this.message = message;
}
}
class Project {
String status;
final List<String> log = new ArrayList<>();
}
/**
* A terminal {@link ClientOutput} based on JLine.
*/
static class TerminalOutput implements ClientOutput {
private static final Logger LOGGER = LoggerFactory.getLogger(TerminalOutput.class);
private final TerminalUpdater updater;
private final BlockingQueue<Map.Entry<String, String>> queue;
private final BlockingQueue<Event> queue;
public TerminalOutput(Path logFile) throws IOException {
this.queue = new LinkedBlockingDeque<>();
@@ -69,7 +98,7 @@ public interface ClientOutput extends AutoCloseable, Consumer<String> {
public void projectStateChanged(String projectId, String task) {
try {
queue.put(new AbstractMap.SimpleImmutableEntry<>(projectId, task));
queue.put(new Event(EventType.PROJECT_STATUS, projectId, task));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
@@ -77,16 +106,16 @@ public interface ClientOutput extends AutoCloseable, Consumer<String> {
public void projectFinished(String projectId) {
try {
queue.put(new AbstractMap.SimpleImmutableEntry<>(projectId, null));
queue.put(new Event(EventType.PROJECT_STATUS, projectId, null));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void accept(String message) {
public void accept(String projectId, String message) {
try {
queue.put(new AbstractMap.SimpleImmutableEntry<>(TerminalUpdater.LOG, message));
queue.put(new Event(EventType.LOG, projectId, message));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
@@ -106,27 +135,28 @@ public interface ClientOutput extends AutoCloseable, Consumer<String> {
msg = error.getClassName() + ": " + error.getMessage();
}
try {
queue.put(new AbstractMap.SimpleImmutableEntry<>(TerminalUpdater.ERROR, msg));
queue.put(new Event(EventType.ERROR, null, msg));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static class TerminalUpdater implements AutoCloseable {
private static final String LOG = "<log>";
private static final String ERROR = "<error>";
private static final String END_OF_STREAM = "<eos>";
private final BlockingQueue<Map.Entry<String, String>> queue;
private final BlockingQueue<Event> queue;
private final Terminal terminal;
private final Display display;
private final LinkedHashMap<String, String> projects = new LinkedHashMap<>();
private final LinkedHashMap<String, Project> projects = new LinkedHashMap<>();
private final Log log;
private final Thread worker;
private final Thread reader;
private volatile Exception exception;
private volatile boolean closing;
private int linesPerProject = 0;
public TerminalUpdater(BlockingQueue<Entry<String, String>> queue, Path logFile) throws IOException {
public TerminalUpdater(BlockingQueue<Event> queue, Path logFile) throws IOException {
super();
this.terminal = TerminalBuilder.terminal();
terminal.enterRawMode();
this.display = new Display(terminal, false);
this.log = logFile == null ? new ClientOutput.Log.MessageCollector(terminal)
: new ClientOutput.Log.FileLog(logFile);
@@ -134,37 +164,88 @@ public interface ClientOutput extends AutoCloseable, Consumer<String> {
final Thread w = new Thread(this::run);
w.start();
this.worker = w;
final Thread r = new Thread(this::read);
r.start();
this.reader = r;
}
void read() {
try {
while (!closing) {
int c = terminal.reader().read(10);
if (c == -1) {
break;
}
if (c == '+' || c == '-' || c == CTRL_L) {
queue.add(new Event(EventType.INPUT, null, Character.toString(c)));
}
}
} catch (InterruptedIOException e) {
Thread.currentThread().interrupt();
} catch (IOException e) {
this.exception = e;
}
}
void run() {
final List<Entry<String, String>> entries = new ArrayList<>();
final List<Event> entries = new ArrayList<>();
while (true) {
try {
entries.add(queue.take());
queue.drainTo(entries);
for (Entry<String, String> entry : entries) {
final String key = entry.getKey();
final String value = entry.getValue();
if (key == END_OF_STREAM) {
for (Event entry : entries) {
switch (entry.type) {
case END_OF_STREAM: {
projects.values().stream().flatMap(p -> p.log.stream()).forEach(log);
display.update(Collections.emptyList(), 0);
LOGGER.debug("Done receiving, printing log");
log.close();
LOGGER.debug("Done !");
terminal.flush();
return;
} else if (key == LOG) {
log.accept(value);
} else if (key == ERROR) {
}
case LOG: {
if (entry.projectId != null) {
Project prj = projects.computeIfAbsent(entry.projectId, p -> new Project());
prj.log.add(entry.message);
} else {
log.accept(entry.message);
}
break;
}
case ERROR: {
projects.values().stream().flatMap(p -> p.log.stream()).forEach(log);
display.update(Collections.emptyList(), 0);
final AttributedStyle s = new AttributedStyle().bold().foreground(AttributedStyle.RED);
terminal.writer().println(new AttributedString(value, s).toAnsi());
terminal.writer().println(new AttributedString(entry.message, s).toAnsi());
terminal.flush();
return;
} else if (value == null) {
projects.remove(key);
}
case PROJECT_STATUS:
if (entry.message != null) {
Project prj = projects.computeIfAbsent(entry.projectId, p -> new Project());
prj.status = entry.message;
} else {
projects.put(key, value);
Project prj = projects.remove(entry.projectId);
if (prj != null) {
prj.log.forEach(log);
}
}
break;
case INPUT:
switch (entry.message.charAt(0)) {
case '+':
linesPerProject = Math.min(10, linesPerProject + 1);
break;
case '-':
linesPerProject = Math.max(0, linesPerProject - 1);
break;
case CTRL_L:
display.reset();
break;
}
break;
}
}
entries.clear();
@@ -179,8 +260,12 @@ public interface ClientOutput extends AutoCloseable, Consumer<String> {
@Override
public void close() throws Exception {
queue.put(new AbstractMap.SimpleImmutableEntry<>(END_OF_STREAM, null));
closing = true;
reader.interrupt();
queue.put(new Event(EventType.END_OF_STREAM, null, null));
worker.join();
reader.join();
terminal.close();
if (exception != null) {
throw exception;
}
@@ -190,35 +275,61 @@ public interface ClientOutput extends AutoCloseable, Consumer<String> {
// no need to refresh the display at every single step
final Size size = terminal.getSize();
final int rows = size.getRows();
final int cols = size.getColumns();
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++;
final List<AttributedString> lines = new ArrayList<>(rows);
final int dispLines = rows - 1;
if (projects.size() <= dispLines) {
lines.add(new AttributedString("Building..."));
int remLogLines = dispLines - projects.size();
for (Project prj : projects.values()) {
lines.add(AttributedString.fromAnsi(prj.status));
// get the last lines of the project log, taking multi-line logs into account
List<AttributedString> logs = lastN(prj.log, linesPerProject).stream()
.flatMap(s -> AttributedString.fromAnsi(s).columnSplitLength(Integer.MAX_VALUE).stream())
.map(s -> concat(" ", s))
.collect(lastN(Math.min(remLogLines, linesPerProject)));
lines.addAll(logs);
remLogLines -= logs.size();
}
} else {
lines.add(shortenIfNeeded(AttributedString.fromAnsi(line), lineMaxLength));
lines.add(new AttributedString("Building... (" + (projects.size() - dispLines) + " more)"));
lines.addAll(projects.values().stream()
.map(prj -> AttributedString.fromAnsi(prj.status))
.collect(lastN(dispLines)));
}
}
display.update(lines, -1);
List<AttributedString> trimmed = lines.stream()
.map(s -> s.columnSubSequence(0, cols))
.collect(Collectors.toList());
display.update(trimmed, -1);
}
static AttributedString shortenIfNeeded(AttributedString s, int length) {
if (s == null) {
return null;
private static <T> List<T> lastN(List<T> list, int n) {
return list.subList(Math.max(0, list.size() - n), list.size());
}
if (s.length() > length) {
return s.columnSubSequence(0, length - 1);
private static <T> Collector<T, ?, List<T>> lastN(int n) {
return Collector.<T, Deque<T>, List<T>> of(ArrayDeque::new, (acc, t) -> {
if (acc.size() == n)
acc.pollFirst();
acc.add(t);
}, (acc1, acc2) -> {
while (acc2.size() < n && !acc1.isEmpty()) {
acc2.addFirst(acc1.pollLast());
}
return s;
return acc2;
}, ArrayList::new);
}
private static AttributedString concat(String s1, AttributedString s2) {
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.append(s1);
asb.append(s2);
return asb.toAttributedString();
}
}

View File

@@ -128,7 +128,7 @@ public class DefaultClient implements Client {
+ "-" + buildProperties.getOsArch()
+ nativeSuffix)
.reset().toString();
output.accept(v);
output.accept(null, v);
/*
* Do not return, rather pass -v to the server so that the client module does not need to depend on any
* Maven artifacts
@@ -140,9 +140,9 @@ public class DefaultClient implements Client {
try (DaemonRegistry registry = new DaemonRegistry(layout.registry())) {
boolean status = args.remove("--status");
if (status) {
output.accept(String.format(" %36s %7s %5s %7s %s",
output.accept(null, 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",
registry.getAll().forEach(d -> output.accept(null, 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())),
@@ -153,7 +153,7 @@ public class DefaultClient implements Client {
if (stop) {
DaemonInfo[] dis = registry.getAll().toArray(new DaemonInfo[0]);
if (dis.length > 0) {
output.accept("Stopping " + dis.length + " running daemons");
output.accept(null, "Stopping " + dis.length + " running daemons");
for (DaemonInfo di : dis) {
try {
ProcessHandle.of(di.getPid()).ifPresent(ProcessHandle::destroyForcibly);
@@ -204,15 +204,19 @@ public class DefaultClient implements Client {
return new DefaultResult(argv, null);
case ProjectStarted:
case MojoStarted:
output.projectStateChanged(be.getProjectId(), be.getDisplay());
break;
case MojoStopped:
output.projectStateChanged(be.getProjectId(), be.getDisplay());
output.projectStateChanged(be.getProjectId(), ":" + be.getProjectId());
break;
case ProjectStopped:
output.projectFinished(be.getProjectId());
break;
}
} else if (m instanceof BuildMessage) {
BuildMessage bm = (BuildMessage) m;
output.accept(bm.getMessage());
output.accept(bm.getProjectId(), bm.getMessage());
}
}
}

View File

@@ -51,6 +51,11 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -146,9 +146,11 @@ public abstract class Message {
}
public static class BuildMessage extends Message {
final String projectId;
final String message;
public BuildMessage(String message) {
public BuildMessage(String projectId, String message) {
this.projectId = projectId;
this.message = message;
}
@@ -156,6 +158,10 @@ public abstract class Message {
return message;
}
public String getProjectId() {
return projectId;
}
@Override
public String toString() {
return "BuildMessage{" +
@@ -236,11 +242,13 @@ public abstract class Message {
}
private BuildMessage readBuildMessage(DataInputStream input) throws IOException {
String projectId = readUTF(input);
String message = readUTF(input);
return new BuildMessage(message);
return new BuildMessage(projectId.isEmpty() ? null : projectId, message);
}
private void writeBuildMessage(DataOutputStream output, BuildMessage value) throws IOException {
writeUTF(output, value.projectId != null ? value.projectId : "");
writeUTF(output, value.message);
}

View File

@@ -514,7 +514,6 @@ public class Server implements AutoCloseable, Runnable {
@Override
public void close() throws Exception {
sendBuildMessages();
super.close();
}
@@ -529,43 +528,38 @@ public class Server implements AutoCloseable, Runnable {
}
@Override
protected void onStartProject(ProjectBuild project) {
super.onStartProject(project);
sendEvent(Type.ProjectStarted, project);
protected void onStartProject(String projectId, String display) {
super.onStartProject(projectId, display);
sendEvent(Type.ProjectStarted, projectId, display);
}
@Override
protected void onStopProject(ProjectBuild project) {
sendEvent(Type.ProjectStopped, project);
super.onStopProject(project);
protected void onStopProject(String projectId, String display) {
sendEvent(Type.ProjectStopped, projectId, display);
super.onStopProject(projectId, display);
}
@Override
protected void onStartMojo(ProjectBuild project) {
super.onStartMojo(project);
sendEvent(Type.MojoStarted, project);
protected void onStartMojo(String projectId, String display) {
super.onStartMojo(projectId, display);
sendEvent(Type.MojoStarted, projectId, display);
}
@Override
protected void onStopMojo(ProjectBuild project) {
sendEvent(Type.MojoStopped, project);
super.onStopMojo(project);
protected void onStopMojo(String projectId, String display) {
sendEvent(Type.MojoStopped, projectId, display);
super.onStopMojo(projectId, display);
}
private void sendEvent(Type type, ProjectBuild project) {
String projectId = project.projectId();
String disp = project.toDisplay().toAnsi(256, false);
queue.add(new BuildEvent(type, projectId, disp));
sendBuildMessages();
@Override
protected void onProjectLog(String projectId, String message) {
queue.add(new BuildMessage(projectId, message));
super.onProjectLog(projectId, message);
}
private void sendEvent(Type type, String projectId, String display) {
queue.add(new BuildEvent(type, projectId, display));
}
private synchronized void sendBuildMessages() {
if (events != null) {
events.stream()
.map(s -> s.endsWith("\n") ? s.substring(0, s.length() - 1) : s)
.map(BuildMessage::new).forEachOrdered(queue::add);
events.clear();
}
}
}
}

View File

@@ -15,14 +15,13 @@
*/
package org.jboss.fuse.mvnd.logging.smart;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.eventspy.AbstractEventSpy;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.jboss.fuse.mvnd.common.Message;
import org.jline.utils.AttributedString;
import org.slf4j.MDC;
@@ -44,17 +43,14 @@ public abstract class AbstractLoggingSpy extends AbstractEventSpy {
}
protected Map<String, ProjectBuild> projects;
protected List<String> events;
protected List<Message.BuildMessage> events;
@Override
public synchronized void init(Context context) throws Exception {
projects = new LinkedHashMap<>();
events = new ArrayList<>();
}
@Override
public synchronized void close() throws Exception {
events = null;
projects = null;
}
@@ -97,73 +93,67 @@ public abstract class AbstractLoggingSpy extends AbstractEventSpy {
protected void notifySessionFinish(ExecutionEvent event) {
}
protected synchronized void notifyProjectBuildStart(ExecutionEvent event) {
ProjectBuild pb = new ProjectBuild();
pb.project = event.getProject();
pb.execution = event.getMojoExecution();
pb.events = new ArrayList<>();
projects.putIfAbsent(event.getProject().getId(), pb);
onStartProject(pb);
protected void notifyProjectBuildStart(ExecutionEvent event) {
onStartProject(getProjectId(event), getProjectDisplay(event));
}
protected void onStartProject(ProjectBuild project) {
MDC.put(KEY_PROJECT_ID, project.project.getId());
protected void notifyProjectBuildFinish(ExecutionEvent event) throws Exception {
onStopProject(getProjectId(event), getProjectDisplay(event));
}
protected void notifyMojoExecutionStart(ExecutionEvent event) {
onStartMojo(getProjectId(event), getProjectDisplay(event));
}
protected void notifyMojoExecutionFinish(ExecutionEvent event) {
onStopMojo(getProjectId(event), getProjectDisplay(event));
}
protected void onStartProject(String projectId, String display) {
MDC.put(KEY_PROJECT_ID, projectId);
update();
}
protected synchronized void notifyProjectBuildFinish(ExecutionEvent event) throws Exception {
ProjectBuild pb = projects.remove(event.getProject().getId());
if (pb != null) {
events.addAll(pb.events);
onStopProject(pb);
}
}
protected void onStopProject(ProjectBuild project) {
protected void onStopProject(String projectId, String display) {
update();
MDC.remove(KEY_PROJECT_ID);
}
protected synchronized void notifyMojoExecutionStart(ExecutionEvent event) {
ProjectBuild pb = projects.get(event.getProject().getId());
if (pb != null) {
pb.execution = event.getMojoExecution();
onStartMojo(pb);
}
}
protected void onStartMojo(ProjectBuild project) {
protected void onStartMojo(String projectId, String display) {
update();
}
protected synchronized void notifyMojoExecutionFinish(ExecutionEvent event) {
ProjectBuild pb = projects.get(event.getProject().getId());
if (pb != null) {
pb.execution = null;
onStopMojo(pb);
}
protected void onStopMojo(String projectId, String display) {
update();
}
protected void onStopMojo(ProjectBuild project) {
protected void onProjectLog(String projectId, String message) {
update();
}
protected void update() {
}
public synchronized void append(String projectId, String event) {
ProjectBuild project = projectId != null ? projects.get(projectId) : null;
if (project != null) {
project.events.add(event);
} else {
events.add(event);
private String getProjectId(ExecutionEvent event) {
return event.getProject().getArtifactId();
}
private String getProjectDisplay(ExecutionEvent event) {
String projectId = getProjectId(event);
String disp = event.getMojoExecution() != null
? ":" + projectId + ":" + event.getMojoExecution().toString()
: ":" + projectId;
return disp;
}
public void append(String projectId, String event) {
String msg = event.endsWith("\n") ? event.substring(0, event.length() - 1) : event;
onProjectLog(projectId, msg);
}
protected static class ProjectBuild {
MavenProject project;
volatile MojoExecution execution;
List<String> events;
@Override
public String toString() {

View File

@@ -17,7 +17,9 @@ package org.jboss.fuse.mvnd.logging.smart;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
@@ -26,6 +28,7 @@ import org.jline.utils.Display;
public class MavenLoggingSpy extends AbstractLoggingSpy {
private Map<String, String> projects = new LinkedHashMap<>();
private Terminal terminal;
private Display display;
@@ -45,9 +48,6 @@ public class MavenLoggingSpy extends AbstractLoggingSpy {
@Override
public void close() throws Exception {
display.update(Collections.emptyList(), 0);
for (String event : events) {
terminal.writer().print(event);
}
terminal.flush();
terminal.close();
terminal = null;
@@ -55,13 +55,42 @@ public class MavenLoggingSpy extends AbstractLoggingSpy {
super.close();
}
@Override
protected void onStartProject(String projectId, String display) {
projects.put(projectId, display);
super.onStartProject(projectId, display);
}
@Override
protected void onStopProject(String projectId, String display) {
projects.remove(projectId);
super.onStopProject(projectId, display);
}
@Override
protected void onStartMojo(String projectId, String display) {
projects.put(projectId, display);
super.onStartMojo(projectId, display);
}
@Override
protected void onStopMojo(String projectId, String display) {
projects.put(projectId, display);
super.onStopMojo(projectId, display);
}
@Override
protected void onProjectLog(String projectId, String message) {
super.onProjectLog(projectId, message);
}
protected void update() {
Size size = terminal.getSize();
display.resize(size.getRows(), size.getColumns());
List<AttributedString> lines = new ArrayList<>();
lines.add(new AttributedString("Building..."));
for (ProjectBuild build : projects.values()) {
lines.add(build.toDisplay());
for (String build : projects.values()) {
lines.add(new AttributedString(build));
}
display.update(lines, -1);
}

View File

@@ -17,6 +17,7 @@ package org.jboss.fuse.mvnd.assertj;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -43,7 +44,7 @@ public class MatchInOrderAmongOthers<T extends List<? extends String>> extends C
.filter(pat -> pat.matcher(m).find())
.findFirst()
.orElse(null))
.filter(pat -> pat != null) /* remove null patterns */
.filter(Objects::nonNull) /* remove null patterns */
.collect(Collectors.toList())
/* if the mapped patterns equal the input patterns then each pattern matched exactly once */
.equals(patterns),

View File

@@ -32,6 +32,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import static org.mockito.ArgumentMatchers.any;
@MvndTest(projectDir = "src/test/projects/multi-module")
public class MultiModuleTest {
@@ -71,7 +73,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)).accept(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(any(), logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.satisfiesAnyOf( /* Two orderings are possible */
messages -> Assertions.assertThat(messages)

View File

@@ -30,6 +30,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.mockito.Mockito;
import static org.mockito.ArgumentMatchers.any;
@MvndNativeTest(projectDir = "src/test/projects/single-module")
public class SingleModuleNativeIT {
@@ -57,14 +59,14 @@ public class SingleModuleNativeIT {
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"));
inOrder.verify(o).accept(any(), Mockito.contains("Building single-module"));
inOrder.verify(o).accept(any(), Mockito.contains(MvndTestUtil.plugin(props, "maven-clean-plugin") + ":clean"));
inOrder.verify(o).accept(any(), Mockito.contains(MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":compile"));
inOrder.verify(o).accept(any(), Mockito.contains(MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":testCompile"));
inOrder.verify(o).accept(any(), Mockito.contains(MvndTestUtil.plugin(props, "maven-surefire-plugin") + ":test"));
inOrder.verify(o).accept(any(), Mockito.contains(MvndTestUtil.plugin(props, "maven-install-plugin") + ":install"));
inOrder.verify(o).accept(any(),
Mockito.contains("SUCCESS build of project org.jboss.fuse.mvnd.test.single-module:single-module"));
assertJVM(o, props);

View File

@@ -29,6 +29,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import static org.mockito.ArgumentMatchers.any;
@MvndTest(projectDir = "src/test/projects/single-module")
public class StopStatusTest {
@@ -54,7 +56,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)).accept(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(any(), logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.is(new MatchInOrderAmongOthers<>(
d.getUid() + " +" + d.getPid() + " +" + d.getAddress()));
@@ -76,7 +78,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)).accept(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(any(), logMessage.capture());
Assertions.assertThat(
logMessage.getAllValues().stream()
.filter(m -> m.contains(d.getUid()))

View File

@@ -28,6 +28,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import static org.mockito.ArgumentMatchers.any;
@MvndNativeTest(projectDir = MvndTestExtension.TEMP_EXTERNAL)
public class VersionNativeIT {
@@ -44,7 +46,7 @@ public class VersionNativeIT {
client.execute(output, "-v").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).accept(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(any(), logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.is(new MatchInOrderAmongOthers<>(

View File

@@ -26,7 +26,6 @@ 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;
@@ -57,7 +56,7 @@ public class NativeTestClient implements Client {
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.addAll(args);
if (!Environment.MVND_PROPERTIES_PATH.hasCommandLineProperty(args)) {
cmd.add(Environment.MVND_PROPERTIES_PATH.asCommandLineProperty(layout.getMvndPropertiesPath().toString()));
}
@@ -81,9 +80,9 @@ public class NativeTestClient implements Client {
if (!Environment.JAVA_HOME.hasCommandLineProperty(args)) {
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)) {
final String cmdString = String.join(" ", cmd);
output.accept(null, "Executing " + cmdString);
try (CommandProcess process = new CommandProcess(builder.start(), cmd, s -> output.accept(null, s))) {
return process.waitFor(timeoutMs);
} catch (IOException e) {
throw new RuntimeException("Could not execute: " + cmdString, e);
@@ -127,7 +126,7 @@ public class NativeTestClient implements Client {
}
sb.append("\n--- stderr+stdout start ---");
synchronized (log) {
log.stream().forEach(s -> sb.append('\n').append(s));
log.forEach(s -> sb.append('\n').append(s));
}
sb.append("\n--- stderr+stdout end ---");
throw new AssertionError(sb);