Skip to content

Commit

Permalink
Merge pull request #1933 from usethesource/progress-bar
Browse files Browse the repository at this point in the history
ANSI enabled terminal progress bar that implements IRascalMonitor. Thread safe-ish"
  • Loading branch information
jurgenvinju authored Apr 7, 2024
2 parents 18a80e7 + 10d31b5 commit 0687e40
Show file tree
Hide file tree
Showing 39 changed files with 1,514 additions and 523 deletions.
2 changes: 1 addition & 1 deletion src/org/rascalmpl/checker/StaticChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class StaticChecker {
public StaticChecker(OutputStream stderr, OutputStream stdout) {
GlobalEnvironment heap = new GlobalEnvironment();
ModuleEnvironment root = heap.addModule(new ModuleEnvironment("$staticchecker$", heap));
eval = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, stderr, stdout, root, heap);
eval = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, stderr, stdout, root, heap, IRascalMonitor.buildConsoleMonitor(System.in, System.out));
eval.addRascalSearchPathContributor(StandardLibraryContributor.getInstance());
checkerEnabled = false;
initialized = false;
Expand Down
102 changes: 99 additions & 3 deletions src/org/rascalmpl/debug/IRascalMonitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,21 @@
*******************************************************************************/
package org.rascalmpl.debug;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import org.rascalmpl.interpreter.ConsoleRascalMonitor;
import org.rascalmpl.interpreter.NullRascalMonitor;
import org.rascalmpl.repl.IsTTY;
import org.rascalmpl.repl.TerminalProgressBarMonitor;

import io.usethesource.vallang.ISourceLocation;
import jline.TerminalFactory;

