PostStoryStatisticsCollector.java

package org.jbehave.core.reporters;

import static java.util.Arrays.asList;

import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.jbehave.core.failures.RestartingStoryFailure;
import org.jbehave.core.model.GivenStories;
import org.jbehave.core.model.OutcomesTable;
import org.jbehave.core.model.Scenario;
import org.jbehave.core.model.Story;
import org.jbehave.core.model.StoryDuration;
import org.jbehave.core.steps.StepCreator.PendingStep;
import org.jbehave.core.steps.Timing;

/**
 * <p>
 * Reporter that collects statistics and writes them as properties to output
 * stream after each story
 * </p>
 */
public class PostStoryStatisticsCollector extends NullStoryReporter {

    private final OutputStream output;
    private final Map<String, Integer> data = new HashMap<>();
    private final List<String> events = asList("excluded", "pending", "scenariosExcluded",
            "givenStoryScenariosExcluded", "steps", "stepsSuccessful", "stepsIgnorable", "comments", "stepsPending",
            "stepsNotPerformed", "stepsFailed", "currentScenarioSteps", "currentScenarioStepsPending", "scenarios",
            "scenariosSuccessful", "scenariosPending", "scenariosFailed", "givenStories", "givenStoryScenarios",
            "givenStoryScenariosSuccessful", "givenStoryScenariosPending", "givenStoryScenariosFailed", "examples");

    private Throwable cause;
    private OutcomesTable outcomesFailed;
    private int givenStories;
    private boolean currentScenarioExcluded;

    public PostStoryStatisticsCollector(OutputStream output) {
        this.output = output;
    }

    @Override
    public void successful(String step) {
        add("steps");
        add("stepsSuccessful");
        add("currentScenarioSteps");
    }

    @Override
    public void ignorable(String step) {
        add("steps");
        add("stepsIgnorable");
        add("currentScenarioSteps");
    }

    @Override
    public void comment(String step) {
        add("steps");
        add("comments");
        add("currentScenarioSteps");
    }

    @Override
    public void pending(PendingStep step) {
        add("steps");
        add("stepsPending");
        add("currentScenarioSteps");
        add("currentScenarioStepsPending");
    }

    @Override
    public void notPerformed(String step) {
        add("steps");
        add("stepsNotPerformed");
        add("currentScenarioSteps");
    }

    @Override
    public void failed(String step, Throwable cause) {
        this.cause = cause;

        if (cause != null && !(cause.getCause() instanceof RestartingStoryFailure)) {
            add("steps");
            add("stepsFailed");
            add("currentScenarioSteps");
        }
    }

    @Override
    public void failedOutcomes(String step, OutcomesTable table) {
        this.outcomesFailed = table;
        add("steps");
        add("stepsFailed");
        add("currentScenarioSteps");
    }

    @Override
    public void beforeStory(Story story, boolean givenStory) {
        if (givenStory) {
            this.givenStories++;
        }

        if (!givenStory) {
            resetData();
        }
    }

    @Override
    public void storyExcluded(Story story, String filter) {
        resetData();
        add("excluded");
        writeData();
    }

    @Override
    public void storyCancelled(Story story, StoryDuration storyDuration) {
        add("cancelled");
    }

    @Override
    public void afterStory(boolean givenStory) {
        boolean write = false;
        if (givenStory) {
            this.givenStories--;
            if (has("stepsFailed")) {
                add("scenariosFailed");
                write = true;
            }
        } else {
            if (has("scenariosPending") || has("givenStoryScenariosPending")) {
                add("pending");
            }
            write = true;
        }
        if (write) {
            writeData();
        }
    }

    @Override
    public void givenStories(GivenStories givenStories) {
        add("givenStories");
    }

    @Override
    public void givenStories(List<String> storyPaths) {
        add("givenStories");
    }

    @Override
    public void beforeScenario(Scenario scenario) {
        cause = null;
        outcomesFailed = null;
        currentScenarioExcluded = false;
        reset("currentScenarioSteps");
        reset("currentScenarioStepsPending");
    }

    @Override
    public void scenarioExcluded(Scenario scenario, String filter) {
        if (givenStories > 0) {
            add("givenStoryScenariosExcluded");
        } else {
            add("scenariosExcluded");
            currentScenarioExcluded = true;
        }
    }

    @Override
    public void afterScenario(Timing timing) {
        if (givenStories > 0) {
            countScenarios("givenStoryScenarios");
        } else {
            countScenarios("scenarios");
        }
        if (has("currentScenarioStepsPending") || (!has("currentScenarioSteps") && !currentScenarioExcluded)) {
            if (givenStories > 0) {
                add("givenStoryScenariosPending");
            } else {
                add("scenariosPending");
            }
        }
    }

    private void countScenarios(String namespace) {
        add(namespace);
        if (!currentScenarioExcluded) {
            if (cause != null || outcomesFailed != null) {
                add(namespace + "Failed");
            } else {
                add(namespace + "Successful");
            }
        }
    }

    @Override
    public void example(Map<String, String> tableRow, int exampleIndex) {
        add("examples");
    }

    @Override
    public void restartedStory(Story story, Throwable cause) {
        resetData();
    }

    private void add(String event) {
        Integer count = data.get(event);
        if (count == null) {
            count = 0;
        }
        count++;
        data.put(event, count);
    }

    private boolean has(String event) {
        Integer count = data.get(event);
        if (count == null) {
            count = 0;
        }
        return count > 0;
    }

    private void writeData() {
        Properties p = new Properties();
        for (String event : data.keySet()) {
            if (!event.startsWith("current")) {
                p.setProperty(event, data.get(event).toString());
            }
        }
        try {
            p.store(output, this.getClass().getName());
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void resetData() {
        data.clear();
        for (String event : events) {
            reset(event);
        }
    }

    private void reset(String event) {
        data.put(event, 0);
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(output).append(data).toString();
    }
}