Skip to content

Commit 2018357

Browse files
szeigerbenmccann
authored andcommitted
Improved logging
- "Test run started/finished" messages include class name. - More useful verbosity settings with a new option to show only the (now more useful) "Test run finished" messages. - Optional summary with more details.
1 parent 2973839 commit 2018357

File tree

8 files changed

+124
-24
lines changed

8 files changed

+124
-24
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ The following options are supported for JUnit tests:
1616

1717
Option | Description
1818
:---------------------------------------------|:----------------------
19-
`-v` | Log "test run started" / "test started" / "test run finished" events on log level "info" instead of "debug".
19+
`-v` | Same as `--verbosity=2`
2020
`-q` | Suppress stdout for successful tests. Stderr is printed to the console normally. Stdout is written to a buffer and discarded when a test succeeds. If it fails, the buffer is dumped to the console. Since stdio redirection in Java is a bad kludge (`System.setOut()` changes the static final field System.out through native code) this may not work for all scenarios. Scala has its own console with a sane redirection feature. If Scala is detected on the class path, junit-interface tries to reroute scala.Console's stdout, too.
2121
`-n` | Do not use ANSI colors in the output even if sbt reports that they are supported.
2222
`-s` | Try to decode Scala names in stack traces and test names. Fall back silently to non-decoded names if no matching Scala library is on the class path.
2323
`-a` | Show stack traces and exception class name for AssertionErrors (thrown by all assert* methods in JUnit).`
2424
`-c` | Do not print the exception class name prefix for any messages. With this option, only the result of getMessage() plus a stack trace is shown.
25-
`+v` | Turn off `-v`. Takes precedence over `-v`.
25+
`+v` | Same as `--verbosity=0`
2626
`+q` | Turn off `-q`. Takes precedence over `-q`.
2727
`+n` | Turn off `-n`. Takes precedence over `-n`.
2828
`+s` | Turn off `-s`. Takes precedence over `-s`.
@@ -34,6 +34,8 @@ The following options are supported for JUnit tests:
3434
`--run-listener=<CLASS_NAME>` | A (user defined) class which extends `org.junit.runner.notification.RunListener`. An instance of this class is created and added to the JUnit Runner, so that it will receive the run events. For more information, see [RunListener](http://junit.org/javadoc/latest/org/junit/runner/notification/RunListener.html). *Note: this uses the test-classloader, so the class needs to be defined in `src/test` or `src/main` or included as a test or compile dependency*
3535
`--include-categories=<CLASSES>` | A comma separated list of category class names that should be included. Only tests with one or more of these categories will be run.
3636
`--exclude-categories=<CLASSES>` | A comma separated list of category class names that should be excluded. No tests that match one or more of these categories will be run.
37+
`--verbosity=<INT>` | Higher verbosity logs more events at level "info" instead of "debug". 0: Default; 1: "Test run finished" at info; 2: Also "test run started" and "test started" at info; 3: Also "test finished" at info.
38+
`--summary=<INT>` | The type of summary to show for a test task execution. 0: Leave to sbt (default); 1: One line; 2: Include list of failed tests
3739

3840
Any parameter not starting with `-` or `+` is treated as a glob pattern for matching tests. Unlike the patterns given directly to sbt's `test-only` command, the patterns given to junit-interface will match against the full test names (as displayed by junit-interface) of all atomic test cases, so you can match on test methods and parts of suites with custom runners.
3941

src/main/java/com/novocode/junit/EventDispatcher.java

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.novocode.junit;
22

3+
import java.util.ArrayList;
34
import java.util.Collections;
45
import java.util.Set;
56
import java.util.concurrent.ConcurrentHashMap;
@@ -23,17 +24,20 @@ final class EventDispatcher extends RunListener
2324
private final ConcurrentHashMap<String, Long> startTimes = new ConcurrentHashMap<String, Long>();
2425
private final EventHandler handler;
2526
private final RunSettings settings;
26-
private OutputCapture capture;
2727
private final Fingerprint fingerprint;
28-
private final Description taskDescription;
28+
private final String taskInfo;
29+
private final RunStatistics runStatistics;
30+
private OutputCapture capture;
2931

30-
EventDispatcher(RichLogger logger, EventHandler handler, RunSettings settings, Fingerprint fingerprint, Description taskDescription)
32+
EventDispatcher(RichLogger logger, EventHandler handler, RunSettings settings, Fingerprint fingerprint,
33+
Description taskDescription, RunStatistics runStatistics)
3134
{
3235
this.logger = logger;
3336
this.handler = handler;
3437
this.settings = settings;
3538
this.fingerprint = fingerprint;
36-
this.taskDescription = taskDescription;
39+
this.taskInfo = settings.buildInfoName(taskDescription);
40+
this.runStatistics = runStatistics;
3741
}
3842

3943
private abstract class Event extends AbstractEvent {
@@ -87,7 +91,7 @@ public void testFinished(Description desc)
8791
uncapture(false);
8892
postIfFirst(new InfoEvent(desc, Status.Success) {
8993
void logTo(RichLogger logger) {
90-
logger.debug("Test "+ansiName+" finished" + durationSuffix());
94+
debugOrInfo("Test "+ansiName+" finished" + durationSuffix(), RunSettings.Verbosity.TEST_FINISHED);
9195
}
9296
});
9397
logger.popCurrentTestClassName();
@@ -108,7 +112,7 @@ public void testStarted(Description description)
108112
{
109113
recordStartTime(description);
110114
logger.pushCurrentTestClassName(description.getClassName());
111-
debugOrInfo("Test " + settings.buildInfoName(description) + " started");
115+
debugOrInfo("Test " + settings.buildInfoName(description) + " started", RunSettings.Verbosity.STARTED);
112116
capture();
113117
}
114118

@@ -128,17 +132,18 @@ private Long elapsedTime(Description description) {
128132
@Override
129133
public void testRunFinished(Result result)
130134
{
131-
debugOrInfo(c("Test run finished: ", INFO)+
135+
debugOrInfo(c("Test run ", INFO)+taskInfo+c(" finished: ", INFO)+
132136
c(result.getFailureCount()+" failed", result.getFailureCount() > 0 ? ERRCOUNT : INFO)+
133137
c(", ", INFO)+
134138
c(result.getIgnoreCount()+" ignored", result.getIgnoreCount() > 0 ? IGNCOUNT : INFO)+
135-
c(", "+result.getRunCount()+" total, "+(result.getRunTime()/1000.0)+"s", INFO));
139+
c(", "+result.getRunCount()+" total, "+(result.getRunTime()/1000.0)+"s", INFO), RunSettings.Verbosity.RUN_FINISHED);
140+
runStatistics.addTime(result.getRunTime());
136141
}
137142

138143
@Override
139144
public void testRunStarted(Description description)
140145
{
141-
debugOrInfo(c("Test run started", INFO));
146+
debugOrInfo(c("Test run ", INFO)+taskInfo+c(" started", INFO), RunSettings.Verbosity.STARTED);
142147
}
143148

144149
void testExecutionFailed(String testName, Throwable err)
@@ -153,12 +158,16 @@ void logTo(RichLogger logger) {
153158
private void postIfFirst(AbstractEvent e)
154159
{
155160
e.logTo(logger);
156-
if(reported.add(e.fullyQualifiedName())) handler.handle(e);
161+
if(reported.add(e.fullyQualifiedName())) {
162+
runStatistics.captureStats(e);
163+
handler.handle(e);
164+
}
157165
}
158166

159167
void post(AbstractEvent e)
160168
{
161169
e.logTo(logger);
170+
runStatistics.captureStats(e);
162171
handler.handle(e);
163172
}
164173

@@ -182,9 +191,9 @@ void uncapture(boolean replay)
182191
}
183192
}
184193

185-
private void debugOrInfo(String msg)
194+
private void debugOrInfo(String msg, RunSettings.Verbosity atVerbosity)
186195
{
187-
if(settings.verbose) logger.info(msg);
196+
if(atVerbosity.ordinal() <= settings.verbosity.ordinal()) logger.info(msg);
188197
else logger.debug(msg);
189198
}
190199
}

src/main/java/com/novocode/junit/JUnitRunner.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@ final class JUnitRunner implements Runner {
1313
private final String[] remoteArgs;
1414
private final RunSettings settings;
1515

16+
private volatile boolean used = false;
17+
1618
final ClassLoader testClassLoader;
1719
final RunListener runListener;
20+
final RunStatistics runStatistics;
1821

1922
JUnitRunner(String[] args, String[] remoteArgs, ClassLoader testClassLoader) {
2023
this.args = args;
2124
this.remoteArgs = remoteArgs;
2225
this.testClassLoader = testClassLoader;
2326

24-
boolean quiet = false, verbose = true, nocolor = false, decodeScalaNames = false,
27+
boolean quiet = false, nocolor = false, decodeScalaNames = false,
2528
logAssert = true, logExceptionClass = true;
29+
RunSettings.Verbosity verbosity = RunSettings.Verbosity.TERSE;
30+
RunSettings.Summary summary = RunSettings.Summary.SBT;
2631
HashMap<String, String> sysprops = new HashMap<String, String>();
2732
ArrayList<String> globPatterns = new ArrayList<String>();
2833
Set<String> includeCategories = new HashSet<String>();
@@ -33,7 +38,10 @@ final class JUnitRunner implements Runner {
3338
String runListener = null;
3439
for(String s : args) {
3540
if("-q".equals(s)) quiet = true;
36-
else if("-v".equals(s)) verbose = true;
41+
else if("-v".equals(s)) verbosity = RunSettings.Verbosity.STARTED;
42+
else if("+v".equals(s)) verbosity = RunSettings.Verbosity.TERSE;
43+
else if(s.startsWith("--verbosity=")) verbosity = RunSettings.Verbosity.values()[Integer.parseInt(s.substring(12))];
44+
else if(s.startsWith("--summary=")) summary = RunSettings.Summary.values()[Integer.parseInt(s.substring(10))];
3745
else if("-n".equals(s)) nocolor = true;
3846
else if("-s".equals(s)) decodeScalaNames = true;
3947
else if("-a".equals(s)) logAssert = true;
@@ -51,21 +59,22 @@ else if(s.startsWith("-D") && s.contains("=")) {
5159
}
5260
for(String s : args) {
5361
if("+q".equals(s)) quiet = false;
54-
else if("+v".equals(s)) verbose = false;
5562
else if("+n".equals(s)) nocolor = false;
5663
else if("+s".equals(s)) decodeScalaNames = false;
5764
else if("+a".equals(s)) logAssert = false;
5865
else if("+c".equals(s)) logExceptionClass = true;
5966
}
6067
this.settings =
61-
new RunSettings(!nocolor, decodeScalaNames, quiet, verbose, logAssert, ignoreRunners, logExceptionClass,
68+
new RunSettings(!nocolor, decodeScalaNames, quiet, verbosity, summary, logAssert, ignoreRunners, logExceptionClass,
6269
sysprops, globPatterns, includeCategories, excludeCategories,
6370
testFilter);
6471
this.runListener = createRunListener(runListener);
72+
this.runStatistics = new RunStatistics(settings);
6573
}
6674

6775
@Override
6876
public Task[] tasks(TaskDef[] taskDefs) {
77+
used = true;
6978
int length = taskDefs.length;
7079
Task[] tasks = new Task[length];
7180
for (int i = 0; i < length; i++) {
@@ -87,7 +96,12 @@ private RunListener createRunListener(String runListenerClassName) {
8796

8897
@Override
8998
public String done() {
90-
return "";
99+
// Can't simply return the summary due to https://github.com/sbt/sbt/issues/3510
100+
if(!used) return "";
101+
String stats = runStatistics.createSummary();
102+
if(stats.isEmpty()) return stats;
103+
System.out.println(stats);
104+
return " ";
91105
}
92106

93107
@Override

src/main/java/com/novocode/junit/JUnitTask.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public Task[] execute(EventHandler eventHandler, Logger[] loggers) {
3838
String testClassName = taskDef.fullyQualifiedName();
3939
Description taskDescription = Description.createSuiteDescription(testClassName);
4040
RichLogger logger = new RichLogger(loggers, settings, testClassName);
41-
EventDispatcher ed = new EventDispatcher(logger, eventHandler, settings, fingerprint, taskDescription);
41+
EventDispatcher ed = new EventDispatcher(logger, eventHandler, settings, fingerprint, taskDescription, runner.runStatistics);
4242
JUnitCore ju = new JUnitCore();
4343
ju.addListener(ed);
4444
if (runner.runListener != null) ju.addListener(runner.runListener);

src/main/java/com/novocode/junit/RunSettings.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
class RunSettings {
1717
private static final Object NULL = new Object();
1818

19-
final boolean color, quiet, verbose, logAssert, logExceptionClass;
19+
final boolean color, quiet, logAssert, logExceptionClass;
20+
final Verbosity verbosity;
21+
final Summary summary;
2022
final ArrayList<String> globPatterns;
2123
final Set<String> includeCategories, excludeCategories;
2224
final String testFilter;
@@ -26,7 +28,7 @@ class RunSettings {
2628
private final HashSet<String> ignoreRunners = new HashSet<String>();
2729

2830
RunSettings(boolean color, boolean decodeScalaNames, boolean quiet,
29-
boolean verbose, boolean logAssert, String ignoreRunners,
31+
Verbosity verbosity, Summary summary, boolean logAssert, String ignoreRunners,
3032
boolean logExceptionClass,
3133
HashMap<String, String> sysprops,
3234
ArrayList<String> globPatterns,
@@ -35,7 +37,8 @@ class RunSettings {
3537
this.color = color;
3638
this.decodeScalaNames = decodeScalaNames;
3739
this.quiet = quiet;
38-
this.verbose = verbose;
40+
this.verbosity = verbosity;
41+
this.summary = summary;
3942
this.logAssert = logAssert;
4043
this.logExceptionClass = logExceptionClass;
4144
for(String s : ignoreRunners.split(","))
@@ -160,4 +163,12 @@ void restoreSystemProperties(Map<String, Object> oldprops) {
160163
}
161164
}
162165
}
166+
167+
static enum Verbosity {
168+
TERSE, RUN_FINISHED, STARTED, TEST_FINISHED
169+
}
170+
171+
static enum Summary {
172+
SBT, ONE_LINE, LIST_FAILED
173+
}
163174
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.novocode.junit;
2+
3+
import sbt.testing.Status;
4+
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
8+
class RunStatistics {
9+
private final RunSettings settings;
10+
11+
private int failedCount, ignoredCount, otherCount;
12+
private final ArrayList<String> failedNames = new ArrayList<>();
13+
private final ArrayList<String> otherNames = new ArrayList<>();
14+
private volatile long accumulatedTime;
15+
16+
RunStatistics(RunSettings settings) {
17+
this.settings = settings;
18+
}
19+
20+
void addTime(long t) { accumulatedTime += t; }
21+
22+
synchronized void captureStats(AbstractEvent e) {
23+
Status s = e.status();
24+
if(s == Status.Error || s == Status.Failure) {
25+
failedCount++;
26+
failedNames.add(e.fullyQualifiedName());
27+
}
28+
else {
29+
if(s == Status.Ignored) ignoredCount++;
30+
else otherCount++;
31+
otherNames.add(e.fullyQualifiedName());
32+
}
33+
}
34+
35+
private String summaryLine() {
36+
return (failedCount == 0 ? "All tests passed: " : "Some tests failed: ") +
37+
failedCount+" failed, "+ignoredCount+" ignored, "+(failedCount+ignoredCount+otherCount)+" total, "+
38+
(accumulatedTime/1000.0)+"s";
39+
}
40+
41+
private static String mkString(List<String> l) {
42+
StringBuilder b = new StringBuilder();
43+
for(String s : l) {
44+
if(b.length() != 0) b.append(", ");
45+
b.append(s);
46+
}
47+
return b.toString();
48+
}
49+
50+
synchronized String createSummary() {
51+
switch(settings.summary) {
52+
case LIST_FAILED:
53+
return failedNames.isEmpty() ?
54+
summaryLine() :
55+
(summaryLine() + "\n- Failed tests: " + mkString(failedNames));
56+
case ONE_LINE:
57+
return summaryLine();
58+
default:
59+
return "";
60+
}
61+
}
62+
}

src/sbt-test/simple/categories/test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
# Turn on fast and slow category
2424
> set testOptions := Seq(Tests.Argument(TestFrameworks.JUnit, Constants.IncludeFastAndSlow))
25-
> test
25+
> testOnly -- --summary=2
2626
> testsExecuted one-fast one-slow two-a two-b
2727
> testsNotExecuted one-none
2828

src/sbt-test/simple/tests-run-once/build.sbt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ scalaVersion := "2.10.2"
55
libraryDependencies += "com.novocode" % "junit-interface" % sys.props("project.version") % "test"
66

77
fork in Test := true
8+
9+
testOptions += Tests.Argument(TestFrameworks.JUnit, "--verbosity=1")

0 commit comments

Comments
 (0)