All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.valfirst.jbehave.junit.monitoring.JUnitScenarioReporter Maven / Gradle / Ivy

Go to download

Library designed to make JBehave stories & scenarios show up in the JUnit view in IDEs supportting custom test runners

There is a newer version: 2.3.2
Show newest version
package com.github.valfirst.jbehave.junit.monitoring;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.jbehave.core.configuration.Keywords;
import org.jbehave.core.failures.BeforeOrAfterFailed;
import org.jbehave.core.failures.FailingUponPendingStep;
import org.jbehave.core.failures.PassingUponPendingStep;
import org.jbehave.core.failures.PendingStepStrategy;
import org.jbehave.core.failures.UUIDExceptionWrapper;
import org.jbehave.core.model.Scenario;
import org.jbehave.core.model.Story;
import org.jbehave.core.reporters.NullStoryReporter;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;

public class JUnitScenarioReporter extends NullStoryReporter {
	private RunNotifier notifier;

	private final Description rootDescription;
	private final int totalTests;
	private final Keywords keywords;
	private final boolean notifyFinished;
	private PendingStepStrategy pendingStepStrategy = new PassingUponPendingStep();

	private ThreadLocal testState = new ThreadLocal() {
		@Override
		protected TestState initialValue() {
			return new TestState();
		}
	};

	private AtomicInteger testCounter = new AtomicInteger();

	public JUnitScenarioReporter(RunNotifier notifier, int totalTests,
			Description rootDescription, Keywords keywords,
			boolean notifyFinished) {
		this.totalTests = totalTests;
		this.rootDescription = rootDescription;
		this.notifier = notifier;
		this.keywords = keywords;
		this.notifyFinished = notifyFinished;
	}

	public JUnitScenarioReporter(RunNotifier notifier, int totalTests,
			Description rootDescription, Keywords keywords) {
		this(notifier, totalTests, rootDescription, keywords, true);
	}

	@Override
	public void beforeStory(Story story, boolean isGivenStory) {
		TestState testState = this.testState.get();
		if (isGivenStory) {
			if (testState.currentStep != null) {
				notifier.fireTestStarted(testState.currentStep);
			}
			testState.givenStoryLevel++;
		} else {
			if (testCounter.get() == 0) {
				notifier.fireTestRunStarted(rootDescription);
			}
			for (Description storyDescription : rootDescription.getChildren()) {
				if (storyDescription.isSuite()
						&& storyDescription.getDisplayName().equals(
								JUnitStringDecorator.getJunitSafeString(story.getName()))) {
					testState.currentStoryDescription = storyDescription;
					notifier.fireTestStarted(storyDescription);

					testState.scenarioDescriptions = storyDescription.getChildren().iterator();
					testState.moveToNextScenario();
					processBeforeStory();
					testState.currentStep = testState.currentStoryDescription;
				} else
				// Related to issue #28: When a story does not contain any
				// scenarios, isTest returns true, but getMethodName
				// still returns null, because it cannot be parsed by JUnit as a
				// method name.
				if (storyDescription.isTest()
						&& storyDescription.getMethodName() != null
						&& storyDescription.getMethodName().equals(
								story.getName())) {
					// Story BeforeStories or After Stories
					testState.currentStoryDescription = storyDescription;
					notifier.fireTestStarted(testState.currentStoryDescription);
					testState.currentStep = testState.currentStoryDescription;
				}
			}
		}
	}

	@Override
	public void afterStory(boolean isGivenStory) {
		TestState testState = this.testState.get();
		if (isGivenStory) {
			testState.givenStoryLevel--;
			if (testState.currentStep != null) {
				notifier.fireTestFinished(testState.currentStep);
			}
			prepareNextStep();
			processBeforeScenario();
		} else {
			if (!testState.failedSteps.contains(testState.currentStoryDescription)) {
				notifier.fireTestFinished(testState.currentStoryDescription);
				if (testState.currentStoryDescription.isTest()) {
					testCounter.incrementAndGet();
				}
			}
			processAfterStory();
			if (testCounter.get() == totalTests && notifyFinished) {
				Result result = new Result();
				notifier.fireTestRunFinished(result);
			}
		}
	}

