Skip to content
Open
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependency-reduced-pom.xml
.project
.classpath
.settings/
.factorypath

# IDEA
.idea
Expand All @@ -31,4 +32,4 @@ nb-configuration.xml
.cache/

# https://ge.apache.org
.mvn/.gradle-enterprise
.mvn/.gradle-enterprise
4 changes: 2 additions & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ If SDKMAN! supports your operating system, it is as easy as
$ sdk install mvnd
----

If you used the manual install in the past, please make sure that the settings in `~/.m2/mvnd.properties` still make
sense. With SDKMAN!, the `~/.m2/mvnd.properties` file is typically not needed at all, because both `JAVA_HOME` and
If you used the manual installation in the past, please make sure that the settings in `~/.m2/mvnd.properties` still
make sense. With SDKMAN!, the `~/.m2/mvnd.properties` file is typically not needed at all, because both `JAVA_HOME` and
`MVND_HOME` are managed by SDKMAN!.

=== Install using https://brew.sh/[Homebrew]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -235,6 +236,15 @@ public DefaultClient(DaemonParameters parameters) {
true);
}

/**
* The environment forwarded to the daemon in the {@link Message.BuildRequest}. Defaults to the
* client's own environment; overridable so tests can run hermetically (e.g. without inheriting
* an ambient {@code MAVEN_ARGS}).
*/
protected Map<String, String> buildRequestEnvironment() {
return System.getenv();
}

@Override
public ExecutionResult execute(ClientOutput output, List<String> argv) {
LOGGER.debug("Starting client");
Expand Down Expand Up @@ -359,7 +369,7 @@ public ExecutionResult execute(ClientOutput output, List<String> argv) {
args,
parameters.userDir().toString(),
parameters.multiModuleProjectDirectory().toString(),
System.getenv()));
buildRequestEnvironment()));

output.accept(Message.buildStatus(
"Connected to daemon " + daemon.getDaemon().getId() + ", scanning for projects..."));
Expand Down
111 changes: 109 additions & 2 deletions common/src/main/java/org/mvndaemon/mvnd/common/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ public abstract class Message {
public static final int PRINT_ERR = 26;
public static final int REQUEST_INPUT = 27;
public static final int INPUT_DATA = 28;
/**
* 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 = 29;

final int type;

Expand All @@ -89,6 +95,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 @@ -154,6 +162,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 @@ -615,6 +625,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 Expand Up @@ -993,8 +1100,8 @@ public String toString() {
+ repositoryUrl + '\'' + ", resourceName='"
+ resourceName + '\'' + ", contentLength="
+ contentLength + ", transferredBytes="
+ transferredBytes + ", exception='"
+ exception + '\'' + '}';
+ transferredBytes
+ (exception != null ? ", exception='" + exception + '\'' : "") + '}';
}

private String mnemonic() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,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 @@ -147,6 +149,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 @@ -279,6 +282,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 @@ -463,6 +467,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 @@ -626,8 +638,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 @@ -753,10 +773,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 @@ -788,9 +839,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 if (buildStatus != null) {
Expand All @@ -811,18 +859,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 @@ -839,10 +888,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
Loading
Loading