public interface IRascalMonitor {
/**
Expand All @@ -31,11 +43,66 @@ default void jobStart(String name, int totalWork) {
jobStart(name, 1, totalWork);
}

default void job(String name, Supplier<Boolean> block) {
/**
* This utility method is not to be implemented by clients. It's a convenience
* function that helps to guarantee jobs that are started, are always ended.
*/
default <T> T job(String name, int totalWork, Supplier<T> block) {
boolean result = false;
try {
jobStart(name);
result = block.get();
jobStart(name, totalWork);
return block.get();
}
finally {
jobEnd(name, result);
}
}

/**
* This utility method is not to be implemented by clients. It's a convenience
* function that helps to guarantee jobs that are started, are always ended.
* Also it provides easy access to the name of the current job, such that
* this "magic" constant does not need to be repeated or stored elsewhere.
*/
default <T> T job(String name, int totalWork, Function<String, T> block) {
boolean result = false;
try {
jobStart(name, totalWork);
return block.apply(name);
}
finally {
jobEnd(name, result);
}
}

/**
* This utility method is not to be implemented by clients. It's a convenience
* function that helps to guarantee jobs that are started, are always ended.
* Also it provides easy access to the name of the current job, such that
* this "magic" constant does not need to be repeated or stored elsewhere.
* @param <T> return type for the entire job
* @param name name of the job to identify the progress bar
* @param totalWork total work to be done
* @param block lambda to execute. It will get the name as a parameter
* and a `step` function to call with the current message and
* the amount of work that has been done.
*
* Example:
* ```
* job("loading", 100, (n, step) -> {
* for (int i = 0; i < 100; i+= 10)
* doSomething()
* step("did " + i, 10);
* }
* });
*
* @return whatever the block returns is returned by the job
*/
default <T> T job(String name, int totalWork, BiFunction<String, BiConsumer<String, Integer>, T> block) {
boolean result = false;
try {
jobStart(name, totalWork);
return block.apply(name, (msg, worked) -> jobStep(name, msg, worked));
}
finally {
jobEnd(name, result);
Expand All @@ -45,6 +112,10 @@ default void job(String name, Supplier<Boolean> block) {
/**
* Log the start of an event with the amount of work that will be done when it's finished.
* An event is finished when the next event is logged, or when endJob() is called.
*
* jobSteps should be _ignored_ for jobs that have not started. This helps with modularizing
* the monitoring of steps acros complex reusable pieces algorithms. If the context registers
* a job, progress is monitored, otherwise it is not shown.
*/
public void jobStep(String name, String message, int workShare);

Expand Down Expand Up @@ -72,8 +143,33 @@ default void jobStep(String name, String message) {
*/
public void jobTodo(String name, int work);

/**
* Remove all active jobs from the monitor
*/
public void endAllJobs();

/**
* Inform (about a warning
*/
public void warning(String message, ISourceLocation src);

/**
* Convenience method will produce a monitor with ANSI progress bars if possible,
* and otherwise default to a dumn terminal console progress logger.
* @return
*/
public static IRascalMonitor buildConsoleMonitor(InputStream in, OutputStream out) {
return IsTTY.isTTY()
? new TerminalProgressBarMonitor(out, in, TerminalFactory.get())
: new ConsoleRascalMonitor(new PrintStream(out))
;
}

/**
* Convenience method will produce a monitor that eats up all events without logging
* or reporting
*/
public default IRascalMonitor buildNullMonitor() {
return new NullRascalMonitor();
}
}
20 changes: 11 additions & 9 deletions src/org/rascalmpl/ideservices/BasicIDEServices.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;

import org.rascalmpl.interpreter.ConsoleRascalMonitor;
import org.rascalmpl.debug.IRascalMonitor;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.uri.URIUtil;

Expand All @@ -32,15 +32,14 @@
*
*/
public class BasicIDEServices implements IDEServices {

private static ConsoleRascalMonitor monitor = new ConsoleRascalMonitor();
private PrintWriter stderr;
private final IRascalMonitor monitor;
private final PrintWriter stderr;

public BasicIDEServices(PrintWriter stderr){
public BasicIDEServices(PrintWriter stderr, IRascalMonitor monitor){
this.stderr = stderr;
monitor = new ConsoleRascalMonitor();
this.monitor = monitor;
}

@Override
public PrintWriter stderr() {
return stderr;
Expand Down Expand Up @@ -119,6 +118,11 @@ public int jobEnd(String name, boolean succeeded) {
return monitor.jobEnd(name, succeeded);
}

@Override
public void endAllJobs() {
monitor.endAllJobs();
}

@Override
public boolean jobIsCanceled(String name) {
return monitor.jobIsCanceled(name);
Expand All @@ -133,6 +137,4 @@ public void jobTodo(String name, int work) {
public void warning(String message, ISourceLocation src) {
monitor.warning(message, src);
}


}
5 changes: 5 additions & 0 deletions src/org/rascalmpl/interpreter/ConsoleRascalMonitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ public void warning(String message, ISourceLocation src) {
out.println("Warning: " + message);
out.flush();
}

@Override
public void endAllJobs() {
// ignore
}
}
63 changes: 22 additions & 41 deletions src/org/rascalmpl/interpreter/DefaultTestResultListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,24 @@

import io.usethesource.vallang.ISourceLocation;

public class DefaultTestResultListener implements ITestResultListener{
private PrintWriter err;
public class DefaultTestResultListener implements ITestResultListener {
private int successes;
private int failures;
private int errors;
private int count;
private int ignored;
private String context;
private final boolean verbose;
private static char[] roller = new char[] {'|', '/', '-', '\\', '|', '/', '-', '\\', '|'};
private PrintWriter out;

public DefaultTestResultListener(PrintWriter errorStream){
this(errorStream, true);
public DefaultTestResultListener(PrintWriter out) {
this(out, true);
}
public DefaultTestResultListener(PrintWriter errorStream, boolean verbose){

public DefaultTestResultListener(PrintWriter out, boolean verbose){
super();

this.err = errorStream;
this.out = out;
this.verbose = verbose;
reset();
}
Expand All @@ -49,7 +49,7 @@ private void reset() {
}

public void setErrorStream(PrintWriter errorStream) {
this.err = errorStream;
this.out = errorStream;
}

@Override
Expand All @@ -61,75 +61,56 @@ public void ignored(String test, ISourceLocation loc) {
public void start(String context, int count) {
this.context = context;
reset();
if (count != 0 && verbose) {
err.println("Running tests for " + context);
}
this.count = count;
progress();
}

private void progress() {
if (count > 0 && verbose) {
err.print(String.format("%s testing %d/%d ",
roller[getNumberOfTests() % roller.length], getNumberOfTests(), count));
}
}

@Override
public void done() {
progress();
if (count > 0) {
if (!verbose) {
// make sure results are reported on a newline
err.println();
out.println();
}
err.println("\rTest report for " + context);
out.println("\rTest report for " + context);
if (errors + failures == 0) {
err.println("\tall " + (count - ignored) + "/" + count + " tests succeeded");
out.println("\tall " + (count - ignored) + "/" + count + " tests succeeded");
}
else {
err.println("\t" + successes + "/" + count + " tests succeeded");
err.println("\t" + failures + "/" + count + " tests failed");
err.println("\t" + errors + "/" + count + " tests threw exceptions");
out.println("\t" + successes + "/" + count + " tests succeeded");
out.println("\t" + failures + "/" + count + " tests failed");
out.println("\t" + errors + "/" + count + " tests threw exceptions");
}

if (ignored != 0) {
err.println("\t" + ignored + "/" + count + " tests ignored");
out.println("\t" + ignored + "/" + count + " tests ignored");
}
}
}


@Override
public void report(boolean successful, String test, ISourceLocation loc, String message, Throwable t) {
progress();

if (successful) {
successes++;
if (verbose) {
err.print("success \r");
}
}
else if (t != null) {
errors++;
if (!verbose) {
err.println();
out.println();
}
err.println("error: " + test + " @ " + ReplTextWriter.valueToString(loc));
err.println(message);
out.println("error: " + test + " @ " + ReplTextWriter.valueToString(loc));
out.println(message);
}
else {
failures++;
if (!verbose) {
err.println();
out.println();
}
err.println("failure: " + test + " @ " + ReplTextWriter.valueToString(loc));
err.println(message);
out.println("failure: " + test + " @ " + ReplTextWriter.valueToString(loc));
out.println(message);
}



err.flush();
out.flush();
}


Expand Down
Loading

0 comments on commit 0687e40

Please sign in to comment.