	@Override
	public void beforeScenario(String title) {
		TestState testState = this.testState.get();
		if (!testState.isGivenStoryRunning()) {
			notifier.fireTestStarted(testState.currentScenario);

			List children = testState.currentScenario.getChildren();
			List examples = filterExamples(children);
			if (!examples.isEmpty()) {
				testState.exampleDescriptions = examples.iterator();
				testState.currentExample = null;
			}
			if (children.size() > examples.size()) {
				// in case of given stories, these steps are actually stories,
				// for which events will be fired in beforeStory(..., true)
				List steps = new ArrayList<>(
						testState.currentScenario.getChildren());
				steps.removeAll(examples);
				testState.stepDescriptions = getAllDescendants(steps).iterator();
				testState.moveToNextStep();
				processBeforeScenario();
			}
		}
	}

	private List filterExamples(List children) {
		for (int i = 0; i < children.size(); i++) {
			Description child = children.get(i);
			boolean isExample = child.getDisplayName().startsWith(
					keywords.examplesTableRow() + " ");
			if (isExample) {
				return children.subList(i, children.size());
			}
		}
		return Collections.emptyList();
	}

	private Collection getAllDescendants(List steps) {
		List descendants = new ArrayList<>();
		for (Description child : steps) {
			descendants.add(child);
			descendants.addAll(getAllDescendants(child.getChildren()));
		}
		return descendants;
	}

	@Override
	public void afterScenario() {
		TestState testState = this.testState.get();
		if (!testState.isGivenStoryRunning()) {
			notifier.fireTestFinished(testState.currentScenario);
			processAfterScenario();
			testState.moveToNextScenario();
		}
	}

	private void processBeforeStory() {
		TestState testState = this.testState.get();
		Description currentScenario = testState.currentScenario;
		if (currentScenario != null &&
				currentScenario.getDisplayName().startsWith(JUnitDescriptionGenerator.BEFORE_STORY_STEP_NAME)) {
			// @BeforeStory has been called already
			notifier.fireTestStarted(currentScenario);
			notifier.fireTestFinished(currentScenario);
			testState.moveToNextScenario();
		}
	}

	private void processAfterStory() {
		TestState testState = this.testState.get();
		Description currentScenario = testState.currentScenario;
		if (currentScenario != null) {
			if (currentScenario.getDisplayName().startsWith(JUnitDescriptionGenerator.AFTER_STORY_STEP_NAME)) {
				// @AfterStory has been called already
				notifier.fireTestStarted(currentScenario);
				notifier.fireTestFinished(currentScenario);
				testState.moveToNextScenario();
			}
			else {
				testState.moveToNextScenario();
				processAfterStory();
			}
		}
	}

	private void processBeforeScenario() {
		Description currentStep = testState.get().currentStep;
		if (currentStep != null &&
				currentStep.getDisplayName().startsWith(JUnitDescriptionGenerator.BEFORE_SCENARIO_STEP_NAME)) {
			// @BeforeScenario has been called already
			notifier.fireTestStarted(currentStep);
			notifier.fireTestFinished(currentStep);
			prepareNextStep();
		}
	}

	private void processAfterScenario() {
		TestState testState = this.testState.get();
		Description currentStep = testState.currentStep;
		if (currentStep != null) {
			if (currentStep.getDisplayName().startsWith(JUnitDescriptionGenerator.AFTER_SCENARIO_STEP_NAME)) {
				// @AfterScenario has been called already
				notifier.fireTestStarted(currentStep);
				notifier.fireTestFinished(currentStep);
				prepareNextStep();
			}
			else {
				testState.moveToNextStep();
				processAfterScenario();
			}
		}
	}

	@Override
	public void example(Map arg0) {
		TestState testState = this.testState.get();
		if (!testState.isGivenStoryRunning()) {
			if (testState.currentExample != null && testState.stepDescriptions != null) {
				processAfterScenario();
			}
			testState.moveToNextExample();
			testState.stepDescriptions = testState.currentExample.getChildren().iterator();
			testState.moveToNextStep();
			processBeforeScenario();
		}
	}

