Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions common/src/main/java/org/mvndaemon/mvnd/common/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ public abstract class Message {
public static final int INPUT_DATA = 28;
public static final int REQUEST_INPUT_AVAILABLE = 29;
public static final int INPUT_AVAILABLE_DATA = 30;
/**
* Live per-test progress for a project's line while surefire/failsafe run.
* TODO: the daemon-side feed that emits this message is not implemented on mvnd-1.x yet; until it
* lands in a follow-up commit, the client render stays dormant (no test-progress suffix is shown).
*/
public static final int PROJECT_TEST_PROGRESS = 31;

final int type;

Expand All @@ -91,6 +97,8 @@ public static Message read(DataInputStream input) throws IOException {
case PROJECT_LOG_MESSAGE:
case DISPLAY:
return ProjectEvent.read(type, input);
case PROJECT_TEST_PROGRESS:
return ProjectTestProgressEvent.read(input);
case BUILD_EXCEPTION:
return BuildException.read(input);
case KEEP_ALIVE:
Expand Down Expand Up @@ -162,6 +170,8 @@ public static int getClassOrder(Message m) {
return 3;
case MOJO_STARTED:
return 4;
case PROJECT_TEST_PROGRESS:
return 5;
case EXECUTION_FAILURE:
return 10;
case TRANSFER_INITIATED:
Expand Down Expand Up @@ -623,6 +633,103 @@ public void write(DataOutputStream output) throws IOException {
}
}

public static class ProjectTestProgressEvent extends Message {
final String projectId;
final String testClass;
final String testMethod;
final int completed;
final int failures;
final int errors;
final int skipped;

public static ProjectTestProgressEvent read(DataInputStream input) throws IOException {
final String projectId = readUTF(input);
final String testClass = readUTF(input);
final String testMethod = readUTF(input);
final int completed = input.readInt();
final int failures = input.readInt();
final int errors = input.readInt();
final int skipped = input.readInt();
return new ProjectTestProgressEvent(projectId, testClass, testMethod, completed, failures, errors, skipped);
}

public ProjectTestProgressEvent(
String projectId,
String testClass,
String testMethod,
int completed,
int failures,
int errors,
int skipped) {
super(PROJECT_TEST_PROGRESS);
this.projectId = Objects.requireNonNull(projectId, "projectId cannot be null");
this.testClass = testClass;
this.testMethod = testMethod;
this.completed = completed;
this.failures = failures;
this.errors = errors;
this.skipped = skipped;
}

public String getProjectId() {
return projectId;
}

public String getTestClass() {
return testClass;
}

public String getTestMethod() {
return testMethod;
}

public int getCompleted() {
return completed;
}

public int getFailures() {
return failures;
}

public int getErrors() {
return errors;
}

public int getSkipped() {
return skipped;
}

@Override
public void write(DataOutputStream output) throws IOException {
super.write(output);
writeUTF(output, projectId);
writeUTF(output, testClass);
writeUTF(output, testMethod);
output.writeInt(completed);
output.writeInt(failures);
output.writeInt(errors);
output.writeInt(skipped);
}

@Override
public String toString() {
return "ProjectTestProgress{projectId='" + projectId + "', testClass='" + testClass + "', testMethod='"
+ testMethod + "', completed=" + completed + ", failures=" + failures + ", errors=" + errors
+ ", skipped=" + skipped + "}";
}
}

public static ProjectTestProgressEvent projectTestProgress(
String projectId,
String testClass,
String testMethod,
int completed,
int failures,
int errors,
int skipped) {
return new ProjectTestProgressEvent(projectId, testClass, testMethod, completed, failures, errors, skipped);
}

public static class BuildStarted extends Message {

final String projectId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public class TerminalOutput implements ClientOutput {

private static final AttributedStyle GREEN_FOREGROUND = new AttributedStyle().foreground(AttributedStyle.GREEN);
private static final AttributedStyle CYAN_FOREGROUND = new AttributedStyle().foreground(AttributedStyle.CYAN);
private static final AttributedStyle BOLD_GREEN_FOREGROUND =
new AttributedStyle().bold().foreground(AttributedStyle.GREEN);

private final Terminal terminal;
private final Terminal.SignalHandler previousIntHandler;
Expand Down Expand Up @@ -141,6 +143,7 @@ public class TerminalOutput implements ClientOutput {
static class Project {
final String id;
MojoStartedEvent runningExecution;
Message.ProjectTestProgressEvent testProgress;
final List<String> log = new ArrayList<>();

public Project(String id) {
Expand Down Expand Up @@ -269,6 +272,7 @@ private boolean doAccept(Message entry) {
final MojoStartedEvent execution = (MojoStartedEvent) entry;
final Project prj = projects.computeIfAbsent(execution.getArtifactId(), Project::new);
prj.runningExecution = execution;
prj.testProgress = null;
break;
}
case Message.PROJECT_STOPPED: {
Expand Down Expand Up @@ -424,6 +428,14 @@ private boolean doAccept(Message entry) {
daemonDispatch.accept(entry);
break;
}
case Message.PROJECT_TEST_PROGRESS: {
final Message.ProjectTestProgressEvent e = (Message.ProjectTestProgressEvent) entry;
final Project prj = projects.get(e.getProjectId());
if (prj != null) {
prj.testProgress = e;
}
break;
}
default:
throw new IllegalStateException("Unexpected message " + entry);
}
Expand Down Expand Up @@ -546,8 +558,16 @@ private void update() {
lines.addAll(logs);
remLogLines -= logs.size();
}
while (remLogLines-- > 0 && lines.size() <= maxThreads + 1) {
lines.add(AttributedString.EMPTY);
final AttributedString idleLine = new AttributedStringBuilder()
.style(BOLD_GREEN_FOREGROUND)
.append("> ")
.style(AttributedStyle.DEFAULT.faint())
.append("IDLE")
.style(AttributedStyle.DEFAULT)
.toAttributedString();
int idleSlots = maxThreads - projectsCount;
while (idleSlots-- > 0 && remLogLines-- > 0 && lines.size() <= maxThreads + 1) {
lines.add(idleLine);
}
} else {
int skipProjects = projectsCount - dispLines;
Expand Down Expand Up @@ -681,10 +701,41 @@ public static String pathToMaven(String location) {
return location;
}

static String renderBar(int percent) {
final int width = 20;
int filled = (int) Math.round(percent / 100.0 * width);
StringBuilder sb = new StringBuilder(width + 2);
sb.append('[');
if (filled >= width) {
for (int i = 0; i < width; i++) {
sb.append('=');
}
} else if (filled > 0) {
for (int i = 0; i < filled - 1; i++) {
sb.append('=');
}
sb.append('>');
for (int i = 0; i < width - filled; i++) {
sb.append(' ');
}
} else {
for (int i = 0; i < width; i++) {
sb.append(' ');
}
}
sb.append(']');
return sb.toString();
}

private void addStatusLine(final List<AttributedString> lines, int dispLines, final int projectsCount) {
if (name != null || buildStatus != null) {
AttributedStringBuilder asb = new AttributedStringBuilder();
if (name != null) {
int percent = doneProjects * 100 / totalProjects;
asb.append(renderBar(percent))
.append(' ')
.append(String.format("%3d", percent))
.append("% ");
asb.append("Building ");
asb.style(AttributedStyle.BOLD);
asb.append(name);
Expand Down Expand Up @@ -714,9 +765,6 @@ private void addStatusLine(final List<AttributedString> lines, int dispLines, fi
.append(String.format(projectsDoneFomat, doneProjects))
.append('/')
.append(String.valueOf(totalProjects))
.append(' ')
.append(String.format("%3d", doneProjects * 100 / totalProjects))
.append('%')
.style(AttributedStyle.DEFAULT);

} else {
Expand All @@ -737,18 +785,19 @@ private void addStatusLine(final List<AttributedString> lines, int dispLines, fi
private void addProjectLine(final List<AttributedString> lines, Project prj) {
final MojoStartedEvent execution = prj.runningExecution;
final AttributedStringBuilder asb = new AttributedStringBuilder();
asb.style(BOLD_GREEN_FOREGROUND).append("> ").style(AttributedStyle.DEFAULT);
AttributedString transfer = formatTransfers(prj.id);
if (transfer != null) {
asb.append(':')
.style(CYAN_FOREGROUND)
asb.style(CYAN_FOREGROUND)
.append(':')
.append(String.format(artifactIdFormat, prj.id))
.style(AttributedStyle.DEFAULT)
.append(transfer);
} else if (execution == null) {
asb.append(':').style(CYAN_FOREGROUND).append(prj.id);
asb.style(CYAN_FOREGROUND).append(':').append(prj.id).style(AttributedStyle.DEFAULT);
} else {
asb.append(':')
.style(CYAN_FOREGROUND)
asb.style(CYAN_FOREGROUND)
.append(':')
.append(String.format(artifactIdFormat, prj.id))
.style(GREEN_FOREGROUND);
if (execution.getPluginGoalPrefix().isEmpty()) {
Expand All @@ -765,10 +814,39 @@ private void addProjectLine(final List<AttributedString> lines, Project prj) {
.append('(')
.append(execution.getExecutionId())
.append(')');
final Message.ProjectTestProgressEvent tp = prj.testProgress;
if (tp != null) {
appendTestProgress(asb, tp);
}
}
lines.add(asb.toAttributedString());
}

static void appendTestProgress(AttributedStringBuilder asb, Message.ProjectTestProgressEvent tp) {
final AttributedStyle faint = AttributedStyle.DEFAULT.faint();
final AttributedStyle red = AttributedStyle.DEFAULT.foreground(AttributedStyle.RED);
asb.append(' ').style(faint).append("[Tests: ").append(String.valueOf(tp.getCompleted()));
if (tp.getFailures() > 0) {
asb.style(faint).append(", Failures: ").style(red).append(String.valueOf(tp.getFailures()));
}
if (tp.getErrors() > 0) {
asb.style(faint).append(", Errors: ").style(red).append(String.valueOf(tp.getErrors()));
}
if (tp.getSkipped() > 0) {
asb.style(faint).append(", Skipped: ").append(String.valueOf(tp.getSkipped()));
}
asb.style(faint).append("]");
final String testClass = tp.getTestClass();
if (testClass != null) {
final String simple = testClass.substring(testClass.lastIndexOf('.') + 1);
asb.append(' ').append(simple);
if (tp.getTestMethod() != null) {
asb.append('#').append(tp.getTestMethod());
}
}
asb.style(AttributedStyle.DEFAULT);
}

private static <T> List<T> lastN(List<T> list, int n) {
return list.subList(Math.max(0, list.size() - n), list.size());
}
Expand Down
40 changes: 40 additions & 0 deletions common/src/test/java/org/mvndaemon/mvnd/common/MessageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,44 @@ void buildExceptionSerialization() throws Exception {
assertTrue(msg2 instanceof Message.BuildException);
assertNull(((Message.BuildException) msg2).getMessage());
}

@Test
void projectTestProgressSerialization() throws IOException {
Message msg = Message.projectTestProgress("my-app", "com.acme.FooTest", "shouldWork", 3, 1, 0, 1);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (DataOutputStream daos = new DataOutputStream(baos)) {
msg.write(daos);
}
Message msg2;
try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()))) {
msg2 = Message.read(dis);
}

assertTrue(msg2 instanceof Message.ProjectTestProgressEvent);
Message.ProjectTestProgressEvent e = (Message.ProjectTestProgressEvent) msg2;
assertEquals("my-app", e.getProjectId());
assertEquals("com.acme.FooTest", e.getTestClass());
assertEquals("shouldWork", e.getTestMethod());
assertEquals(3, e.getCompleted());
assertEquals(1, e.getFailures());
assertEquals(0, e.getErrors());
assertEquals(1, e.getSkipped());
}

@Test
void projectTestProgressNullClassAndMethod() throws IOException {
Message msg = Message.projectTestProgress("my-app", null, null, 0, 0, 0, 0);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (DataOutputStream daos = new DataOutputStream(baos)) {
msg.write(daos);
}
Message msg2;
try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()))) {
msg2 = Message.read(dis);
}
Message.ProjectTestProgressEvent e = (Message.ProjectTestProgressEvent) msg2;
assertNull(e.getTestClass());
assertNull(e.getTestMethod());
}
}
Loading
Loading