Skip to content

Commit

Permalink
[Core] Fix concurrent access issues in JUnit and TestNG formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
mpkorstanje committed Mar 6, 2019
1 parent 9073e1e commit 6e17d86
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 47 deletions.
48 changes: 21 additions & 27 deletions core/src/main/java/cucumber/runtime/formatter/JUnitFormatter.java
Expand Up @@ -45,6 +45,12 @@ final class JUnitFormatter implements EventListener, StrictAware {
private TestCase testCase; private TestCase testCase;
private Element root; private Element root;


private final TestSourcesModel testSources = new TestSourcesModel();
private boolean strict = false;
private String currentFeatureFile = null;
private String previousTestCaseName = "";
private int exampleNumber = 1;

private EventHandler<TestSourceRead> sourceReadHandler= new EventHandler<TestSourceRead>() { private EventHandler<TestSourceRead> sourceReadHandler= new EventHandler<TestSourceRead>() {
@Override @Override
public void receive(TestSourceRead event) { public void receive(TestSourceRead event) {
Expand Down Expand Up @@ -79,10 +85,6 @@ public void receive(TestRunFinished event) {
@SuppressWarnings("WeakerAccess") // Used by plugin factory @SuppressWarnings("WeakerAccess") // Used by plugin factory
public JUnitFormatter(URL out) throws IOException { public JUnitFormatter(URL out) throws IOException {
this.out = new UTF8OutputStreamWriter(new URLOutputStream(out)); this.out = new UTF8OutputStreamWriter(new URLOutputStream(out));
TestCase.treatConditionallySkippedAsFailure = false;
TestCase.currentFeatureFile = null;
TestCase.previousTestCaseName = "";
TestCase.exampleNumber = 1;
try { try {
doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
rootElement = doc.createElement("testsuite"); rootElement = doc.createElement("testsuite");
Expand All @@ -102,14 +104,14 @@ public void setEventPublisher(EventPublisher publisher) {
} }


private void handleTestSourceRead(TestSourceRead event) { private void handleTestSourceRead(TestSourceRead event) {
TestCase.testSources.addTestSourceReadEvent(event.uri, event); testSources.addTestSourceReadEvent(event.uri, event);
} }


private void handleTestCaseStarted(TestCaseStarted event) { private void handleTestCaseStarted(TestCaseStarted event) {
if (TestCase.currentFeatureFile == null || !TestCase.currentFeatureFile.equals(event.testCase.getUri())) { if (currentFeatureFile == null || !currentFeatureFile.equals(event.testCase.getUri())) {
TestCase.currentFeatureFile = event.testCase.getUri(); currentFeatureFile = event.testCase.getUri();
TestCase.previousTestCaseName = ""; previousTestCaseName = "";
TestCase.exampleNumber = 1; exampleNumber = 1;
} }
testCase = new TestCase(event.testCase); testCase = new TestCase(event.testCase);
root = testCase.createElement(doc); root = testCase.createElement(doc);
Expand Down Expand Up @@ -181,29 +183,19 @@ private void increaseAttributeValue(Element element, String attribute) {


@Override @Override
public void setStrict(boolean strict) { public void setStrict(boolean strict) {
TestCase.treatConditionallySkippedAsFailure = strict; this.strict = strict;
} }


private static class TestCase { private class TestCase {
private static final DecimalFormat NUMBER_FORMAT = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
private static final TestSourcesModel testSources = new TestSourcesModel();


static { final List<PickleStepTestStep> steps = new ArrayList<>();
NUMBER_FORMAT.applyPattern("0.######"); final List<Result> results = new ArrayList<>();
} private final cucumber.api.TestCase testCase;


private TestCase(cucumber.api.TestCase testCase) { private TestCase(cucumber.api.TestCase testCase) {
this.testCase = testCase; this.testCase = testCase;
} }


static String currentFeatureFile;
static String previousTestCaseName;
static int exampleNumber;
static boolean treatConditionallySkippedAsFailure = false;
final List<PickleStepTestStep> steps = new ArrayList<PickleStepTestStep>();
final List<Result> results = new ArrayList<Result>();
private final cucumber.api.TestCase testCase;

private Element createElement(Document doc) { private Element createElement(Document doc) {
return doc.createElement("testcase"); return doc.createElement("testcase");
} }
Expand Down Expand Up @@ -237,7 +229,7 @@ public void addTestCaseElement(Document doc, Element tc, Result result) {
addStackTrace(sb, result); addStackTrace(sb, result);
child = createElementWithMessage(doc, sb, "failure", result.getErrorMessage()); child = createElementWithMessage(doc, sb, "failure", result.getErrorMessage());
} else if (result.is(Result.Type.PENDING) || result.is(Result.Type.UNDEFINED)) { } else if (result.is(Result.Type.PENDING) || result.is(Result.Type.UNDEFINED)) {
if (treatConditionallySkippedAsFailure) { if (strict) {
child = createElementWithMessage(doc, sb, "failure", "The scenario has pending or undefined step(s)"); child = createElementWithMessage(doc, sb, "failure", "The scenario has pending or undefined step(s)");
} }
else { else {
Expand All @@ -256,14 +248,16 @@ public void addTestCaseElement(Document doc, Element tc, Result result) {
public void handleEmptyTestCase(Document doc, Element tc, Result result) { public void handleEmptyTestCase(Document doc, Element tc, Result result) {
tc.setAttribute("time", calculateTotalDurationString(result)); tc.setAttribute("time", calculateTotalDurationString(result));


String resultType = treatConditionallySkippedAsFailure ? "failure" : "skipped"; String resultType = strict ? "failure" : "skipped";
Element child = createElementWithMessage(doc, new StringBuilder(), resultType, "The scenario has no steps"); Element child = createElementWithMessage(doc, new StringBuilder(), resultType, "The scenario has no steps");


tc.appendChild(child); tc.appendChild(child);
} }


private String calculateTotalDurationString(Result result) { private String calculateTotalDurationString(Result result) {
return NUMBER_FORMAT.format(((double) result.getDuration()) / 1000000000); DecimalFormat numberFormat = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
numberFormat.applyPattern("0.######");
return numberFormat.format(((double) result.getDuration()) / 1000000000);
} }


private void addStepAndResultListing(StringBuilder sb) { private void addStepAndResultListing(StringBuilder sb) {
Expand Down
41 changes: 21 additions & 20 deletions core/src/main/java/cucumber/runtime/formatter/TestNGFormatter.java
Expand Up @@ -40,7 +40,6 @@


class TestNGFormatter implements EventListener, StrictAware { class TestNGFormatter implements EventListener, StrictAware {


private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
private final Writer writer; private final Writer writer;
private final Document document; private final Document document;
private final Element results; private final Element results;
Expand All @@ -50,6 +49,13 @@ class TestNGFormatter implements EventListener, StrictAware {
private Element root; private Element root;
private TestMethod testMethod; private TestMethod testMethod;


private final TestSourcesModel testSources = new TestSourcesModel();
private String currentFeatureFile = null;
private boolean strict = false;
private String previousTestCaseName;
private int exampleNumber;


private EventHandler<TestSourceRead> testSourceReadHandler = new EventHandler<TestSourceRead>() { private EventHandler<TestSourceRead> testSourceReadHandler = new EventHandler<TestSourceRead>() {
@Override @Override
public void receive(TestSourceRead event) { public void receive(TestSourceRead event) {
Expand Down Expand Up @@ -84,8 +90,6 @@ public void receive(TestRunFinished event) {
@SuppressWarnings("WeakerAccess") // Used by PluginFactory @SuppressWarnings("WeakerAccess") // Used by PluginFactory
public TestNGFormatter(URL url) throws IOException { public TestNGFormatter(URL url) throws IOException {
this.writer = new UTF8OutputStreamWriter(new URLOutputStream(url)); this.writer = new UTF8OutputStreamWriter(new URLOutputStream(url));
TestMethod.treatSkippedAsFailure = false;
TestMethod.currentFeatureFile = null;
try { try {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
results = document.createElement("testng-results"); results = document.createElement("testng-results");
Expand All @@ -110,20 +114,20 @@ public void setEventPublisher(EventPublisher publisher) {


@Override @Override
public void setStrict(boolean strict) { public void setStrict(boolean strict) {
TestMethod.treatSkippedAsFailure = strict; this.strict = strict;
} }


private void handleTestSourceRead(TestSourceRead event) { private void handleTestSourceRead(TestSourceRead event) {
TestMethod.testSources.addTestSourceReadEvent(event.uri, event); testSources.addTestSourceReadEvent(event.uri, event);
} }


private void handleTestCaseStarted(TestCaseStarted event) { private void handleTestCaseStarted(TestCaseStarted event) {
if (TestMethod.currentFeatureFile == null || !TestMethod.currentFeatureFile.equals(event.testCase.getUri())) { if (currentFeatureFile == null || !currentFeatureFile.equals(event.testCase.getUri())) {
TestMethod.currentFeatureFile = event.testCase.getUri(); currentFeatureFile = event.testCase.getUri();
TestMethod.previousTestCaseName = ""; previousTestCaseName = "";
TestMethod.exampleNumber = 1; exampleNumber = 1;
clazz = document.createElement("class"); clazz = document.createElement("class");
clazz.setAttribute("name", TestMethod.testSources.getFeature(event.testCase.getUri()).getName()); clazz.setAttribute("name", testSources.getFeature(event.testCase.getUri()).getName());
test.appendChild(clazz); test.appendChild(clazz);
} }
root = document.createElement("test-method"); root = document.createElement("test-method");
Expand Down Expand Up @@ -200,13 +204,10 @@ private String getTotalDuration(NodeList testCaseNodes) {
return String.valueOf(totalDuration); return String.valueOf(totalDuration);
} }


private static class TestMethod { private class TestMethod {

private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");


static String currentFeatureFile;
static boolean treatSkippedAsFailure = false;
static String previousTestCaseName;
static int exampleNumber;
static final TestSourcesModel testSources = new TestSourcesModel();
final List<PickleStepTestStep> steps = new ArrayList<PickleStepTestStep>(); final List<PickleStepTestStep> steps = new ArrayList<PickleStepTestStep>();
final List<Result> results = new ArrayList<Result>(); final List<Result> results = new ArrayList<Result>();
final List<Result> hooks = new ArrayList<Result>(); final List<Result> hooks = new ArrayList<Result>();
Expand All @@ -218,7 +219,7 @@ private TestMethod(TestCase scenario) {


private void start(Element element) { private void start(Element element) {
element.setAttribute("name", calculateElementName(scenario)); element.setAttribute("name", calculateElementName(scenario));
element.setAttribute("started-at", DATE_FORMAT.format(new Date())); element.setAttribute("started-at", dateFormat.format(new Date()));
} }


private String calculateElementName(TestCase testCase) { private String calculateElementName(TestCase testCase) {
Expand All @@ -232,9 +233,9 @@ private String calculateElementName(TestCase testCase) {
} }
} }


public void finish(Document doc, Element element) { void finish(Document doc, Element element) {
element.setAttribute("duration-ms", calculateTotalDurationString()); element.setAttribute("duration-ms", calculateTotalDurationString());
element.setAttribute("finished-at", DATE_FORMAT.format(new Date())); element.setAttribute("finished-at", dateFormat.format(new Date()));
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
addStepAndResultListing(stringBuilder); addStepAndResultListing(stringBuilder);
Result skipped = null; Result skipped = null;
Expand All @@ -259,7 +260,7 @@ public void finish(Document doc, Element element) {
Element exception = createException(doc, failed.getError().getClass().getName(), stringBuilder.toString(), stringWriter.toString()); Element exception = createException(doc, failed.getError().getClass().getName(), stringBuilder.toString(), stringWriter.toString());
element.appendChild(exception); element.appendChild(exception);
} else if (skipped != null) { } else if (skipped != null) {
if (treatSkippedAsFailure) { if (strict) {
element.setAttribute("status", "FAIL"); element.setAttribute("status", "FAIL");
Element exception = createException(doc, "The scenario has pending or undefined step(s)", stringBuilder.toString(), "The scenario has pending or undefined step(s)"); Element exception = createException(doc, "The scenario has pending or undefined step(s)", stringBuilder.toString(), "The scenario has pending or undefined step(s)");
element.appendChild(exception); element.appendChild(exception);
Expand Down

0 comments on commit 6e17d86

Please sign in to comment.