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

Correct subtest placement #50

Merged
merged 5 commits into from
May 19, 2018
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
14 changes: 7 additions & 7 deletions tap4j/src/main/java/org/tap4j/model/Patterns.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,39 +42,39 @@ private Patterns() { // hidden as this is a final utility class
/**
* TAP Text Regex.
*/
static final String REGEX_TEXT = "((\\s|\\t)*)(.*)";
static final String REGEX_TEXT = "(\\s*)(.*)";

/**
* TAP Header Regex.
*/
static final String REGEX_HEADER = "((\\s|\\t)*)?TAP\\s*version\\s*(\\d+)\\s*(#\\s*(.*))?";
static final String REGEX_HEADER = "(\\s*)TAP\\s*version\\s*(\\d+)\\s*(#\\s*(.*))?";

/**
* TAP Plan Regex.
*/
static final String REGEX_PLAN = "((\\s|\\t)*)?(\\d+)(\\.{2})(\\d+)"
static final String REGEX_PLAN = "(\\s*)(\\d+)(\\.{2})(\\d+)"
+ "\\s*(#\\s*(SKIP|skip)\\s*([^#]+))?\\s*(#\\s*(.*))?";

/**
* TAP Test Result Regex.
*/
static final String REGEX_TEST_RESULT = "((\\s|\\t)*)?(ok|not ok)\\s*(\\d*)\\s*([^#]*)?\\s*"
static final String REGEX_TEST_RESULT = "(\\s*)(ok|not ok)\\s*(\\d*)\\s*([^#]*)?\\s*"
Copy link
Member

Choose a reason for hiding this comment

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

Any reason why we should remove tabs here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The thing is \s includes \t, so it's redundant to use alternating matching between them. See https://docs.oracle.com/javase/tutorial/essential/regex/pre_char_classes.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And this minor issue is present in other regexes too, but I didn't fix it, because not sure if wanted.

Copy link
Member

@kinow kinow May 17, 2018

Choose a reason for hiding this comment

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

Just past midnight here, so the new day barely started and I can proudly say Today-I-Learned.

Had no idea they were redundant. I've been using both in Perl, shell, java... might have a few places to fix besides tap4j.

This change is definitely wanted. But we can fix in another issue if that's easier.

Thanks!!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, * takes exactly zero or more elements, so you can omit redundant ?.

Copy link
Member

Choose a reason for hiding this comment

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

Right. I think you are talking about this part:

([^#]*)?

I thought that it would resolve [^#]*as zero or more of [^#] in a group ()?. And so the group was receiving the ?, and thus would appear zero or at most once?

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 wrote about e.g. ((\\s|\\t)*)?(putting \t symbol aside) - in this case, ? is redundant.
We capture "zero or more space characters" for "zero or one time". It could be meaningful with (.+)?("one or more any character" could or could not occur), but it's roughly a wordy equivalent of just (.*).
That's what I meant.

Copy link
Member

Choose a reason for hiding this comment

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

Aahh! Sure. You fixed that one already, but I agree for the remaining regular expressions. Gonna start thinking a bit more when writing regexes now :)

+ "(#\\s*(SKIP|skip|TODO|todo)\\s*([^#]*))?\\s*(#\\s*(.*))?";

/**
* TAP Bail Out! Regex.
*/
static final String REGEX_BAIL_OUT = "((\\s|\\t)*)?Bail out!\\s*([^#]+)?\\s*(#\\s*(.*))?";
static final String REGEX_BAIL_OUT = "(\\s*)Bail out!\\s*([^#]+)?\\s*(#\\s*(.*))?";

/**
* TAP Comment Regex.
*/
static final String REGEX_COMMENT = "((\\s|\\t)*)?#\\s*(.*)";
static final String REGEX_COMMENT = "(\\s*)#\\s*(.*)";

/**
* TAP Footer Regex.
*/
static final String REGEX_FOOTER = "((\\s|\\t)*)?TAP\\s*([^#]*)?\\s*(#\\s*(.*))?";
static final String REGEX_FOOTER = "(\\s*)TAP\\s*([^#]*)?\\s*(#\\s*(.*))?";

/* -- Patterns -- */

Expand Down
34 changes: 17 additions & 17 deletions tap4j/src/main/java/org/tap4j/model/TapElementFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,42 +71,42 @@ public static TapElement createTapElement(String tapLine) {

m = Patterns.COMMENT_PATTERN.matcher(tapLine);
if (m.matches()) {
Comment comment = new Comment(m.group(3), false);
Comment comment = new Comment(m.group(2), false);
comment.indentation = m.group(1).length();
return comment;
}

m = Patterns.HEADER_PATTERN.matcher(tapLine);
if (m.matches()) {
Header header = new Header(Integer.parseInt(m.group(3)));
Header header = new Header(Integer.parseInt(m.group(2)));
header.indentation = m.group(1).length();
addComment(header, m.group(5));
addComment(header, m.group(4));
return header;
}

m = Patterns.FOOTER_PATTERN.matcher(tapLine);
if (m.matches()) {
Footer footer = new Footer(m.group(3));
addComment(footer, m.group(5));
Footer footer = new Footer(m.group(2));
addComment(footer, m.group(4));
footer.indentation = m.group(1).length();
return footer;
}

m = Patterns.PLAN_PATTERN.matcher(tapLine);
if (m.matches()) {
String skip = m.group(8);
String comment = m.group(10);
String skip = m.group(7);
String comment = m.group(9);
SkipPlan skipPlan = skip != null && skip.trim().length() > 0 ? new SkipPlan(skip) : null;
Plan plan = new Plan(Integer.parseInt(m.group(3)), Integer.parseInt(m.group(5)), skipPlan);
Plan plan = new Plan(Integer.parseInt(m.group(2)), Integer.parseInt(m.group(4)), skipPlan);
addComment(plan, comment);
plan.indentation = m.group(1).length();
return plan;
}

m = Patterns.BAIL_OUT_PATTERN.matcher(tapLine);
if (m.matches()) {
String reason = m.group(3);
String comment = m.group(5);
String reason = m.group(2);
String comment = m.group(4);
BailOut bailOut = new BailOut(reason);
addComment(bailOut, comment);
bailOut.indentation = m.group(1).length();
Expand All @@ -115,22 +115,22 @@ public static TapElement createTapElement(String tapLine) {

m = Patterns.TEST_RESULT_PATTERN.matcher(tapLine);
if (m.matches()) {
String testNumberText = m.group(4);
String testNumberText = m.group(3);
int testNumber = 0;
if (testNumberText != null && testNumberText.trim().equals("") == false) {
if (testNumberText != null && !testNumberText.trim().equals("")) {
testNumber = Integer.parseInt(testNumberText);
}
TestResult testResult = new TestResult(StatusValues.get(m.group(3)), testNumber);
String comment = m.group(10);
TestResult testResult = new TestResult(StatusValues.get(m.group(2)), testNumber);
String comment = m.group(9);
if (comment != null && comment.trim().length() > 0) {
final Comment c = new Comment(comment, true);
testResult.setComment(c);
testResult.addComment(c);
}
testResult.setDescription(m.group(5));
DirectiveValues directive = DirectiveValues.get(m.group(7));
testResult.setDescription(m.group(4));
DirectiveValues directive = DirectiveValues.get(m.group(6));
if (directive != null) {
testResult.setDirective(new Directive(directive, m.group(8)));
testResult.setDirective(new Directive(directive, m.group(7)));
}
testResult.indentation = m.group(1).length();
return testResult;
Expand Down
11 changes: 0 additions & 11 deletions tap4j/src/main/java/org/tap4j/parser/StreamStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,6 @@ public class StreamStatus {
*/
private final TestSet testSet = new TestSet();

/**
* In case sub-tests could not have been attached to parent (as the sub-tests came first)
* we need to make sure we make the link as the parent test comes.
*/
protected boolean attachedToParent;

/**
* Stream status test set.
*/
protected TestSet looseSubtests;

/**
* Default constructor.
*/
Expand Down
82 changes: 46 additions & 36 deletions tap4j/src/main/java/org/tap4j/parser/Tap13Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
*/
package org.tap4j.parser;

import org.tap4j.model.BailOut;
import org.tap4j.model.Comment;
import org.tap4j.model.Footer;
import org.tap4j.model.Header;
import org.tap4j.model.Plan;
import org.tap4j.model.TapElement;
import org.tap4j.model.TapElementFactory;
import org.tap4j.model.TestResult;
import org.tap4j.model.TestSet;
import org.tap4j.model.Text;
import org.yaml.snakeyaml.Yaml;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
Expand All @@ -38,18 +50,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import org.tap4j.model.BailOut;
import org.tap4j.model.Comment;
import org.tap4j.model.Footer;
import org.tap4j.model.Header;
import org.tap4j.model.Plan;
import org.tap4j.model.TapElement;
import org.tap4j.model.TapElementFactory;
import org.tap4j.model.TestResult;
import org.tap4j.model.TestSet;
import org.tap4j.model.Text;
import org.yaml.snakeyaml.Yaml;

/**
* TAP 13 parser.
*
Expand All @@ -72,7 +72,7 @@ public class Tap13Parser implements Parser {
* Stack of stream status information bags. Every bag stores state of the parser
* related to certain indentation level. This is to support subtest feature.
*/
private Stack<StreamStatus> states = new Stack<StreamStatus>();
private Stack<StreamStatus> states = new Stack<>();

/**
* The current state.
Expand All @@ -97,6 +97,11 @@ public class Tap13Parser implements Parser {
*/
private boolean enableSubtests = true;

/**
* A stack that holds subtests for which we don't know exact parent yet.
*/
private Stack<StreamStatus> subStack = new Stack<>();

/**
* Parser Constructor.
*
Expand Down Expand Up @@ -280,31 +285,24 @@ public void parseLine(String tapLine) {
baseIndentation = indentation;
}

if (indentation != state.getIndentationLevel()) { // indentation changed
StreamStatus prevState = null;
if (indentation != state.getIndentationLevel() && enableSubtests) { // indentation changed

if (indentation > state.getIndentationLevel()) {
StreamStatus parentState = state;
int prevIndent = state.getIndentationLevel();
pushState(indentation); // make room for children

TapElement lastParentElement = parentState.getLastParsedElement();
if (lastParentElement instanceof TestResult) {
final TestResult lastTestResult = (TestResult) lastParentElement;
// whatever test set comes should be attached to parent
if (lastTestResult.getSubtest() == null && this.enableSubtests) {
lastTestResult.setSubtest(state.getTestSet());
state.attachedToParent = true;
}
}
if (indentation - prevIndent > 4)
Copy link
Member

Choose a reason for hiding this comment

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

Could we use if (indentation - prevIndent > 0) instead? If I understand it right, we are changing the state only when we find a statement with 4 spaces more than the previous statement? Not sure if that's defined somewhere, but I suspect there might be tools/devs doing 2 spaces, or 3...

Copy link
Contributor Author

@Altai-man Altai-man May 17, 2018

Choose a reason for hiding this comment

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

Not sure if that's defined somewhere

Well, specification is not the strong point of TAP at all... I see at least a long conversation at TestAnything/Specification#2. But the thing is, all origin sources of truth(e.g. Perl) are using exact four spaces, not de jure, but de facto standard. It is up to you, of course, but I think If someone is doing weird indentation, it is theirs thing to patch.

If I understand it right, we are changing the state only when we find a statement with 4 spaces more than the previous statement?

Yes, We cannot use > 0 here. This check is needed to differentiate between those cases:

ok
    ok # 4 spaces, all is nice
ok

and

ok
            ok # 12 spaces, need to push in subtests stack
        ok
    ok
ok

That's why we are checking exact more than 4 spaces.

subStack.push(state);
} else {
// going down
do {
StreamStatus prevState = state;
if (states.peek().getIndentationLevel() == indentation) {
prevState = state;
state = states.pop();
if (!prevState.attachedToParent && this.enableSubtests) {
state.looseSubtests = prevState.getTestSet();
}
// there could be more than one level diff
} while (indentation < state.getIndentationLevel());
} else {
state = new StreamStatus();
state.setIndentationLevel(indentation);
subStack.push(state);
}
}
}

Expand Down Expand Up @@ -343,7 +341,7 @@ public void parseLine(String tapLine) {

final TestResult testResult = (TestResult) tapElement;
if (testResult.getTestNumber() == 0) {
if (state.getTestSet().getPlan() != null && state.isPlanBeforeTestResult() == false) {
if (state.getTestSet().getPlan() != null && !state.isPlanBeforeTestResult()) {
return; // done testing mark
}
if (state.getTestSet().getPlan() != null &&
Expand All @@ -354,9 +352,21 @@ public void parseLine(String tapLine) {
}

state.getTestSet().addTestResult(testResult);
if (state.looseSubtests != null && this.enableSubtests) {
testResult.setSubtest(state.looseSubtests);
state.looseSubtests = null;

if (prevState != null && enableSubtests) {
state.getTestSet().getTestResults().get(
state.getTestSet().getNumberOfTestResults() - 1)
.setSubtest(prevState.getTestSet());
}

if (indentation == 0 && enableSubtests) {
TestResult currLast = state.getTestSet().getTestResults().get(
state.getTestSet().getNumberOfTestResults() - 1);
while (!subStack.empty()) {
StreamStatus nextLevel = subStack.pop();
currLast.setSubtest(nextLevel.getTestSet());
currLast = nextLevel.getTestSet().getTestResults().get(0);
}
}

} else if (tapElement instanceof Footer) {
Expand Down
14 changes: 7 additions & 7 deletions tap4j/src/main/java/org/tap4j/representer/Tap13Representer.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ protected void printTapLine(PrintWriter pw, TapElement tapResult) {
* @param testResult TAP test result
*/
protected void printTestResult(PrintWriter pw, TestResult testResult) {
if (testResult.getSubtest() != null) {
int indent = this.options.getIndent();
int spaces = this.options.getSpaces();
this.options.setIndent(indent + spaces);
pw.append(this.representData(testResult.getSubtest()));
this.options.setIndent(indent);
}
printFiller(pw);
pw.append(testResult.getStatus().toString());
pw.append(' ' + Integer.toString(testResult.getTestNumber()));
Expand Down Expand Up @@ -155,13 +162,6 @@ protected void printTestResult(PrintWriter pw, TestResult testResult) {
}
printDiagnostic(pw, testResult);
pw.append(LINE_SEPARATOR);
if (testResult.getSubtest() != null) {
int indent = this.options.getIndent();
int spaces = this.options.getSpaces();
this.options.setIndent(indent + spaces);
pw.append(this.representData(testResult.getSubtest()));
this.options.setIndent(indent);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public class TestIssue3504508 extends BaseTapTest {
@Test
public void testTapConsumer() {
final TestSet testSet = getTestSet(new Tap13Parser(/* enable subtests*/ true), "/org/tap4j/consumer/issue3504508/sample.tap");
assertTrue(testSet.getTestResult(1).getSubtest() != null);
assertTrue(testSet.getTestResult(1).getSubtest()
assertTrue(testSet.getTestResult(1).getSubtest() == null);
assertTrue(testSet.getTestResult(2).getSubtest()
.getTestResult(2).getSubtest() != null);
assertTrue(testSet.getTestResults().size() == 3);
}
Expand All @@ -59,10 +59,10 @@ public void testProducingSubtests() {
+ "ok 1 - First test\n"
+ " 1..2\n"
+ " ok 1 - This is a subtest\n"
+ " ok 2 - So is this\n"
+ " 1..2\n"
+ " 1..2\n"
+ " ok 1 - This is a subtest\n"
+ " ok 2 - So is this\n"
+ " ok 2 - So is this\n"
+ " ok 2 - So is this\n"
+ "ok 2 - An example subtest\n"
+ "ok 3 - Third test\n";
final TapConsumer consumer = TapConsumerFactory.makeTap13YamlConsumer();
Expand Down
Loading