diff --git a/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonClientConnection.java b/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonClientConnection.java index 50e3b273..fea05bc8 100644 --- a/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonClientConnection.java +++ b/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonClientConnection.java @@ -17,6 +17,8 @@ package org.jboss.fuse.mvnd.client; import java.io.Closeable; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; @@ -30,6 +32,7 @@ import org.jboss.fuse.mvnd.common.DaemonException.ConnectException; import org.jboss.fuse.mvnd.common.DaemonException.StaleAddressException; import org.jboss.fuse.mvnd.common.DaemonInfo; import org.jboss.fuse.mvnd.common.Message; +import org.jboss.fuse.mvnd.common.Message.Prompt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,19 +88,27 @@ public class DaemonClientConnection implements Closeable { } } - public Message receive() throws ConnectException, StaleAddressException { + public List receive() throws ConnectException, StaleAddressException { while (true) { try { - Message m = queue.poll(maxKeepAliveMs, TimeUnit.MILLISECONDS); + final Message m = queue.poll(maxKeepAliveMs, TimeUnit.MILLISECONDS); + { + Exception e = exception.get(); + if (e != null) { + throw e; + } else if (m == null) { + throw new IOException("No message received within " + maxKeepAliveMs + + "ms, daemon may have crashed. You may want to check its status using mvnd --status"); + } + } + final List result = new ArrayList<>(4); + result.add(m); + queue.drainTo(result); Exception e = exception.get(); if (e != null) { throw e; - } else if (m != null) { - return m; - } else { - throw new IOException("No message received within " + maxKeepAliveMs - + "ms, daemon may have crashed. You may want to check its status using mvnd --status"); } + return result; } catch (Exception e) { DaemonDiagnostics diag = new DaemonDiagnostics(daemon.getUid(), parameters); LOG.debug("Problem receiving message to the daemon. Performing 'on failure' operation..."); @@ -119,6 +130,10 @@ public class DaemonClientConnection implements Closeable { if (m == null) { break; } + if (m.getType() == Message.PROMPT) { + final Prompt prompt = (Prompt) m; + m = prompt.withCallback(response -> dispatch(prompt.response(response))); + } queue.put(m); } } catch (Exception e) { diff --git a/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonConnector.java b/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonConnector.java index 2a8d6e09..16f6a9df 100644 --- a/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonConnector.java +++ b/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonConnector.java @@ -41,6 +41,7 @@ import org.jboss.fuse.mvnd.common.DaemonState; import org.jboss.fuse.mvnd.common.DaemonStopEvent; import org.jboss.fuse.mvnd.common.Environment; import org.jboss.fuse.mvnd.common.MavenDaemon; +import org.jboss.fuse.mvnd.common.Message; import org.jboss.fuse.mvnd.common.Os; import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.slf4j.Logger; @@ -85,7 +86,7 @@ public class DaemonConnector { public DaemonClientConnection connect(ClientOutput output) { final DaemonCompatibilitySpec constraint = new DaemonCompatibilitySpec( parameters.javaHome(), parameters.getDaemonOpts()); - output.buildStatus("Looking up daemon..."); + output.accept(Message.buildStatus("Looking up daemon...")); Map> idleBusy = registry.getAll().stream() .collect(Collectors.groupingBy(di -> di.getState() == DaemonState.Idle)); final Collection idleDaemons = idleBusy.getOrDefault(true, Collections.emptyList()); @@ -105,7 +106,7 @@ public class DaemonConnector { // No compatible daemons available - start a new daemon String message = handleStopEvents(idleDaemons, busyDaemons); - output.buildStatus(message); + output.accept(Message.buildStatus(message)); return startDaemon(); } diff --git a/client/src/main/java/org/jboss/fuse/mvnd/client/DefaultClient.java b/client/src/main/java/org/jboss/fuse/mvnd/client/DefaultClient.java index b4f82e9b..3ab8cd17 100644 --- a/client/src/main/java/org/jboss/fuse/mvnd/client/DefaultClient.java +++ b/client/src/main/java/org/jboss/fuse/mvnd/client/DefaultClient.java @@ -28,10 +28,7 @@ import org.jboss.fuse.mvnd.common.DaemonInfo; import org.jboss.fuse.mvnd.common.DaemonRegistry; import org.jboss.fuse.mvnd.common.Environment; import org.jboss.fuse.mvnd.common.Message; -import org.jboss.fuse.mvnd.common.Message.BuildEvent; import org.jboss.fuse.mvnd.common.Message.BuildException; -import org.jboss.fuse.mvnd.common.Message.BuildMessage; -import org.jboss.fuse.mvnd.common.Message.BuildStarted; import org.jboss.fuse.mvnd.common.OsUtils; import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.jboss.fuse.mvnd.common.logging.TerminalOutput; @@ -125,7 +122,7 @@ public class DefaultClient implements Client { + "-" + buildProperties.getOsArch() + nativeSuffix) .reset().toString(); - output.accept(null, v); + output.accept(Message.log(v)); // Print terminal information output.describeTerminal(); /* @@ -138,20 +135,20 @@ public class DefaultClient implements Client { boolean status = args.remove("--status"); if (status) { final String template = " %36s %7s %5s %7s %5s %23s %s"; - output.accept(null, String.format(template, - "UUID", "PID", "Port", "Status", "RSS", "Last activity", "Java home")); + output.accept(Message.log(String.format(template, + "UUID", "PID", "Port", "Status", "RSS", "Last activity", "Java home"))); for (DaemonInfo d : registry.getAll()) { if (ProcessHandle.of(d.getPid()).isEmpty()) { /* The process does not exist anymore - remove it from the registry */ registry.remove(d.getUid()); } else { - output.accept(null, String.format(template, + output.accept(Message.log(String.format(template, d.getUid(), d.getPid(), d.getAddress(), d.getState(), OsUtils.kbTohumanReadable(OsUtils.findProcessRssInKb(d.getPid())), LocalDateTime.ofInstant( Instant.ofEpochMilli(Math.max(d.getLastIdle(), d.getLastBusy())), ZoneId.systemDefault()), - d.getJavaHome())); + d.getJavaHome()))); } } return new DefaultResult(argv, null); @@ -160,7 +157,7 @@ public class DefaultClient implements Client { if (stop) { DaemonInfo[] dis = registry.getAll().toArray(new DaemonInfo[0]); if (dis.length > 0) { - output.accept(null, "Stopping " + dis.length + " running daemons"); + output.accept(Message.log("Stopping " + dis.length + " running daemons")); for (DaemonInfo di : dis) { try { ProcessHandle.of(di.getPid()).ifPresent(ProcessHandle::destroyForcibly); @@ -194,7 +191,7 @@ public class DefaultClient implements Client { final DaemonConnector connector = new DaemonConnector(parameters, registry); try (DaemonClientConnection daemon = connector.connect(output)) { - output.buildStatus("Connected to daemon"); + output.accept(Message.buildStatus("Connected to daemon")); daemon.dispatch(new Message.BuildRequest( args, @@ -202,44 +199,28 @@ public class DefaultClient implements Client { parameters.multiModuleProjectDirectory().toString(), System.getenv())); - output.buildStatus("Build request sent"); + output.accept(Message.buildStatus("Build request sent")); while (true) { - Message m = daemon.receive(); - if (m instanceof BuildException) { - final BuildException e = (BuildException) m; - output.error(e.getMessage(), e.getClassName(), e.getStackTrace()); - return new DefaultResult(argv, - new Exception(e.getClassName() + ": " + e.getMessage() + "\n" + e.getStackTrace())); - } else if (m instanceof BuildStarted) { - final BuildStarted bs = (BuildStarted) m; - output.startBuild(bs.getProjectId(), bs.getProjectCount(), bs.getMaxThreads()); - } else if (m instanceof BuildEvent) { - BuildEvent be = (BuildEvent) m; - switch (be.getType()) { - case BuildStopped: + final List messages = daemon.receive(); + for (int i = 0; i < messages.size(); i++) { + Message m = messages.get(i); + switch (m.getType()) { + case Message.BUILD_EXCEPTION: { + output.accept(messages.subList(0, i + 1)); + final BuildException e = (BuildException) m; + return new DefaultResult(argv, + new Exception(e.getClassName() + ": " + e.getMessage() + "\n" + e.getStackTrace())); + } + case Message.BUILD_STOPPED: { + output.accept(messages.subList(0, i)); return new DefaultResult(argv, null); - case ProjectStarted: - case MojoStarted: - output.projectStateChanged(be.getProjectId(), be.getDisplay()); - break; - case ProjectStopped: - output.projectFinished(be.getProjectId()); + } + default: break; } - } else if (m instanceof BuildMessage) { - BuildMessage bm = (BuildMessage) m; - output.accept(bm.getProjectId(), bm.getMessage()); - } else if (m == Message.KEEP_ALIVE_SINGLETON) { - output.keepAlive(); - } else if (m instanceof Message.Display) { - Message.Display d = (Message.Display) m; - output.display(d.getProjectId(), d.getMessage()); - } else if (m instanceof Message.Prompt) { - Message.Prompt p = (Message.Prompt) m; - String response = output.prompt(p.getProjectId(), p.getMessage(), p.isPassword()); - daemon.dispatch(new Message.PromptResponse(p.getProjectId(), p.getUid(), response)); } + output.accept(messages); } } } diff --git a/common/src/main/java/org/jboss/fuse/mvnd/common/Message.java b/common/src/main/java/org/jboss/fuse/mvnd/common/Message.java index a231c0fd..8a89feec 100644 --- a/common/src/main/java/org/jboss/fuse/mvnd/common/Message.java +++ b/common/src/main/java/org/jboss/fuse/mvnd/common/Message.java @@ -26,21 +26,34 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; public abstract class Message { - static final int BUILD_REQUEST = 0; - static final int BUILD_EVENT = 1; - static final int BUILD_MESSAGE = 2; - static final int BUILD_EXCEPTION = 3; - static final int KEEP_ALIVE = 4; - static final int STOP = 5; - static final int BUILD_STARTED = 6; - static final int DISPLAY = 7; - static final int PROMPT = 8; - static final int PROMPT_RESPONSE = 9; + public static final int BUILD_REQUEST = 0; + public static final int BUILD_STARTED = 1; + public static final int BUILD_STOPPED = 2; + public static final int PROJECT_STARTED = 3; + public static final int PROJECT_STOPPED = 4; + public static final int MOJO_STARTED = 5; + public static final int BUILD_MESSAGE = 6; + public static final int BUILD_EXCEPTION = 7; + public static final int KEEP_ALIVE = 8; + public static final int STOP = 9; + public static final int DISPLAY = 10; + public static final int PROMPT = 11; + public static final int PROMPT_RESPONSE = 12; + public static final int BUILD_STATUS = 13; + public static final int KEYBOARD_INPUT = 14; - public static final SimpleMessage KEEP_ALIVE_SINGLETON = new SimpleMessage(Message.KEEP_ALIVE, "KEEP_ALIVE"); - public static final SimpleMessage STOP_SINGLETON = new SimpleMessage(Message.STOP, "STOP"); + public static final SimpleMessage KEEP_ALIVE_SINGLETON = new SimpleMessage(KEEP_ALIVE); + public static final SimpleMessage STOP_SINGLETON = new SimpleMessage(STOP); + public static final SimpleMessage BUILD_STOPPED_SINGLETON = new SimpleMessage(BUILD_STOPPED); + + final int type; + + Message(int type) { + this.type = type; + } public static Message read(DataInputStream input) throws IOException { int type = input.read(); @@ -52,8 +65,12 @@ public abstract class Message { return BuildRequest.read(input); case BUILD_STARTED: return BuildStarted.read(input); - case BUILD_EVENT: - return BuildEvent.read(input); + case BUILD_STOPPED: + return SimpleMessage.BUILD_STOPPED_SINGLETON; + case PROJECT_STARTED: + case PROJECT_STOPPED: + case MOJO_STARTED: + return BuildEvent.read(type, input); case BUILD_MESSAGE: return BuildMessage.read(input); case BUILD_EXCEPTION: @@ -68,6 +85,8 @@ public abstract class Message { return Prompt.read(input); case PROMPT_RESPONSE: return PromptResponse.read(input); + case BUILD_STATUS: + return StringMessage.read(BUILD_STATUS, input); } throw new IllegalStateException("Unexpected message type: " + type); } @@ -78,7 +97,9 @@ public abstract class Message { return timestamp; } - public abstract void write(DataOutputStream output) throws IOException; + public void write(DataOutputStream output) throws IOException { + output.write(type); + } static void writeStringList(DataOutputStream output, List value) throws IOException { output.writeInt(value.size()); @@ -233,6 +254,7 @@ public abstract class Message { } public BuildRequest(List args, String workingDir, String projectDir, Map env) { + super(BUILD_REQUEST); this.args = args; this.workingDir = workingDir; this.projectDir = projectDir; @@ -266,7 +288,7 @@ public abstract class Message { @Override public void write(DataOutputStream output) throws IOException { - output.write(BUILD_REQUEST); + super.write(output); writeStringList(output, args); writeUTF(output, workingDir); writeUTF(output, projectDir); @@ -297,6 +319,7 @@ public abstract class Message { } public BuildException(String message, String className, String stackTrace) { + super(BUILD_EXCEPTION); this.message = message; this.className = className; this.stackTrace = stackTrace; @@ -325,7 +348,7 @@ public abstract class Message { @Override public void write(DataOutputStream output) throws IOException { - output.write(BUILD_EXCEPTION); + super.write(output); writeUTF(output, message); writeUTF(output, className); writeUTF(output, stackTrace); @@ -333,31 +356,21 @@ public abstract class Message { } public static class BuildEvent extends Message { - public enum Type { - BuildStopped, ProjectStarted, ProjectStopped, MojoStarted - } - - final Type type; final String projectId; final String display; - public static Message read(DataInputStream input) throws IOException { - BuildEvent.Type type = BuildEvent.Type.values()[input.read()]; + public static Message read(int type, DataInputStream input) throws IOException { String projectId = readUTF(input); String display = readUTF(input); return new BuildEvent(type, projectId, display); } - public BuildEvent(Type type, String projectId, String display) { - this.type = type; + public BuildEvent(int type, String projectId, String display) { + super(type); this.projectId = projectId; this.display = display; } - public Type getType() { - return type; - } - public String getProjectId() { return projectId; } @@ -368,17 +381,28 @@ public abstract class Message { @Override public String toString() { - return "BuildEvent{" + + return mnemonic() + "{" + "projectId='" + projectId + '\'' + - ", type=" + type + ", display='" + display + '\'' + '}'; } + private String mnemonic() { + switch (type) { + case PROJECT_STARTED: + return "ProjectStarted"; + case PROJECT_STOPPED: + return "ProjectStopped"; + case MOJO_STARTED: + return "MojoStarted"; + default: + throw new IllegalStateException("Unexpected type " + type); + } + } + @Override public void write(DataOutputStream output) throws IOException { - output.write(BUILD_EVENT); - output.write(type.ordinal()); + super.write(output); writeUTF(output, projectId); writeUTF(output, display); } @@ -398,6 +422,7 @@ public abstract class Message { } public BuildStarted(String projectId, int projectCount, int maxThreads) { + super(BUILD_STARTED); this.projectId = projectId; this.projectCount = projectCount; this.maxThreads = maxThreads; @@ -417,16 +442,14 @@ public abstract class Message { @Override public String toString() { - return "BuildEvent{" + - "projectId='" + projectId + '\'' + - ", projectCount=" + projectCount + - ", maxThreads='" + maxThreads + '\'' + - '}'; + return "BuildStarted{" + + "projectId='" + projectId + "', projectCount=" + projectCount + + ", maxThreads='" + maxThreads + "'}"; } @Override public void write(DataOutputStream output) throws IOException { - output.write(BUILD_STARTED); + super.write(output); writeUTF(output, projectId); output.writeInt(projectCount); output.writeInt(maxThreads); @@ -444,6 +467,7 @@ public abstract class Message { } public BuildMessage(String projectId, String message) { + super(BUILD_MESSAGE); this.projectId = projectId; this.message = message; } @@ -466,7 +490,7 @@ public abstract class Message { @Override public void write(DataOutputStream output) throws IOException { - output.write(BUILD_MESSAGE); + super.write(output); writeUTF(output, projectId != null ? projectId : ""); writeUTF(output, message); } @@ -474,28 +498,66 @@ public abstract class Message { public static class SimpleMessage extends Message { - final int type; - final String mnemonic; - - /** - * Use {@link #KEEP_ALIVE_SINGLETON} - * - * @param type - */ - private SimpleMessage(int type, String mnemonic) { - this.type = type; - this.mnemonic = mnemonic; + private SimpleMessage(int type) { + super(type); } @Override public String toString() { - return mnemonic; + switch (type) { + case KEEP_ALIVE: + return "KeepAlive"; + case BUILD_STOPPED: + return "BuildStopped"; + case STOP: + return "Stop"; + default: + throw new IllegalStateException("Unexpected type " + type); + } + } + + } + + public static class StringMessage extends Message { + + final String payload; + + public static StringMessage read(int type, DataInputStream input) throws IOException { + String payload = readUTF(input); + return new StringMessage(type, payload); + } + + private StringMessage(int type, String payload) { + super(type); + this.payload = payload; + } + + public String getPayload() { + return payload; } @Override public void write(DataOutputStream output) throws IOException { - output.write(type); + super.write(output); + writeUTF(output, payload); } + + @Override + public String toString() { + return mnemonic() + "{payload='" + payload + "'}"; + } + + private String mnemonic() { + switch (type) { + case BUILD_STATUS: + return "BuildStatus"; + case Message.KEYBOARD_INPUT: + return "KeyboardInput"; + default: + throw new IllegalStateException("Unexpected type " + type); + } + } + } public static class Display extends Message { @@ -510,6 +572,7 @@ public abstract class Message { } public Display(String projectId, String message) { + super(DISPLAY); this.projectId = projectId; this.message = message; } @@ -532,7 +595,7 @@ public abstract class Message { @Override public void write(DataOutputStream output) throws IOException { - output.write(DISPLAY); + super.write(output); writeUTF(output, projectId); writeUTF(output, message); } @@ -544,8 +607,9 @@ public abstract class Message { final String uid; final String message; final boolean password; + final Consumer callback; - public static Message read(DataInputStream input) throws IOException { + public static Prompt read(DataInputStream input) throws IOException { String projectId = Message.readUTF(input); String uid = Message.readUTF(input); String message = Message.readUTF(input); @@ -554,10 +618,16 @@ public abstract class Message { } public Prompt(String projectId, String uid, String message, boolean password) { + this(projectId, uid, message, password, null); + } + + public Prompt(String projectId, String uid, String message, boolean password, Consumer callback) { + super(PROMPT); this.projectId = projectId; this.uid = uid; this.message = message; this.password = password; + this.callback = callback; } public String getProjectId() { @@ -588,12 +658,25 @@ public abstract class Message { @Override public void write(DataOutputStream output) throws IOException { - output.write(PROMPT); + super.write(output); writeUTF(output, projectId); writeUTF(output, uid); writeUTF(output, message); output.writeBoolean(password); } + + public Prompt withCallback(Consumer callback) { + return new Prompt(projectId, uid, message, password, callback); + } + + public PromptResponse response(String message) { + return new PromptResponse(projectId, uid, message); + } + + public Consumer getCallback() { + return callback; + } + } public static class PromptResponse extends Message { @@ -610,6 +693,7 @@ public abstract class Message { } public PromptResponse(String projectId, String uid, String message) { + super(PROMPT_RESPONSE); this.projectId = projectId; this.uid = uid; this.message = message; @@ -638,11 +722,31 @@ public abstract class Message { @Override public void write(DataOutputStream output) throws IOException { - output.write(PROMPT_RESPONSE); + super.write(output); writeUTF(output, projectId); writeUTF(output, uid); writeUTF(output, message); } } + public int getType() { + return type; + } + + public static StringMessage buildStatus(String payload) { + return new StringMessage(BUILD_STATUS, payload); + } + + public static BuildMessage log(String message) { + return new BuildMessage(null, message); + } + + public static BuildMessage log(String projectId, String message) { + return new BuildMessage(projectId, message); + } + + public static StringMessage keyboardInput(char keyStroke) { + return new StringMessage(KEYBOARD_INPUT, String.valueOf(keyStroke)); + } + } diff --git a/common/src/main/java/org/jboss/fuse/mvnd/common/logging/ClientOutput.java b/common/src/main/java/org/jboss/fuse/mvnd/common/logging/ClientOutput.java index daba4c7d..ef6aa13d 100644 --- a/common/src/main/java/org/jboss/fuse/mvnd/common/logging/ClientOutput.java +++ b/common/src/main/java/org/jboss/fuse/mvnd/common/logging/ClientOutput.java @@ -15,28 +15,16 @@ */ package org.jboss.fuse.mvnd.common.logging; +import java.util.List; +import org.jboss.fuse.mvnd.common.Message; + /** * A sink for various kinds of events sent by the daemon. */ public interface ClientOutput extends AutoCloseable { + void accept(Message message); - void startBuild(String name, int projects, int cores); - - void projectStateChanged(String projectId, String display); - - void projectFinished(String projectId); - - void accept(String projectId, String message); - - void error(String message, String className, String stackTrace); - - void keepAlive(); - - void buildStatus(String status); - - void display(String projectId, String message); - - String prompt(String projectId, String message, boolean password); + void accept(List messages); void describeTerminal(); } diff --git a/common/src/main/java/org/jboss/fuse/mvnd/common/logging/TerminalOutput.java b/common/src/main/java/org/jboss/fuse/mvnd/common/logging/TerminalOutput.java index 6a6dccc4..add1707d 100644 --- a/common/src/main/java/org/jboss/fuse/mvnd/common/logging/TerminalOutput.java +++ b/common/src/main/java/org/jboss/fuse/mvnd/common/logging/TerminalOutput.java @@ -27,16 +27,20 @@ import java.util.Collections; import java.util.Deque; import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; import java.util.stream.Collector; import java.util.stream.Collectors; +import org.jboss.fuse.mvnd.common.Message; +import org.jboss.fuse.mvnd.common.Message.BuildEvent; +import org.jboss.fuse.mvnd.common.Message.BuildException; +import org.jboss.fuse.mvnd.common.Message.BuildMessage; +import org.jboss.fuse.mvnd.common.Message.BuildStarted; +import org.jboss.fuse.mvnd.common.Message.SimpleMessage; +import org.jboss.fuse.mvnd.common.Message.StringMessage; import org.jline.terminal.Size; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; @@ -55,12 +59,10 @@ public class TerminalOutput implements ClientOutput { public static final int CTRL_M = 'M' & 0x1f; - private final BlockingQueue queue; private final Terminal terminal; private final Display display; private final LinkedHashMap projects = new LinkedHashMap<>(); private final ClientLog log; - private final Thread worker; private final Thread reader; private volatile Exception exception; private volatile boolean closing; @@ -77,39 +79,6 @@ public class TerminalOutput implements ClientOutput { private String buildStatus; // read/written only by the displayLoop private boolean displayDone = false; // read/written only by the displayLoop - enum EventType { - BUILD_STATUS, - PROJECT_STATE, - PROJECT_FINISHED, - LOG, - ERROR, - END_OF_STREAM, - INPUT, - KEEP_ALIVE, - DISPLAY, - PROMPT, - PROMPT_PASSWORD - } - - static class Event { - public static final Event KEEP_ALIVE = new Event(EventType.KEEP_ALIVE, null, null); - public final EventType type; - public final String projectId; - public final String message; - public final SynchronousQueue response; - - public Event(EventType type, String projectId, String message) { - this(type, projectId, message, null); - } - - public Event(EventType type, String projectId, String message, SynchronousQueue response) { - this.type = type; - this.projectId = projectId; - this.message = message; - this.response = response; - } - } - /** * {@link Project} is owned by the display loop thread and is accessed only from there. Therefore it does not need * to be immutable. @@ -126,113 +95,177 @@ public class TerminalOutput implements ClientOutput { public TerminalOutput(Path logFile) throws IOException { this.start = System.currentTimeMillis(); - this.queue = new LinkedBlockingDeque<>(); this.terminal = TerminalBuilder.terminal(); terminal.enterRawMode(); this.display = new Display(terminal, false); this.log = logFile == null ? new MessageCollector() : new FileLog(logFile); - final Thread w = new Thread(this::displayLoop); - w.start(); - this.worker = w; final Thread r = new Thread(this::readInputLoop); r.start(); this.reader = r; } - public void startBuild(String name, int projects, int cores) { - this.name = name; - this.totalProjects = projects; - this.maxThreads = cores; - try { - queue.put(Event.KEEP_ALIVE); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - public void projectStateChanged(String projectId, String task) { - try { - queue.put(new Event(EventType.PROJECT_STATE, projectId, task)); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - public void projectFinished(String projectId) { - try { - queue.put(new Event(EventType.PROJECT_FINISHED, projectId, null)); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + @Override + public void accept(Message entry) { + assert "main".equals(Thread.currentThread().getName()); + doAccept(entry); + update(); } @Override - public void accept(String projectId, String message) { - try { - if (closing) { - closed.await(); - System.err.println(message); + public void accept(List entries) { + assert "main".equals(Thread.currentThread().getName()); + for (Message entry : entries) { + doAccept(entry); + } + update(); + } + + private void doAccept(Message entry) { + switch (entry.getType()) { + case Message.BUILD_STARTED: { + BuildStarted bs = (BuildStarted) entry; + this.name = bs.getProjectId(); + this.totalProjects = bs.getProjectCount(); + this.maxThreads = bs.getMaxThreads(); + break; + } + case Message.BUILD_EXCEPTION: { + final BuildException e = (BuildException) entry; + final String msg; + if ("org.apache.commons.cli.UnrecognizedOptionException".equals(e.getClassName())) { + msg = "Unable to parse command line options: " + e.getMessage(); } else { - queue.put(new Event(EventType.LOG, projectId, message)); + msg = e.getClassName() + ": " + e.getMessage(); } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + projects.values().stream().flatMap(p -> p.log.stream()).forEach(log); + clearDisplay(); + try { + log.close(); + } catch (IOException e1) { + throw new RuntimeException(e1); + } + final AttributedStyle s = new AttributedStyle().bold().foreground(AttributedStyle.RED); + new AttributedString(msg, s).println(terminal); + terminal.flush(); + return; } - } + case Message.PROJECT_STARTED: + case Message.MOJO_STARTED: { + BuildEvent be = (BuildEvent) entry; + Project prj = projects.computeIfAbsent(be.getProjectId(), Project::new); + prj.status = be.getDisplay(); + break; + } + case Message.PROJECT_STOPPED: { + BuildEvent be = (BuildEvent) entry; + Project prj = projects.remove(be.getProjectId()); + if (prj != null) { + prj.log.forEach(log); + } + doneProjects++; + displayDone(); + break; + } + case Message.BUILD_STATUS: { + this.buildStatus = ((StringMessage) entry).getPayload(); + break; + } + case Message.BUILD_STOPPED: { + projects.values().stream().flatMap(p -> p.log.stream()).forEach(log); + clearDisplay(); + try { + log.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + terminal.flush(); + } + return; + } + case Message.KEEP_ALIVE: { + break; + } + case Message.DISPLAY: { + Message.Display d = (Message.Display) entry; + display.update(Collections.emptyList(), 0); + terminal.writer().printf("[%s] %s%n", d.getProjectId(), d.getMessage()); + break; + } + case Message.PROMPT: { + Message.Prompt prompt = (Message.Prompt) entry; - @Override - public void error(String message, String className, String stackTrace) { - final String msg; - if ("org.apache.commons.cli.UnrecognizedOptionException".equals(className)) { - msg = "Unable to parse command line options: " + message; - } else { - msg = className + ": " + message; + readInput.writeLock().lock(); + try { + display.update(Collections.emptyList(), 0); + terminal.writer().printf("[%s] %s", prompt.getProjectId(), prompt.getMessage()); + terminal.flush(); + StringBuilder sb = new StringBuilder(); + while (true) { + int c = terminal.reader().read(); + if (c < 0) { + break; + } else if (c == '\n' || c == '\r') { + prompt.getCallback().accept(sb.toString()); + terminal.writer().println(); + break; + } else if (c == 127) { + if (sb.length() > 0) { + sb.setLength(sb.length() - 1); + terminal.writer().write("\b \b"); + terminal.writer().flush(); + } + } else { + terminal.writer().print((char) c); + terminal.writer().flush(); + sb.append((char) c); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + readInput.writeLock().unlock(); + } + break; } - try { - queue.put(new Event(EventType.ERROR, null, msg)); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + case Message.BUILD_MESSAGE: { + BuildMessage bm = (BuildMessage) entry; + if (closing) { + try { + closed.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + System.err.println(bm.getMessage()); + } else { + if (bm.getProjectId() != null) { + Project prj = projects.computeIfAbsent(bm.getProjectId(), Project::new); + prj.log.add(bm.getMessage()); + } else { + log.accept(bm.getMessage()); + } + } + break; + } + case Message.KEYBOARD_INPUT: + char keyStroke = ((StringMessage) entry).getPayload().charAt(0); + switch (keyStroke) { + case '+': + linesPerProject = Math.min(10, linesPerProject + 1); + break; + case '-': + linesPerProject = Math.max(0, linesPerProject - 1); + break; + case CTRL_L: + display.update(Collections.emptyList(), 0); + break; + case CTRL_M: + displayDone = !displayDone; + displayDone(); + break; + } + break; } - } - @Override - public void keepAlive() { - try { - queue.put(Event.KEEP_ALIVE); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - @Override - public void buildStatus(String status) { - try { - queue.put(new Event(EventType.BUILD_STATUS, null, status)); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - @Override - public void display(String projectId, String message) { - try { - queue.put(new Event(EventType.DISPLAY, projectId, message)); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - @Override - public String prompt(String projectId, String message, boolean password) { - String response = null; - try { - SynchronousQueue sq = new SynchronousQueue<>(); - queue.put(new Event(password ? EventType.PROMPT_PASSWORD : EventType.PROMPT, projectId, message, sq)); - response = sq.take(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - return response; } @Override @@ -242,7 +275,7 @@ public class TerminalOutput implements ClientOutput { if (terminal instanceof AbstractPosixTerminal) { sb.append(" with pty ").append(((AbstractPosixTerminal) terminal).getPty().getClass().getName()); } - this.accept(null, sb.toString()); + this.accept(Message.log(sb.toString())); } void readInputLoop() { @@ -254,7 +287,7 @@ public class TerminalOutput implements ClientOutput { break; } if (c == '+' || c == '-' || c == CTRL_L || c == CTRL_M) { - queue.add(new Event(EventType.INPUT, null, Character.toString((char) c))); + accept(Message.keyboardInput((char) c)); } readInput.readLock().unlock(); } @@ -268,130 +301,17 @@ public class TerminalOutput implements ClientOutput { } } - void displayLoop() { - final List entries = new ArrayList<>(); - - while (true) { - try { - entries.add(queue.take()); - queue.drainTo(entries); - for (Event entry : entries) { - switch (entry.type) { - case BUILD_STATUS: { - this.buildStatus = entry.message; - break; - } - case END_OF_STREAM: { - projects.values().stream().flatMap(p -> p.log.stream()).forEach(log); - clearDisplay(); - log.close(); - terminal.flush(); - return; - } - case LOG: { - if (entry.projectId != null) { - Project prj = projects.computeIfAbsent(entry.projectId, Project::new); - prj.log.add(entry.message); - } else { - log.accept(entry.message); - } - break; - } - case ERROR: { - projects.values().stream().flatMap(p -> p.log.stream()).forEach(log); - clearDisplay(); - log.close(); - final AttributedStyle s = new AttributedStyle().bold().foreground(AttributedStyle.RED); - new AttributedString(entry.message, s).println(terminal); - terminal.flush(); - return; - } - case PROJECT_STATE: { - Project prj = projects.computeIfAbsent(entry.projectId, Project::new); - prj.status = entry.message; - break; - } - case PROJECT_FINISHED: { - Project prj = projects.remove(entry.projectId); - if (prj != null) { - prj.log.forEach(log); - } - doneProjects++; - displayDone(); - 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.update(Collections.emptyList(), 0); - break; - case CTRL_M: - displayDone = !displayDone; - displayDone(); - break; - } - break; - case DISPLAY: - display.update(Collections.emptyList(), 0); - terminal.writer().printf("[%s] %s%n", entry.projectId, entry.message); - break; - case PROMPT: - case PROMPT_PASSWORD: { - readInput.writeLock().lock(); - try { - display.update(Collections.emptyList(), 0); - terminal.writer().printf("[%s] %s", entry.projectId, entry.message); - terminal.flush(); - StringBuilder sb = new StringBuilder(); - while (true) { - int c = terminal.reader().read(); - if (c < 0) { - break; - } else if (c == '\n' || c == '\r') { - entry.response.put(sb.toString()); - terminal.writer().println(); - break; - } else if (c == 127) { - if (sb.length() > 0) { - sb.setLength(sb.length() - 1); - terminal.writer().write("\b \b"); - terminal.writer().flush(); - } - } else { - terminal.writer().print((char) c); - terminal.writer().flush(); - sb.append((char) c); - } - } - } finally { - readInput.writeLock().unlock(); - } - } - } - } - entries.clear(); - update(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (Exception e) { - this.exception = e; - } - } - } - private void clearDisplay() { display.update(Collections.emptyList(), 0); } - private void displayDone() throws IOException { + private void displayDone() { if (displayDone) { - log.flush(); + try { + log.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } } } @@ -399,8 +319,7 @@ public class TerminalOutput implements ClientOutput { public void close() throws Exception { closing = true; reader.interrupt(); - queue.put(new Event(EventType.END_OF_STREAM, null, null)); - worker.join(); + accept(SimpleMessage.BUILD_STOPPED_SINGLETON); reader.join(); terminal.close(); closed.countDown(); @@ -456,39 +375,41 @@ public class TerminalOutput implements ClientOutput { } private void addStatusLine(final List lines, int dispLines, final int projectsCount) { - AttributedStringBuilder asb = new AttributedStringBuilder(); - StringBuilder statusLine = new StringBuilder(64); - if (name == null) { - statusLine.append(buildStatus != null ? buildStatus : "Looking up daemon..."); - } else { - asb.append("Building "); - asb.style(AttributedStyle.BOLD); - asb.append(name); - asb.style(AttributedStyle.DEFAULT); - if (projectsCount <= dispLines) { - statusLine.append(" threads used/max: ") - .append(projectsCount).append('/').append(maxThreads); + if (name != null || buildStatus != null) { + AttributedStringBuilder asb = new AttributedStringBuilder(); + StringBuilder statusLine = new StringBuilder(64); + if (name != null) { + asb.append("Building "); + asb.style(AttributedStyle.BOLD); + asb.append(name); + asb.style(AttributedStyle.DEFAULT); + if (projectsCount <= dispLines) { + statusLine.append(" threads used/max: ") + .append(projectsCount).append('/').append(maxThreads); + } else { + statusLine.append(" threads used/hidden/max: ") + .append(projectsCount).append('/').append(projectsCount - dispLines).append('/').append(maxThreads); + } + + if (totalProjects > 0) { + statusLine.append(" progress: ").append(doneProjects).append('/').append(totalProjects).append(' ') + .append(doneProjects * 100 / totalProjects).append('%'); + } + } else if (buildStatus != null) { + statusLine.append(buildStatus); + } + + statusLine.append(" time: "); + long sec = (System.currentTimeMillis() - this.start) / 1000; + if (sec > 60) { + statusLine.append(sec / 60).append('m').append(String.valueOf(sec % 60)).append('s'); } else { - statusLine.append(" threads used/hidden/max: ") - .append(projectsCount).append('/').append(projectsCount - dispLines).append('/').append(maxThreads); + statusLine.append(sec).append('s'); } - if (totalProjects > 0) { - statusLine.append(" progress: ").append(doneProjects).append('/').append(totalProjects).append(' ') - .append(doneProjects * 100 / totalProjects).append('%'); - } + asb.append(statusLine.toString()); + lines.add(asb.toAttributedString()); } - - statusLine.append(" time: "); - long sec = (System.currentTimeMillis() - this.start) / 1000; - if (sec > 60) { - statusLine.append(sec / 60).append('m').append(String.valueOf(sec % 60)).append('s'); - } else { - statusLine.append(sec).append('s'); - } - - asb.append(statusLine.toString()); - lines.add(asb.toAttributedString()); } private void addProjectLine(final List lines, Project prj) { diff --git a/daemon/src/main/java/org/jboss/fuse/mvnd/daemon/Server.java b/daemon/src/main/java/org/jboss/fuse/mvnd/daemon/Server.java index a0a81cf4..75ad674d 100644 --- a/daemon/src/main/java/org/jboss/fuse/mvnd/daemon/Server.java +++ b/daemon/src/main/java/org/jboss/fuse/mvnd/daemon/Server.java @@ -51,14 +51,10 @@ import org.jboss.fuse.mvnd.common.DaemonStopEvent; import org.jboss.fuse.mvnd.common.Environment; import org.jboss.fuse.mvnd.common.Message; import org.jboss.fuse.mvnd.common.Message.BuildEvent; -import org.jboss.fuse.mvnd.common.Message.BuildEvent.Type; import org.jboss.fuse.mvnd.common.Message.BuildException; import org.jboss.fuse.mvnd.common.Message.BuildMessage; import org.jboss.fuse.mvnd.common.Message.BuildRequest; import org.jboss.fuse.mvnd.common.Message.BuildStarted; -import org.jboss.fuse.mvnd.common.Message.Display; -import org.jboss.fuse.mvnd.common.Message.Prompt; -import org.jboss.fuse.mvnd.common.Message.PromptResponse; import org.jboss.fuse.mvnd.daemon.DaemonExpiration.DaemonExpirationResult; import org.jboss.fuse.mvnd.daemon.DaemonExpiration.DaemonExpirationStrategy; import org.jboss.fuse.mvnd.logging.smart.AbstractLoggingSpy; @@ -521,29 +517,32 @@ public class Server implements AutoCloseable, Runnable { } int getClassOrder(Message m) { - if (m instanceof BuildRequest) { + switch (m.getType()) { + case Message.BUILD_REQUEST: return 0; - } else if (m instanceof BuildStarted) { + case Message.BUILD_STARTED: return 1; - } else if (m instanceof Prompt || m instanceof PromptResponse || m instanceof Display) { + case Message.PROMPT: + case Message.PROMPT_RESPONSE: + case Message.DISPLAY: return 2; - } else if (m instanceof BuildEvent && ((BuildEvent) m).getType() == Type.ProjectStarted) { + case Message.PROJECT_STARTED: return 3; - } else if (m instanceof BuildEvent && ((BuildEvent) m).getType() == Type.MojoStarted) { + case Message.MOJO_STARTED: return 4; - } else if (m instanceof BuildMessage) { + case Message.BUILD_MESSAGE: return 50; - } else if (m instanceof BuildEvent && ((BuildEvent) m).getType() == Type.ProjectStopped) { + case Message.PROJECT_STOPPED: return 95; - } else if (m instanceof BuildEvent && ((BuildEvent) m).getType() == Type.BuildStopped) { + case Message.BUILD_STOPPED: return 96; - } else if (m instanceof BuildException) { + case Message.BUILD_EXCEPTION: return 97; - } else if (m == Message.STOP_SINGLETON) { + case Message.STOP: return 99; - } else if (m == Message.KEEP_ALIVE_SINGLETON) { + case Message.KEEP_ALIVE: return 100; - } else { + default: throw new IllegalStateException(); } } @@ -598,7 +597,7 @@ public class Server implements AutoCloseable, Runnable { } public void finish() throws Exception { - queue.add(new BuildEvent(Type.BuildStopped, "", "")); + queue.add(Message.BUILD_STOPPED_SINGLETON); queue.add(Message.STOP_SINGLETON); } @@ -615,17 +614,17 @@ public class Server implements AutoCloseable, Runnable { @Override protected void onStartProject(String projectId, String display) { - sendEvent(Type.ProjectStarted, projectId, display); + queue.add(new BuildEvent(Message.PROJECT_STARTED, projectId, display)); } @Override protected void onStopProject(String projectId, String display) { - sendEvent(Type.ProjectStopped, projectId, display); + queue.add(new BuildEvent(Message.PROJECT_STOPPED, projectId, display)); } @Override protected void onStartMojo(String projectId, String display) { - sendEvent(Type.MojoStarted, projectId, display); + queue.add(new BuildEvent(Message.MOJO_STARTED, projectId, display)); } @Override @@ -633,9 +632,5 @@ public class Server implements AutoCloseable, Runnable { queue.add(new BuildMessage(projectId, message)); } - private void sendEvent(Type type, String projectId, String display) { - queue.add(new BuildEvent(type, projectId, display)); - } - } } diff --git a/daemon/src/main/java/org/jboss/fuse/mvnd/logging/smart/MavenLoggingSpy.java b/daemon/src/main/java/org/jboss/fuse/mvnd/logging/smart/MavenLoggingSpy.java index 94b1fafd..c9365377 100644 --- a/daemon/src/main/java/org/jboss/fuse/mvnd/logging/smart/MavenLoggingSpy.java +++ b/daemon/src/main/java/org/jboss/fuse/mvnd/logging/smart/MavenLoggingSpy.java @@ -17,6 +17,8 @@ package org.jboss.fuse.mvnd.logging.smart; import java.io.IOError; import org.apache.maven.execution.MavenSession; +import org.jboss.fuse.mvnd.common.Message; +import org.jboss.fuse.mvnd.common.Message.BuildEvent; import org.jboss.fuse.mvnd.common.logging.TerminalOutput; public class MavenLoggingSpy extends AbstractLoggingSpy { @@ -30,10 +32,10 @@ public class MavenLoggingSpy extends AbstractLoggingSpy { protected void onStartSession(MavenSession session) { try { output = new TerminalOutput(null); - output.startBuild( + output.accept(new Message.BuildStarted( session.getTopLevelProject().getName(), session.getAllProjects().size(), - session.getRequest().getDegreeOfConcurrency()); + session.getRequest().getDegreeOfConcurrency())); } catch (Exception e) { throw new IOError(e); } @@ -51,30 +53,24 @@ public class MavenLoggingSpy extends AbstractLoggingSpy { @Override protected void onStartProject(String projectId, String display) { super.onStartProject(projectId, display); - output.projectStateChanged(projectId, display); + output.accept(new BuildEvent(Message.PROJECT_STARTED, projectId, display)); } @Override protected void onStopProject(String projectId, String display) { - output.projectFinished(projectId); + output.accept(new BuildEvent(Message.PROJECT_STOPPED, projectId, display)); } @Override protected void onStartMojo(String projectId, String display) { super.onStartMojo(projectId, display); - output.projectStateChanged(projectId, display); - } - - @Override - protected void onStopMojo(String projectId, String display) { - output.projectStateChanged(projectId, ":" + projectId); - super.onStopMojo(projectId, display); + output.accept(new BuildEvent(Message.MOJO_STARTED, projectId, display)); } @Override protected void onProjectLog(String projectId, String message) { super.onProjectLog(projectId, message); - output.accept(projectId, message); + output.accept(Message.log(projectId, message)); } } diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 14ed21e0..d69111a3 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -63,11 +63,6 @@ junit-jupiter test - - org.mockito - mockito-core - test - org.apache.maven maven-model diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/EqualsInOrderAmongOthers.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/EqualsInOrderAmongOthers.java deleted file mode 100644 index 2d107ccf..00000000 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/EqualsInOrderAmongOthers.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.assertj; - -import java.util.Collection; -import java.util.List; -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 expected items is equal to some item in - * a list of strings exactly once in the order given by the expected items collection. The input list may contain other - * non-matching items. - * - * @param the type of the tested {@link List}. - */ -public class EqualsInOrderAmongOthers> extends Condition { - - public EqualsInOrderAmongOthers(String... expectedItems) { - this(Stream.of(expectedItems).collect(Collectors.toList())); - } - - public EqualsInOrderAmongOthers(final Collection expectedItems) { - super( - messages -> messages.stream() - /* map each message to the matching pattern or null of none matches */ - .map(m -> expectedItems.stream() - .filter(expected -> expected.equals(m)) - .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(expectedItems), - "Match in order: " + expectedItems.stream().collect(Collectors.joining(", ")), - expectedItems); - } - -} diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/MatchInOrderAmongOthers.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/MatchInOrderAmongOthers.java index 39d4c8c0..6e3211da 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/MatchInOrderAmongOthers.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/MatchInOrderAmongOthers.java @@ -22,6 +22,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import org.assertj.core.api.Condition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An AssertJ {@link Condition} to assert that each item of a collection of regular expressions matches some item in @@ -32,22 +34,36 @@ import org.assertj.core.api.Condition; */ public class MatchInOrderAmongOthers> extends Condition { + private static final Logger LOG = LoggerFactory.getLogger(MatchInOrderAmongOthers.class); + public MatchInOrderAmongOthers(String... expectedItems) { this(Stream.of(expectedItems).map(Pattern::compile).collect(Collectors.toList())); } public MatchInOrderAmongOthers(final Collection 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(Objects::nonNull) /* remove null patterns */ - .collect(Collectors.toList()) - /* if the mapped patterns equal the input patterns then each pattern matched exactly once */ - .equals(patterns), + messages -> { + final List matchingPatterns = 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(Objects::nonNull) /* remove null patterns */ + .collect(Collectors.toList()); + final boolean result = matchingPatterns.equals(patterns); + if (!result) { + LOG.warn("Actually matched:\n" + + matchingPatterns.stream().map(p -> " " + p.pattern()).collect(Collectors.joining("\n"))); + LOG.warn("Did not match:\n" + + patterns.stream() + .filter(p -> !matchingPatterns.contains(p)) + .map(p -> " " + p.pattern()) + .collect(Collectors.joining("\n"))); + } + /* if the mapped patterns equal the input patterns then each pattern matched exactly once */ + return result; + }, "Match in order: " + patterns.stream().map(Pattern::pattern).collect(Collectors.joining(", ")), patterns); } diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/TestClientOutput.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/TestClientOutput.java new file mode 100644 index 00000000..43f1eb6d --- /dev/null +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/assertj/TestClientOutput.java @@ -0,0 +1,61 @@ +/* + * 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.assertj; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.assertj.core.api.Assertions; +import org.jboss.fuse.mvnd.common.Message; +import org.jboss.fuse.mvnd.common.logging.ClientOutput; + +public class TestClientOutput implements ClientOutput { + private final List messages = new ArrayList<>(); + + @Override + public void close() throws Exception { + } + + @Override + public void accept(Message message) { + messages.add(message); + } + + @Override + public void accept(List messages) { + for (Message message : messages) { + accept(message); + } + } + + @Override + public void describeTerminal() { + accept(Message.log("Test terminal")); + } + + public List getMessages() { + return messages; + } + + public void assertContainsMatchingSubsequence(String... patterns) { + Assertions.assertThat(messagesToString()).is(new MatchInOrderAmongOthers<>(patterns)); + } + + public List messagesToString() { + return messages.stream().map(m -> m.toString()).collect(Collectors.toList()); + } + +} diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/DaemonCrashTest.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/DaemonCrashTest.java index 01960669..c751acac 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/DaemonCrashTest.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/DaemonCrashTest.java @@ -21,14 +21,13 @@ import java.nio.file.Path; import java.util.stream.Stream; import javax.inject.Inject; import org.assertj.core.api.Assertions; +import org.jboss.fuse.mvnd.assertj.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.client.DaemonParameters; import org.jboss.fuse.mvnd.common.DaemonException; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.jboss.fuse.mvnd.junit.MvndTest; import org.jboss.fuse.mvnd.junit.TestUtils; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -58,7 +57,7 @@ public class DaemonCrashTest { }; Stream.of(installedJars).forEach(jar -> Assertions.assertThat(jar).doesNotExist()); - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); assertThrows(DaemonException.StaleAddressException.class, () -> client.execute(output, "clean", "install", "-e", "-Dmvnd.log.level=DEBUG").assertFailure()); } diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/ExtensionsNativeIT.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/ExtensionsNativeIT.java index fccde270..67bb4476 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/ExtensionsNativeIT.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/ExtensionsNativeIT.java @@ -18,14 +18,13 @@ 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.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.client.DaemonParameters; import org.jboss.fuse.mvnd.common.DaemonInfo; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.jboss.fuse.mvnd.junit.MvndNativeTest; import org.jboss.fuse.mvnd.junit.TestRegistry; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -46,7 +45,7 @@ public class ExtensionsNativeIT { registry.killAll(); Assertions.assertThat(registry.getAll().size()).isEqualTo(0); - final ClientOutput o = Mockito.mock(ClientOutput.class); + final TestClientOutput o = new TestClientOutput(); client.execute(o, "-v").assertSuccess(); Assertions.assertThat(registry.getAll().size()).isEqualTo(1); DaemonInfo daemon = registry.getAll().iterator().next(); diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/InteractiveTest.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/InteractiveTest.java index c06e0abe..2182f657 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/InteractiveTest.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/InteractiveTest.java @@ -17,13 +17,14 @@ package org.jboss.fuse.mvnd.it; import java.io.IOException; import javax.inject.Inject; +import org.jboss.fuse.mvnd.assertj.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.client.DaemonParameters; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; +import org.jboss.fuse.mvnd.common.Message; +import org.jboss.fuse.mvnd.common.Message.Prompt; import org.jboss.fuse.mvnd.junit.MvndTest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; @MvndTest(projectDir = "src/test/projects/single-module") public class InteractiveTest { @@ -39,9 +40,15 @@ public class InteractiveTest { final String version = MvndTestUtil.version(parameters.multiModuleProjectDirectory().resolve("pom.xml")); Assertions.assertEquals("0.0.1-SNAPSHOT", version); - final ClientOutput o = Mockito.mock(ClientOutput.class); - Mockito.when(o.prompt("single-module", "Enter the new version to set 0.0.1-SNAPSHOT: ", false)) - .thenReturn("0.1.0-SNAPSHOT"); + final TestClientOutput o = new TestClientOutput() { + @Override + public void accept(Message m) { + if (m instanceof Prompt) { + ((Prompt) m).getCallback().accept("0.1.0-SNAPSHOT"); + } + super.accept(m); + } + }; client.execute(o, "versions:set").assertSuccess(); final String newVersion = MvndTestUtil.version(parameters.multiModuleProjectDirectory().resolve("pom.xml")); diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/InvokerNativeIT.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/InvokerNativeIT.java index 1214bbf1..7c0cd57f 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/InvokerNativeIT.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/InvokerNativeIT.java @@ -23,12 +23,11 @@ import java.util.List; import java.util.regex.Pattern; import javax.inject.Inject; import org.assertj.core.api.Assertions; +import org.jboss.fuse.mvnd.assertj.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.client.DaemonParameters; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.jboss.fuse.mvnd.junit.MvndNativeTest; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; @MvndNativeTest(projectDir = "src/test/projects/invoker") public class InvokerNativeIT { @@ -55,7 +54,7 @@ public class InvokerNativeIT { throw new RuntimeException("Could not delete " + helloPath); } - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); client.execute(output, "clean", "verify", "-e", "-Dmvnd.log.level=DEBUG").assertSuccess(); Assertions.assertThat(helloPath).exists(); diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/ModuleAndPluginNativeIT.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/ModuleAndPluginNativeIT.java index 0d5c4e42..305904ce 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/ModuleAndPluginNativeIT.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/ModuleAndPluginNativeIT.java @@ -22,13 +22,12 @@ import java.nio.file.Path; import java.util.stream.Stream; import javax.inject.Inject; import org.assertj.core.api.Assertions; +import org.jboss.fuse.mvnd.assertj.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.client.DaemonParameters; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.jboss.fuse.mvnd.junit.MvndNativeTest; import org.jboss.fuse.mvnd.junit.TestUtils; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; @MvndNativeTest(projectDir = "src/test/projects/module-and-plugin") public class ModuleAndPluginNativeIT { @@ -61,7 +60,7 @@ public class ModuleAndPluginNativeIT { /* Build #1: with "Hello" output to target/hello.txt */ { - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); client.execute(output, "clean", "install", "-e", "-Dmvnd.log.level=DEBUG", "-Dhello.property=Hello1") .assertSuccess(); @@ -78,7 +77,7 @@ public class ModuleAndPluginNativeIT { .resolve("plugin/src/main/java/org/jboss/fuse/mvnd/test/module/plugin/mojo/HelloMojo.java"); TestUtils.replace(mojoPath, "\"Hello\".getBytes", "\"Hi\".getBytes"); - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); client.execute(output, "clean", "install", "-e", "-Dmvnd.log.level=DEBUG", "-Dhello.property=Hello2").assertSuccess(); diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/MultiModuleTest.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/MultiModuleTest.java index f3a4376e..c83754cd 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/MultiModuleTest.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/MultiModuleTest.java @@ -18,21 +18,22 @@ package org.jboss.fuse.mvnd.it; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Inject; import org.assertj.core.api.Assertions; -import org.jboss.fuse.mvnd.assertj.EqualsInOrderAmongOthers; import org.jboss.fuse.mvnd.assertj.MatchInOrderAmongOthers; +import org.jboss.fuse.mvnd.assertj.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.client.DaemonParameters; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; +import org.jboss.fuse.mvnd.common.Message; +import org.jboss.fuse.mvnd.common.Message.BuildEvent; +import org.jboss.fuse.mvnd.common.Message.BuildMessage; import org.jboss.fuse.mvnd.junit.MvndTest; import org.jboss.fuse.mvnd.junit.TestUtils; 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 { @@ -69,44 +70,52 @@ public class MultiModuleTest { }; Stream.of(installedJars).forEach(jar -> Assertions.assertThat(jar).doesNotExist()); - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); client.execute(output, "clean", "install", "-e").assertSuccess(); - final ArgumentCaptor logMessage = ArgumentCaptor.forClass(String.class); - Mockito.verify(output, Mockito.atLeast(1)).accept(any(), logMessage.capture()); - Assertions.assertThat(logMessage.getAllValues()) - .satisfiesAnyOf( /* Two orderings are possible */ - messages -> Assertions.assertThat(messages) - .is(new MatchInOrderAmongOthers<>( - "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module$", - "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-api", - "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-hello", - "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-hi")), - messages -> Assertions.assertThat(messages) - .is(new MatchInOrderAmongOthers<>( - "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module$", - "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-api", - "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-hi", - "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-hello")) + { + final List filteredMessages = output.getMessages().stream() + .filter(m -> m.getType() == Message.BUILD_MESSAGE) + .map(m -> ((BuildMessage) m).getMessage()) + .collect(Collectors.toList()); - ); + Assertions.assertThat(filteredMessages) + .satisfiesAnyOf( /* Two orderings are possible */ + messages -> Assertions.assertThat(messages) + .is(new MatchInOrderAmongOthers<>( + "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module$", + "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-api", + "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-hello", + "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-hi")), + messages -> Assertions.assertThat(messages) + .is(new MatchInOrderAmongOthers<>( + "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module$", + "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-api", + "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-hi", + "SUCCESS build of project org.jboss.fuse.mvnd.test.multi-module:multi-module-hello"))); + } - final ArgumentCaptor projectFinished = ArgumentCaptor.forClass(String.class); - Mockito.verify(output, Mockito.atLeast(1)).projectFinished(projectFinished.capture()); - Assertions.assertThat(projectFinished.getAllValues()) - .satisfiesAnyOf( /* Two orderings are possible */ - messages -> Assertions.assertThat(messages) - .is(new EqualsInOrderAmongOthers<>( - "multi-module", - "multi-module-api", - "multi-module-hello", - "multi-module-hi")), - messages -> Assertions.assertThat(messages) - .is(new EqualsInOrderAmongOthers<>( - "multi-module", - "multi-module-api", - "multi-module-hi", - "multi-module-hello"))); + { + final List filteredMessages = output.getMessages().stream() + .filter(m -> m.getType() == Message.PROJECT_STARTED) + .map(m -> ((BuildEvent) m).getProjectId()) + .collect(Collectors.toList()); + + Assertions.assertThat(filteredMessages) + .satisfiesAnyOf( /* Two orderings are possible */ + messages -> Assertions.assertThat(messages) + .isEqualTo(Arrays.asList( + "multi-module", + "multi-module-api", + "multi-module-hello", + "multi-module-hi")), + messages -> Assertions.assertThat(messages) + .isEqualTo(Arrays.asList( + "multi-module", + "multi-module-api", + "multi-module-hi", + "multi-module-hello"))); + } /* Make sure HelloTest and HiTest have created the files they were supposed to create */ Stream.of(helloFilePaths).forEach(path -> Assertions.assertThat(path).exists()); diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/SingleModuleNativeIT.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/SingleModuleNativeIT.java index 98c4252a..850b50cb 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/SingleModuleNativeIT.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/SingleModuleNativeIT.java @@ -18,19 +18,19 @@ package org.jboss.fuse.mvnd.it; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Properties; +import java.util.stream.Collectors; import javax.inject.Inject; import org.assertj.core.api.Assertions; +import org.jboss.fuse.mvnd.assertj.MatchInOrderAmongOthers; +import org.jboss.fuse.mvnd.assertj.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.client.DaemonParameters; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; +import org.jboss.fuse.mvnd.common.Message; import org.jboss.fuse.mvnd.junit.MvndNativeTest; import org.jboss.fuse.mvnd.junit.TestUtils; 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 { @@ -54,18 +54,23 @@ public class SingleModuleNativeIT { "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); + final TestClientOutput o = new TestClientOutput(); client.execute(o, "clean", "install", "-e").assertSuccess(); final Properties props = MvndTestUtil.properties(parameters.multiModuleProjectDirectory().resolve("pom.xml")); - final InOrder inOrder = Mockito.inOrder(o); - 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("BUILD SUCCESS")); + final List messages = o.getMessages().stream() + .filter(m -> m.getType() != Message.MOJO_STARTED) + .map(m -> m.toString()) + .collect(Collectors.toList()); + Assertions.assertThat(messages) + .is(new MatchInOrderAmongOthers<>( + "Building single-module", + MvndTestUtil.plugin(props, "maven-clean-plugin") + ":clean", + MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":compile", + MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":testCompile", + MvndTestUtil.plugin(props, "maven-surefire-plugin") + ":test", + MvndTestUtil.plugin(props, "maven-install-plugin") + ":install", + "BUILD SUCCESS")); assertJVM(o, props); @@ -76,7 +81,7 @@ public class SingleModuleNativeIT { } - protected void assertJVM(ClientOutput o, Properties props) { - /* implemented in the subclass*/ + protected void assertJVM(TestClientOutput o, Properties props) { + /* implemented in the subclass */ } } diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/SingleModuleTest.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/SingleModuleTest.java index abda01c3..ce3bcbdd 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/SingleModuleTest.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/SingleModuleTest.java @@ -15,50 +15,41 @@ */ package org.jboss.fuse.mvnd.it; +import java.util.List; import java.util.Properties; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; +import java.util.stream.Collectors; +import org.assertj.core.api.Assertions; +import org.jboss.fuse.mvnd.assertj.MatchInOrderAmongOthers; +import org.jboss.fuse.mvnd.assertj.TestClientOutput; +import org.jboss.fuse.mvnd.common.Message; import org.jboss.fuse.mvnd.junit.MvndTest; -import org.mockito.InOrder; -import org.mockito.Mockito; @MvndTest(projectDir = "src/test/projects/single-module") public class SingleModuleTest extends SingleModuleNativeIT { - protected void assertJVM(ClientOutput output, Properties props) { - 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:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-resources-plugin") - + ":resources {execution: default-resources}"); - 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:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-resources-plugin") - + ":testResources {execution: default-testResources}"); - 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:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-surefire-plugin") - + ":test {execution: default-test}"); - inOrder.verify(output).projectStateChanged( - "single-module", - ":single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-install-plugin") - + ":install {execution: default-install}"); + protected void assertJVM(TestClientOutput o, Properties props) { + final List filteredMessages = o.getMessages().stream() + .filter(m -> m.getType() == Message.MOJO_STARTED) + .map(m -> m.toString()) + .collect(Collectors.toList()); + + Assertions.assertThat(filteredMessages) + .is(new MatchInOrderAmongOthers<>( + "\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-clean-plugin") + + ":clean {execution: default-clean}\\E", + "\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-resources-plugin") + + ":resources {execution: default-resources}\\E", + "\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-compiler-plugin") + + ":compile {execution: default-compile}\\E", + "\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-resources-plugin") + + ":testResources {execution: default-testResources}\\E", + "\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-compiler-plugin") + + ":testCompile {execution: default-testCompile}\\E", + "\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-surefire-plugin") + + ":test {execution: default-test}\\E", + "\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-install-plugin") + + ":install {execution: default-install}\\E")); - inOrder.verify(output).projectFinished("single-module"); } } diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/StopStatusTest.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/StopStatusTest.java index a61f0125..420c0cf0 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/StopStatusTest.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/StopStatusTest.java @@ -19,17 +19,12 @@ import java.io.IOException; import java.util.stream.Collectors; import javax.inject.Inject; import org.assertj.core.api.Assertions; -import org.jboss.fuse.mvnd.assertj.MatchInOrderAmongOthers; +import org.jboss.fuse.mvnd.assertj.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.common.DaemonInfo; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.jboss.fuse.mvnd.junit.MvndTest; import org.jboss.fuse.mvnd.junit.TestRegistry; 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 { @@ -46,41 +41,36 @@ public class StopStatusTest { /* The registry should be empty before we run anything */ Assertions.assertThat(registry.getAll()).isEmpty(); - client.execute(Mockito.mock(ClientOutput.class), "clean").assertSuccess(); + client.execute(new TestClientOutput(), "clean").assertSuccess(); /* There should be exactly one item in the registry after the first build */ Assertions.assertThat(registry.getAll().size()).isEqualTo(1); final DaemonInfo d = registry.getAll().get(0); { /* The output of --status must be consistent with the registry */ - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); client.execute(output, "--status").assertSuccess(); - final ArgumentCaptor logMessage = ArgumentCaptor.forClass(String.class); - Mockito.verify(output, Mockito.atLeast(1)).accept(any(), logMessage.capture()); - Assertions.assertThat(logMessage.getAllValues()) - .is(new MatchInOrderAmongOthers<>( - d.getUid() + " +" + d.getPid() + " +" + d.getAddress())); + output.assertContainsMatchingSubsequence(d.getUid() + " +" + d.getPid() + " +" + d.getAddress()); } /* Wait, till the instance becomes idle */ registry.awaitIdle(d.getUid()); - client.execute(Mockito.mock(ClientOutput.class), "clean").assertSuccess(); + client.execute(new TestClientOutput(), "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(); + client.execute(new TestClientOutput(), "--stop").assertSuccess(); /* No items in the registry after we have killed all daemons */ Assertions.assertThat(registry.getAll()).isEmpty(); { /* After --stop, the output of --status may not contain the UID we have seen before */ - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); client.execute(output, "--status").assertSuccess(); - final ArgumentCaptor logMessage = ArgumentCaptor.forClass(String.class); - Mockito.verify(output, Mockito.atLeast(1)).accept(any(), logMessage.capture()); + Assertions.assertThat( - logMessage.getAllValues().stream() + output.messagesToString().stream() .filter(m -> m.contains(d.getUid())) .collect(Collectors.toList())) .isEmpty(); diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/UpgradesInBomNativeIT.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/UpgradesInBomNativeIT.java index 39a9d21b..2c892a85 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/UpgradesInBomNativeIT.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/UpgradesInBomNativeIT.java @@ -20,16 +20,15 @@ import java.nio.file.Path; import java.util.Arrays; import javax.inject.Inject; import org.assertj.core.api.Assertions; +import org.jboss.fuse.mvnd.assertj.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.common.DaemonInfo; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.jboss.fuse.mvnd.junit.ClientFactory; import org.jboss.fuse.mvnd.junit.MvndNativeTest; import org.jboss.fuse.mvnd.junit.TestParameters; import org.jboss.fuse.mvnd.junit.TestRegistry; import org.jboss.fuse.mvnd.junit.TestUtils; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; @MvndNativeTest(projectDir = "src/test/projects/upgrades-in-bom") public class UpgradesInBomNativeIT { @@ -48,7 +47,7 @@ public class UpgradesInBomNativeIT { /* Install the dependencies */ for (String artifactDir : Arrays.asList("project/hello-0.0.1", "project/hello-0.0.2-SNAPSHOT")) { final Client cl = clientFactory.newClient(parameters.cd(parameters.getTestDir().resolve(artifactDir))); - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); cl.execute(output, "clean", "install", "-e").assertSuccess(); registry.killAll(); } @@ -58,7 +57,7 @@ public class UpgradesInBomNativeIT { final Path parentDir = parameters.getTestDir().resolve("project/parent"); final Client cl = clientFactory.newClient(parameters.cd(parentDir)); { - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); cl.execute(output, "clean", "install", "-e").assertSuccess(); } Assertions.assertThat(registry.getAll().size()).isEqualTo(1); @@ -76,7 +75,7 @@ public class UpgradesInBomNativeIT { .resolve("module/src/main/java/org/jboss/fuse/mvnd/test/upgrades/bom/module/UseHello.java"); TestUtils.replace(useHelloPath, "new Hello().sayHello()", "new Hello().sayWisdom()"); { - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); cl.execute(output, "clean", "install", "-e").assertSuccess(); } Assertions.assertThat(registry.getAll().size()).isEqualTo(1); diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/VersionNativeIT.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/VersionNativeIT.java index b187f9db..2bc7b1fb 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/VersionNativeIT.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/it/VersionNativeIT.java @@ -17,18 +17,12 @@ 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.assertj.TestClientOutput; import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.client.DaemonParameters; -import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.jboss.fuse.mvnd.junit.MvndNativeTest; import org.jboss.fuse.mvnd.junit.MvndTestExtension; 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 { @@ -41,20 +35,16 @@ public class VersionNativeIT { @Test void version() throws IOException, InterruptedException { - final ClientOutput output = Mockito.mock(ClientOutput.class); + final TestClientOutput output = new TestClientOutput(); client.execute(output, "-v").assertSuccess(); - final ArgumentCaptor logMessage = ArgumentCaptor.forClass(String.class); - Mockito.verify(output, Mockito.atLeast(1)).accept(any(), logMessage.capture()); - - Assertions.assertThat(logMessage.getAllValues()) - .is(new MatchInOrderAmongOthers<>( - "\\QMaven Daemon " - + System.getProperty("project.version") - + "-" + System.getProperty("os.detected.name") - + "-" + System.getProperty("os.detected.arch") - + "\\E", - "\\QMaven home: " + parameters.mvndHome() + "\\E")); + output.assertContainsMatchingSubsequence( + "\\QMaven Daemon " + + System.getProperty("project.version") + + "-" + System.getProperty("os.detected.name") + + "-" + System.getProperty("os.detected.arch") + + "\\E", + "\\QMaven home: " + parameters.mvndHome() + "\\E"); } } diff --git a/integration-tests/src/test/java/org/jboss/fuse/mvnd/junit/NativeTestClient.java b/integration-tests/src/test/java/org/jboss/fuse/mvnd/junit/NativeTestClient.java index b3127524..6eaf22cc 100644 --- a/integration-tests/src/test/java/org/jboss/fuse/mvnd/junit/NativeTestClient.java +++ b/integration-tests/src/test/java/org/jboss/fuse/mvnd/junit/NativeTestClient.java @@ -25,6 +25,7 @@ import org.jboss.fuse.mvnd.client.Client; import org.jboss.fuse.mvnd.client.DaemonParameters; import org.jboss.fuse.mvnd.client.ExecutionResult; import org.jboss.fuse.mvnd.common.Environment; +import org.jboss.fuse.mvnd.common.Message; import org.jboss.fuse.mvnd.common.OsUtils.CommandProcess; import org.jboss.fuse.mvnd.common.logging.ClientOutput; @@ -82,7 +83,7 @@ public class NativeTestClient implements Client { env.put("JAVA_HOME", System.getProperty("java.home")); } final String cmdString = String.join(" ", cmd); - output.accept(null, "Executing " + cmdString); + output.accept(Message.log("Executing " + cmdString)); final List log = new ArrayList<>(); final Consumer loggingConsumer = s -> { @@ -91,7 +92,7 @@ public class NativeTestClient implements Client { } }; try (CommandProcess process = new CommandProcess(builder.start(), - loggingConsumer.andThen(s -> output.accept(null, s)))) { + loggingConsumer.andThen(s -> output.accept(Message.log(s))))) { final int exitCode = process.waitFor(timeoutMs); return new Result(args, exitCode, log); } catch (IOException e) { diff --git a/integration-tests/src/test/resources/simplelogger.properties b/integration-tests/src/test/resources/simplelogger.properties new file mode 100644 index 00000000..57a6bcc7 --- /dev/null +++ b/integration-tests/src/test/resources/simplelogger.properties @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +org.slf4j.simpleLogger.defaultLogLevel=info +org.slf4j.simpleLogger.showDateTime=false +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.showLogName=false +org.slf4j.simpleLogger.logFile=System.out +org.slf4j.simpleLogger.cacheOutputStream=true +org.slf4j.simpleLogger.levelInBrackets=true +org.slf4j.simpleLogger.log.Sisu=info +org.slf4j.simpleLogger.warnLevelString=WARNING + +# MNG-6181: mvn -X also prints all debug logging from HttpClient +# Be aware that the shaded packages are used +# org.apache.http -> org.apache.maven.wagon.providers.http.httpclient +org.slf4j.simpleLogger.log.org.apache.maven.wagon.providers.http.httpclient=off +org.slf4j.simpleLogger.log.org.apache.maven.wagon.providers.http.httpclient.wire=off diff --git a/pom.xml b/pom.xml index e241efd2..6c255a75 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,6 @@ 5.6.0 1.2.3 3.6.3 - 3.3.3 1.7.29 0.11.2 @@ -192,12 +191,6 @@ ${jline.version} - - org.mockito - mockito-core - ${mockito.version} - - org.slf4j log4j-over-slf4j