// Copyright © 2011-2013, Esko Luontola <www.orfjackal.net> // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 package fi.jumi.core.junit; import fi.jumi.api.drivers.*; import org.junit.runner.*; import org.junit.runner.notification.*; import javax.annotation.concurrent.NotThreadSafe; import java.util.*; import java.util.regex.Pattern; @NotThreadSafe public class JUnitRunListenerAdapter extends RunListener { private static final Pattern JAVA_CLASS_NAME_PATTERN = Pattern.compile("[\\._$\\p{Alnum}]+"); private final SuiteNotifier notifier; private final Deque<TestNotifier> activeTestsStack = new ArrayDeque<>(); private final Map<Description, TestId> descriptionIds = new HashMap<>(); private Description rootDescription; public JUnitRunListenerAdapter(SuiteNotifier notifier) { this.notifier = notifier; } @Override public void testRunStarted(Description description) { rootDescription = description; fireTestFound(TestId.ROOT, description); } private void fireTestFound(TestId testId, Description description) { TestId previousValue = descriptionIds.put(description, testId); if (previousValue == null) { notifier.fireTestFound(testId, formatTestName(description)); } TestId childId = testId.getFirstChild(); for (Description child : description.getChildren()) { fireTestFound(childId, child); childId = childId.getNextSibling(); } } private static String formatTestName(Description description) { String methodName = description.getMethodName(); if (methodName != null) { return methodName; } String className = description.getClassName(); if (isJavaClassName(className)) { return simpleClassName(className); } // not a class name, but actually free-form text return className; } private static boolean isJavaClassName(String s) { return JAVA_CLASS_NAME_PATTERN.matcher(s).matches(); } private static String simpleClassName(String name) { name = name.substring(name.lastIndexOf('.') + 1); name = name.substring(name.lastIndexOf('$') + 1); return name; } @Override public void testRunFinished(Result result) { } @Override public void testStarted(Description description) { TestId id = descriptionIds.get(description); if (id == null) { // Hoping that the runner added this description as a child to the top-level description fireTestFound(TestId.ROOT, rootDescription); id = descriptionIds.get(description); } if (id == null) { // Fallback if we have no way of knowing this description's parent id = nextUnassignedChildOf(TestId.ROOT); fireTestFound(id, description); } startTestAndItsParents(id); } private TestId nextUnassignedChildOf(TestId parent) { Set<TestId> assignedIds = new HashSet<>(descriptionIds.values()); TestId id = parent.getFirstChild(); while (assignedIds.contains(id)) { id = id.getNextSibling(); } return id; } private void startTestAndItsParents(TestId testId) { if (!testId.isRoot()) { startTestAndItsParents(testId.getParent()); } TestNotifier tn = notifier.fireTestStarted(testId); activeTestsStack.push(tn); } @Override public void testFinished(Description description) { finishAllTests(); } private void finishAllTests() { while (!activeTestsStack.isEmpty()) { TestNotifier tn = activeTestsStack.pop(); tn.fireTestFinished(); } } @Override public void testFailure(Failure failure) { TestNotifier tn = activeTestsStack.peek(); if (tn != null) { tn.fireFailure(failure.getException()); } } @Override public void testAssumptionFailure(Failure failure) { // TODO: implement ignoring tests into Jumi, then fire the appropriate event here failure.getException().printStackTrace(); } @Override public void testIgnored(Description description) { // TODO: implement ignoring tests into Jumi, then fire the appropriate event here } }