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

UnitFormatter for cucumber-jvm #226

Merged
merged 3 commits into from
Feb 29, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ report.js
chromedriver.log
.scala_dependencies
nexus.properties

# OS generated files
.DS_Store*
ehthumbs.db
Icon?
Thumbs.db
6 changes: 6 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>xmlunit</groupId>
<artifactId>xmlunit</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
Expand Down
11 changes: 4 additions & 7 deletions core/src/main/java/cucumber/formatter/FormatterFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import gherkin.formatter.PrettyFormatter;

import java.io.File;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -21,6 +20,7 @@ public class FormatterFactory {
put("json", JSONFormatter.class.getName());
put("json-pretty", JSONPrettyFormatter.class.getName());
put("pretty", PrettyFormatter.class.getName());
put("unit", UnitFormatter.class.getName());
}};

public FormatterFactory(ClassLoader classLoader) {
Expand All @@ -37,13 +37,10 @@ private Formatter createFormatterFromClassName(String className, Object out) {
Class ctorArgClass = Appendable.class;
if (out instanceof File) {
File file = (File) out;
if (file.isDirectory()) {
out = file;
ctorArgClass = File.class;
} else {
out = new FileWriter(file);
}
out = file;
ctorArgClass = File.class;
}

Class<Formatter> formatterClass = getFormatterClass(className);
// TODO: Remove these if statements. We should fix PrettyFormatter and ProgressFormatter to only take a single Appendable arg.
// Whether or not to use Monochrome is tricky. Maybe always enforce another 2nd argument for that
Expand Down
21 changes: 4 additions & 17 deletions core/src/main/java/cucumber/formatter/HTMLFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,9 @@
import gherkin.formatter.Mappable;
import gherkin.formatter.NiceAppendable;
import gherkin.formatter.Reporter;
import gherkin.formatter.model.Background;
import gherkin.formatter.model.Examples;
import gherkin.formatter.model.Feature;
import gherkin.formatter.model.Match;
import gherkin.formatter.model.Result;
import gherkin.formatter.model.Scenario;
import gherkin.formatter.model.ScenarioOutline;
import gherkin.formatter.model.Step;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import gherkin.formatter.model.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if you could configure your IDE to not do star imports - see the README for coding standards

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I've noticed it after submitting pull request. Do I need another commit?


import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -181,6 +167,7 @@ private NiceAppendable jsOut() {
}

private OutputStream reportFileOutputStream(String fileName) {
htmlReportDir.mkdirs();
File file = new File(htmlReportDir, fileName);
try {
return new FileOutputStream(file);
Expand Down
208 changes: 208 additions & 0 deletions core/src/main/java/cucumber/formatter/UnitFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package cucumber.formatter;

import cucumber.runtime.CucumberException;
import gherkin.formatter.Formatter;
import gherkin.formatter.Reporter;
import gherkin.formatter.model.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

/**
* @author Uladzimir Mihura
* Date: 2/24/12
* Time: 4:02 PM
*/
public class UnitFormatter implements Formatter, Reporter {
private File out;
private Document doc;
private Element rootElement;
private TestCase testCase;


public UnitFormatter(File out) {
this.out = out;
try {
doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
rootElement = doc.createElement("testsuite");
doc.appendChild(rootElement);
} catch (ParserConfigurationException e) {
throw new CucumberException("Error while processing unit report", e);
}
}


@Override
public void feature(Feature feature) {
TestCase.feature = feature;
}

@Override
public void background(Background background) {
testCase = new TestCase();
}

@Override
public void scenario(Scenario scenario) {
if (testCase != null) {
testCase.scenario = scenario;
} else {
testCase = new TestCase(scenario);
}

increaseAttributeValue(rootElement, "tests");
}

@Override
public void step(Step step) {
if (testCase != null) testCase.steps.add(step);
}


@Override
public void done() {
try {
//set up a transformer
rootElement.setAttribute("failed", String.valueOf(rootElement.getElementsByTagName("failure").getLength()));
TransformerFactory transfac = TransformerFactory.newInstance();
Transformer trans = transfac.newTransformer();
trans.setOutputProperty(OutputKeys.INDENT, "yes");
StreamResult result = new StreamResult(out);
DOMSource source = new DOMSource(doc);
trans.transform(source, result);
} catch (TransformerException e) {
new CucumberException("Error while transforming.", e);
}
}


@Override
public void result(Result result) {
testCase.results.add(result);

if (testCase.scenario != null && testCase.results.size() == testCase.steps.size()) {
rootElement.appendChild(testCase.writeTo(doc));
testCase = null;
}
}

private void increaseAttributeValue(Element element, String attribute) {
int value = 0;
if (element.hasAttribute(attribute)) {
value = Integer.parseInt(element.getAttribute(attribute));
}
element.setAttribute(attribute, String.valueOf(++value));
}

@Override
public void scenarioOutline(ScenarioOutline scenarioOutline) {
}

@Override
public void examples(Examples examples) {
TestCase.examples = examples.getRows().size()-1;
}

@Override
public void match(Match match) {
}

@Override
public void embedding(String mimeType, InputStream data) {
}

@Override
public void write(String text) {
}

@Override
public void uri(String uri) {
}

@Override
public void close() {

}

@Override
public void eof() {
}

@Override
public void syntaxError(String state, String event, List<String> legalEvents, String uri, int line) {
}

private static class TestCase {
private TestCase(Scenario scenario) {
this.scenario = scenario;
}

private TestCase() {
}

Scenario scenario;
static Feature feature;
static int examples = 0;
List<Step> steps = new ArrayList<Step>();
List<Result> results = new ArrayList<Result>();

private Element writeTo(Document doc) {
Element tc = doc.createElement("testcase");
tc.setAttribute("classname", feature.getName());
tc.setAttribute("name", examples > 0 ? scenario.getName() + "_" + examples-- : scenario.getName());
long time = 0;
for (Result r : results) {
time += r.getDuration() != null ? r.getDuration() : 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Result.getDuration() is currently in nanoseconds. I'm not sure what the JUnit report contract is, but I assume millis.

What do you think - should I rename this to getDurationNanos() ? That would make it more obvious that people who want millis (you?) have to divide by 1000000.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this this would be better if you need nanos somewhere else.

public long getDuration() {
    this. getDurationNanos()/1000000
}

I just noticed that my tests running for years :)

}
tc.setAttribute("time", String.valueOf(time));

StringBuilder sb = new StringBuilder();
Result skipped = null, failed = null;
for (int i = 0; i < steps.size(); i++) {
int length = sb.length();
Step step = steps.get(i);
Result result = results.get(i);
if ("failed".equals(result.getStatus())) failed = result;
if ("undefined".equals(result.getStatus()) || "pending".equals(result.getStatus())) skipped = result;
sb.append(steps.get(i).getKeyword());
sb.append(steps.get(i).getName());
for (int j = 0; sb.length() - length + j < 140; j++) sb.append(".");
sb.append(result.getStatus());
sb.append("\n");
}
Element child;
if (failed != null) {
sb.append("\nStackTrace:\n");
StringWriter sw = new StringWriter();
failed.getError().printStackTrace(new PrintWriter(sw));
sb.append(sw.toString());
child = doc.createElement("failure");
child.setAttribute("message", failed.getErrorMessage());
child.appendChild(doc.createCDATASection(sb.toString()));
} else if (skipped != null) {
child = doc.createElement("skipped");
child.appendChild(doc.createCDATASection(sb.toString()));
} else {
child = doc.createElement("system-out");
child.appendChild(doc.createCDATASection(sb.toString()));
}
tc.appendChild(child);
return tc;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public void shouldInstantiateHtmlFormatter() {
assertThat(formatterFactory.createFormatter("html", new File(System.getProperty("user.dir"))), is(HTMLFormatter.class));
}

@Test
public void shouldInstantiateUnitFormatter() {
assertThat(formatterFactory.createFormatter("unit", new File(System.getProperty("user.dir")+"report.xml")), is(UnitFormatter.class));
}

@Test
public void shouldInstantiateCustomFormatterFromClassNameWithAppender() {
StringWriter writer = new StringWriter();
Expand Down
64 changes: 64 additions & 0 deletions core/src/test/java/cucumber/formatter/UnitFormatterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package cucumber.formatter;

import cucumber.io.ClasspathResourceLoader;
import cucumber.runtime.Backend;
import cucumber.runtime.Runtime;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Test;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.util.List;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;

/**
* @author Uladzimir Mihura
* Date: 2/25/12
* Time: 8:39 PM
*/
public class UnitFormatterTest {

@Test
public void featureSimpleTest() throws Exception {
runFeaturesWithFormatter(asList("cucumber/formatter/UnitFormatterTest_1.feature"));
compareXML("cucumber/formatter/UnitFormatterTest_1.report.xml", "report.xml");
}

@Test
public void featureWithBackgroundTest() throws Exception {
runFeaturesWithFormatter(asList("cucumber/formatter/UnitFormatterTest_2.feature"));
compareXML("cucumber/formatter/UnitFormatterTest_2.report.xml", "report.xml");
}

@Test
public void featureWithOutlineTest() throws Exception {
runFeaturesWithFormatter(asList("cucumber/formatter/UnitFormatterTest_3.feature"));
compareXML("cucumber/formatter/UnitFormatterTest_3.report.xml", "report.xml");
}

private void runFeaturesWithFormatter(final List<String> featurePaths) throws IOException {
File report = new File("report.xml");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think File.createTempFile would be better here.

// report.deleteOnExit();
final UnitFormatter f = new UnitFormatter(report);
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader);
final List<String> gluePaths = emptyList();
final cucumber.runtime.Runtime runtime = new Runtime(resourceLoader, gluePaths, classLoader, asList(mock(Backend.class)), false);
runtime.run(featurePaths, emptyList(), f, f);
f.done();
f.close();
}

private void compareXML(String expected, String received) throws IOException, ParserConfigurationException, SAXException {
XMLUnit.setIgnoreWhitespace(true);
Diff diff = new Diff(new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream(expected)), new FileReader(received));
assertTrue("XML files are similar " + diff, diff.identical());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Feature: Feature_1
Some description

Scenario: Scenario_1
Given step_1
When step_2
Then step_3

Scenario: Scenario_2
Given step_1
When step_2
Then step_3
Loading