	@Override
	public void beforeStep(String title) {
		TestState testState = this.testState.get();
		if (!testState.isGivenStoryRunning()) {
			notifier.fireTestStarted(testState.currentStep);
		}
	}

	@Override
	public void failed(String step, Throwable e) {
		TestState testState = this.testState.get();
		if (!testState.isGivenStoryRunning()) {
			Throwable thrownException = e instanceof UUIDExceptionWrapper ? e.getCause() : e;
			if (thrownException instanceof BeforeOrAfterFailed) {
				notifier.fireTestStarted(testState.currentStep);
			}
			notifier.fireTestFailure(new Failure(testState.currentStep, thrownException));
			notifier.fireTestFinished(testState.currentStep);
			testState.failedSteps.add(testState.currentStep);
			prepareNextStep();
		}
	}

	@Override
	public void successful(String step) {
		TestState testState = this.testState.get();
		if (!testState.isGivenStoryRunning()) {
			if (testState.currentStep != null) {
				notifier.fireTestFinished(testState.currentStep);
			}
			prepareNextStep();
		}
	}

	private void prepareNextStep() {
		TestState testState = this.testState.get();
		if (testState.currentStep != null && testState.currentStep.isTest()) {
			testCounter.incrementAndGet();
		}
		if (testState.stepDescriptions != null) {
			testState.moveToNextStep();
		}
	}

	@Override
	public void pending(String arg0) {
		TestState testState = this.testState.get();
		if (!testState.isGivenStoryRunning()) {
			if (pendingStepStrategy instanceof FailingUponPendingStep) {
				notifier.fireTestStarted(testState.currentStep);
				notifier.fireTestFailure(new Failure(testState.currentStep,
						new RuntimeException("Step is pending!")));
				// Pending step strategy says to fail so treat this step as
				// having failed.
				testState.failedSteps.add(testState.currentStep);
				notifier.fireTestFinished(testState.currentStep);
			} else {
				notifier.fireTestIgnored(testState.currentStep);
			}

			prepareNextStep();
		}
	}

	@Override
	public void ignorable(String arg0) {
		TestState testState = this.testState.get();
		if (!testState.isGivenStoryRunning()) {
			notifier.fireTestIgnored(testState.currentStep);
			prepareNextStep();
		}
	}

	@Override
	public void notPerformed(String arg0) {
		TestState testState = this.testState.get();
		if (!testState.isGivenStoryRunning()) {
			notifier.fireTestIgnored(testState.currentStep);
			prepareNextStep();
		}
	}

	/**
	 * Notify the IDE that the current step and scenario is not being executed.
	 * Reason is a JBehave meta tag is filtering out this scenario.
	 *
	 * @param scenario Scenario
	 * @param filter Filter
	 */
	@Override
	public void scenarioNotAllowed(Scenario scenario, String filter) {
		TestState testState = this.testState.get();
		notifier.fireTestIgnored(testState.currentStep);
		notifier.fireTestIgnored(testState.currentScenario);
	}

	public void usePendingStepStrategy(PendingStepStrategy pendingStepStrategy) {
		this.pendingStepStrategy = pendingStepStrategy;
	}

	private class TestState {
		private Description currentStep;
		private Iterator stepDescriptions;

		private Description currentScenario;
		private Iterator scenarioDescriptions;

		private Description currentExample;
		private Iterator exampleDescriptions;

		private Description currentStoryDescription;
		private int givenStoryLevel;

		private Set failedSteps = new HashSet<>();

		private void moveToNextScenario() {
			currentScenario = getNextOrNull(scenarioDescriptions);
			currentStep = currentScenario;
		}

		private void moveToNextExample() {
			currentExample = getNextOrNull(exampleDescriptions);
		}

		private void moveToNextStep() {
			currentStep = getNextOrNull(stepDescriptions);
		}

		private boolean isGivenStoryRunning() {
			return givenStoryLevel != 0;
		}

		private  T getNextOrNull(Iterator iterator) {
			return iterator.hasNext() ? iterator.next() : null;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy