Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow running tests with same fully-qualified name multiple times #96 #97

Merged
merged 10 commits into from
May 23, 2021
Merged
34 changes: 25 additions & 9 deletions src/main/java/com/novocode/junit/EventDispatcher.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.novocode.junit;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -20,8 +19,8 @@
final class EventDispatcher extends RunListener
{
private final RichLogger logger;
private final Set<String> reported = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
private final ConcurrentHashMap<String, Long> startTimes = new ConcurrentHashMap<String, Long>();
private final Set<String> reported = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final ConcurrentHashMap<String, Long> startTimes = new ConcurrentHashMap<>();
private final EventHandler handler;
private final RunSettings settings;
private final Fingerprint fingerprint;
Expand Down Expand Up @@ -100,7 +99,7 @@ void logTo(RichLogger logger) {
@Override
public void testIgnored(Description desc)
{
postIfFirst(new InfoEvent(desc, Status.Skipped) {
postIfFirst(new InfoEvent(desc, Status.Ignored) {
void logTo(RichLogger logger) {
logger.info("Test "+ansiName+" ignored");
}
Expand All @@ -123,7 +122,7 @@ private void recordStartTime(Description description) {
private Long elapsedTime(Description description) {
Long startTime = startTimes.get(settings.buildPlainName(description));
if( startTime == null ) {
return 0l;
return 0L;
} else {
return System.currentTimeMillis() - startTime;
}
Expand All @@ -136,7 +135,7 @@ public void testRunFinished(Result result)
c(result.getFailureCount()+" failed", result.getFailureCount() > 0 ? ERRCOUNT : INFO)+
c(", ", INFO)+
c(result.getIgnoreCount()+" ignored", result.getIgnoreCount() > 0 ? IGNCOUNT : INFO)+
c(", "+result.getRunCount()+" total, "+(result.getRunTime()/1000.0)+"s", INFO), RunSettings.Verbosity.RUN_FINISHED);
c(", "+result.getRunCount()+" total, "+ result.getRunTime() / 1000.0 +"s", INFO), RunSettings.Verbosity.RUN_FINISHED);
runStatistics.addTime(result.getRunTime());
}

Expand All @@ -148,7 +147,7 @@ public void testRunStarted(Description description)

void testExecutionFailed(String testName, Throwable err)
{
post(new Event(Ansi.c(testName, Ansi.ERRMSG), settings.buildErrorMessage(err), Status.Error, 0l, err) {
post(new Event(Ansi.c(testName, Ansi.ERRMSG), settings.buildErrorMessage(err), Status.Error, 0L, err) {
void logTo(RichLogger logger) {
logger.error("Execution of test "+ansiName+" failed: "+ansiMsg, error);
}
Expand All @@ -158,13 +157,30 @@ void logTo(RichLogger logger) {
private void postIfFirst(AbstractEvent e)
{
e.logTo(logger);
if(reported.add(e.fullyQualifiedName())) {

String fqn = e.fullyQualifiedName();
if (reported.add(fqn)) {
runStatistics.captureStats(e);
handler.handle(e);
}

// NOTE: Status.Success is used to indicate that test is finished with any result (Success or Failure)
// When test has failed, two events are actually generated:
// 1) with Status.Failure
// 2) with Status.Success (actually meaning that test has finished)
// For non-failed tests, single event is emitted: Status.Success OR Status.Skipped OR Status.Ignored
boolean testProcessed = e.status == Status.Success || e.status == Status.Skipped || e.status == Status.Ignored;
if (testProcessed) {
// JUnit can run tests with the same name multiple times: https://github.com/sbt/junit-interface/issues/96
// Once test is finished, we mark it as unreported to allow running it again.
//
// There should be no issues with running tests in parallel (default behaviour of SBT)
// For each JUnitTask a dedicated EventDispatcher will be created with it's own `reported` map
reported.remove(fqn);
}
}

void post(AbstractEvent e)
private void post(AbstractEvent e)
{
e.logTo(logger);
runStatistics.captureStats(e);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/novocode/junit/GlobFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

public final class GlobFilter extends Filter
{
private final ArrayList<Pattern> patterns = new ArrayList<Pattern>();
private final ArrayList<Pattern> patterns = new ArrayList<>();
private final RunSettings settings;

public GlobFilter(RunSettings settings, Iterable<String> globPatterns)
Expand Down
20 changes: 11 additions & 9 deletions src/main/java/com/novocode/junit/JUnitRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import sbt.testing.TaskDef;

import java.util.*;

import java.util.stream.Collectors;

final class JUnitRunner implements Runner {
private final String[] args;
Expand All @@ -16,7 +16,7 @@ final class JUnitRunner implements Runner {
private volatile boolean used = false;

final ClassLoader testClassLoader;
final RunListener runListener;
final List<RunListener> runListeners;
final RunStatistics runStatistics;

JUnitRunner(String[] args, String[] remoteArgs, ClassLoader testClassLoader) {
Expand All @@ -28,14 +28,14 @@ final class JUnitRunner implements Runner {
logAssert = true, logExceptionClass = true;
RunSettings.Verbosity verbosity = RunSettings.Verbosity.TERSE;
RunSettings.Summary summary = RunSettings.Summary.SBT;
HashMap<String, String> sysprops = new HashMap<String, String>();
ArrayList<String> globPatterns = new ArrayList<String>();
Set<String> includeCategories = new HashSet<String>();
Set<String> excludeCategories = new HashSet<String>();
HashMap<String, String> sysprops = new HashMap<>();
ArrayList<String> globPatterns = new ArrayList<>();
Set<String> includeCategories = new HashSet<>();
Set<String> excludeCategories = new HashSet<>();

String testFilter = "";
String ignoreRunners = "org.junit.runners.Suite";
String runListener = null;
final List<String> runListeners = new ArrayList<>();
for(String s : args) {
if("-q".equals(s)) quiet = true;
else if("-v".equals(s)) verbosity = RunSettings.Verbosity.STARTED;
Expand All @@ -48,7 +48,7 @@ final class JUnitRunner implements Runner {
else if("-c".equals(s)) logExceptionClass = false;
else if(s.startsWith("--tests=")) testFilter = s.substring(8);
else if(s.startsWith("--ignore-runners=")) ignoreRunners = s.substring(17);
else if(s.startsWith("--run-listener=")) runListener = s.substring(15);
else if(s.startsWith("--run-listener=")) runListeners.add(s.substring(15));
else if(s.startsWith("--include-categories=")) includeCategories.addAll(Arrays.asList(s.substring(21).split(",")));
else if(s.startsWith("--exclude-categories=")) excludeCategories.addAll(Arrays.asList(s.substring(21).split(",")));
else if(s.startsWith("-D") && s.contains("=")) {
Expand All @@ -68,7 +68,9 @@ else if(s.startsWith("-D") && s.contains("=")) {
new RunSettings(!nocolor, decodeScalaNames, quiet, verbosity, summary, logAssert, ignoreRunners, logExceptionClass,
sysprops, globPatterns, includeCategories, excludeCategories,
testFilter);
this.runListener = createRunListener(runListener);
this.runListeners = runListeners.stream()
.map(this::createRunListener)
.collect(Collectors.toList());
this.runStatistics = new RunStatistics(settings);
}

Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/novocode/junit/JUnitTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public Task[] execute(EventHandler eventHandler, Logger[] loggers) {
EventDispatcher ed = new EventDispatcher(logger, eventHandler, settings, fingerprint, taskDescription, runner.runStatistics);
JUnitCore ju = new JUnitCore();
ju.addListener(ed);
if (runner.runListener != null) ju.addListener(runner.runListener);

runner.runListeners.forEach(ju::addListener);

Map<String, Object> oldprops = settings.overrideSystemProperties();
try {
Expand Down Expand Up @@ -91,7 +92,7 @@ private boolean shouldRun(Fingerprint fingerprint, Class<?> clazz, RunSettings s
}

private static Set<Class<?>> loadClasses(ClassLoader classLoader, Set<String> classNames) throws ClassNotFoundException {
Set<Class<?>> classes = new HashSet<Class<?>>();
Set<Class<?>> classes = new HashSet<>();
for(String className : classNames) {
classes.add(classLoader.loadClass(className));
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/novocode/junit/RichLogger.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ final class RichLogger
private final Logger[] loggers;
private final RunSettings settings;
/* The top element is the test class of the currently executing test */
private final Stack<String> currentTestClassName = new Stack<String>();
private final Stack<String> currentTestClassName = new Stack<>();

RichLogger(Logger[] loggers, RunSettings settings, String testClassName)
{
Expand Down Expand Up @@ -132,7 +132,7 @@ private String stackTraceElementToString(StackTraceElement e, String testClassNa
{
boolean highlight = settings.color && (
testClassName.equals(e.getClassName()) ||
(testFileName != null && testFileName.equals(e.getFileName()))
testFileName != null && testFileName.equals(e.getFileName())
);
StringBuilder b = new StringBuilder();
b.append(settings.decodeName(e.getClassName() + '.' + e.getMethodName()));
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/com/novocode/junit/RunSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class RunSettings {

private final boolean decodeScalaNames;
private final HashMap<String, String> sysprops;
private final HashSet<String> ignoreRunners = new HashSet<String>();
private final HashSet<String> ignoreRunners = new HashSet<>();

RunSettings(boolean color, boolean decodeScalaNames, boolean quiet,
Verbosity verbosity, Summary summary, boolean logAssert, String ignoreRunners,
Expand Down Expand Up @@ -81,15 +81,15 @@ String buildPlainName(Description desc) {

String buildColoredMessage(Throwable t, String c1) {
if(t == null) return "null";
if(!logExceptionClass || (!logAssert && (t instanceof AssertionError))) return t.getMessage();
if(!logExceptionClass || !logAssert && t instanceof AssertionError) return t.getMessage();
StringBuilder b = new StringBuilder();

String cn = decodeName(t.getClass().getName());
int pos1 = cn.indexOf('$');
int pos2 = pos1 == -1 ? cn.lastIndexOf('.') : cn.lastIndexOf('.', pos1);
if(pos2 == -1) b.append(c(cn, c1));
else {
b.append(cn.substring(0, pos2));
b.append(cn, 0, pos2);
b.append('.');
b.append(c(cn.substring(pos2+1), c1));
}
Expand All @@ -114,7 +114,7 @@ private String buildColoredName(Description desc, String c1, String c2, String c
int pos2 = pos1 == -1 ? cn.lastIndexOf('.') : cn.lastIndexOf('.', pos1);
if(pos2 == -1) b.append(c(cn, c1));
else {
b.append(cn.substring(0, pos2));
b.append(cn, 0, pos2);
b.append('.');
b.append(c(cn.substring(pos2+1), c1));
}
Expand All @@ -139,7 +139,7 @@ private String buildColoredName(Description desc, String c1, String c2, String c
boolean ignoreRunner(String cln) { return ignoreRunners.contains(cln); }

Map<String, Object> overrideSystemProperties() {
HashMap<String, Object> oldprops = new HashMap<String, Object>();
HashMap<String, Object> oldprops = new HashMap<>();
synchronized(System.getProperties()) {
for(Map.Entry<String, String> me : sysprops.entrySet()) {
String old = System.getProperty(me.getKey());
Expand All @@ -164,11 +164,11 @@ void restoreSystemProperties(Map<String, Object> oldprops) {
}
}

static enum Verbosity {
enum Verbosity {
TERSE, RUN_FINISHED, STARTED, TEST_FINISHED
}

static enum Summary {
enum Summary {
SBT, ONE_LINE, LIST_FAILED
}
}
6 changes: 2 additions & 4 deletions src/main/java/com/novocode/junit/RunStatistics.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class RunStatistics {

private int failedCount, ignoredCount, otherCount;
private final ArrayList<String> failedNames = new ArrayList<>();
private final ArrayList<String> otherNames = new ArrayList<>();
private volatile long accumulatedTime;

RunStatistics(RunSettings settings) {
Expand All @@ -28,14 +27,13 @@ synchronized void captureStats(AbstractEvent e) {
else {
if(s == Status.Ignored) ignoredCount++;
else otherCount++;
otherNames.add(e.fullyQualifiedName());
}
}

private String summaryLine() {
return (failedCount == 0 ? "All tests passed: " : "Some tests failed: ") +
failedCount+" failed, "+ignoredCount+" ignored, "+(failedCount+ignoredCount+otherCount)+" total, "+
(accumulatedTime/1000.0)+"s";
accumulatedTime / 1000.0 +"s";
}

private static String mkString(List<String> l) {
Expand All @@ -52,7 +50,7 @@ synchronized String createSummary() {
case LIST_FAILED:
return failedNames.isEmpty() ?
summaryLine() :
(summaryLine() + "\n- Failed tests: " + mkString(failedNames));
summaryLine() + "\n- Failed tests: " + mkString(failedNames);
case ONE_LINE:
return summaryLine();
default:
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/novocode/junit/TestFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

public final class TestFilter extends Filter
{
private static final String DELIMITER = "\\,";
private static final String DELIMITER = ",";

private final HashSet<String> ignored = new HashSet<String>();
private final HashSet<String> ignored = new HashSet<>();
private final String[] testPatterns;
private final EventDispatcher ed;

Expand Down
47 changes: 47 additions & 0 deletions src/sbt-test/simple/test-listener-multiple/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name := "test-project"

scalaVersion := "2.10.7"

libraryDependencies += "com.novocode" % "junit-interface" % sys.props("plugin.version") % "test"

testOptions += Tests.Argument(
TestFrameworks.JUnit,
"-v", "-n",
"--run-listener=test.JUnitListener1",
"--run-listener=test.JUnitListener2"
)

val listenerFile = settingKey[File]("location of the listener output")

listenerFile := target.value / "listener.txt"

javaOptions in Test += "-Djunit.output.file=" + listenerFile.value.getAbsolutePath

fork in Test := true

val checkRunListenerFile = taskKey[Unit]("Tests that the file is correct")

checkRunListenerFile := {
val expectedContent =
"""testStarted_1 testFail(TestFoo)
|testStarted_2 testFail(TestFoo)
|testFailure_1 testFail(TestFoo)
|testFailure_2 testFail(TestFoo)
|testFinished_1 testFail(TestFoo)
|testFinished_2 testFail(TestFoo)
|testStarted_1 testPass(TestFoo)
|testStarted_2 testPass(TestFoo)
|testFinished_1 testPass(TestFoo)
|testFinished_2 testPass(TestFoo)
|testRunFinished_1
|testRunFinished_2""".stripMargin.replace("\r", "")

val actualContent = sbt.IO.readLines(listenerFile.value).mkString("\n")
assert(
expectedContent == actualContent,
s"""Expecting content:
|$expectedContent
|Actual content:
|$actualContent""".stripMargin
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test;

public class JUnitListener1 extends JUnitListenerBase {
public JUnitListener1() {
super("1");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test;

public class JUnitListener2 extends JUnitListenerBase {
public JUnitListener2() {
super("2");
}
}
Loading