Skip to content

Commit

Permalink
Merge pull request #24 from gilcesarf/master
Browse files Browse the repository at this point in the history
- Solve issues #9 and #23.
  • Loading branch information
teverett authored May 9, 2020
2 parents 3615984 + c8d574f commit 96b52fc
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 6 deletions.
46 changes: 45 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Maven Mojo for testing [Antlr4](http://www.antlr.org/) Grammars
```xml
<groupId>com.khubla.antlr</groupId>
<artifactId>antlr4test-maven-plugin</artifactId>
<version>1.11</version>
<version>1.14</version>
<packaging>jar</packaging>
```

Expand Down Expand Up @@ -233,3 +233,47 @@ The following two configurations are completely equivalent.
</configuration>
</plugin>
```

## Checking parsed tree

Sometimes you want to assure that a given input file generates an specific parsed tree. To do so, you can create sibling files to the parsed examples provided adding the .tree extension to the full name of the parsed file to ask the plugin to check if the parsed tree obtained from the file matchs an expected tree.

For example, if you have an 'examples/fileToBeParsed.txt' file, you can create a 'examples/fileToBeParsed.txt.tree' file containing the LISP style tree expected to be parsed from you file.

The checking is done by taking the parsed tree and transforming it to LISP style tree with toStringTree(parserRuleContext, parser) of class org.antlr.v4.runtime.tree.Trees. If they match, everything is fine. If don't, a Diff is generated to show where differences was found.

Here is the code snippet that checks the parsed tree against the expected one:

```java
final File treeFile = new File(grammarFile.getAbsolutePath() + GrammarTestMojo.TREE_SUFFIX);
if (treeFile.exists()) {
final String lispTree = Trees.toStringTree(parserRuleContext, parser);
if (null != lispTree) {
final String treeFileData = FileUtils.fileRead(treeFile, scenario.getFileEncoding());
if (null != treeFileData) {
if (0 != treeFileData.compareTo(lispTree)) {
StringBuilder sb = new StringBuilder(
"Parse tree does not match '" + treeFile.getName() + "'. Differences: ");
for (DiffMatchPatch.Diff diff : new DiffMatchPatch().diffMain(treeFileData, lispTree)) {
sb.append(diff.toString());
sb.append(", ");
}
throw new Exception(sb.toString());
} else {
log.info("Parse tree for '" + grammarFile.getName() + "' matches '" + treeFile.getName() + "'");
}
}
}
}
```

A tip to use this feature is to configure the plugin to shows the parsed tree using **<showTree>true</showTree>** option. Then you can check the log output manually to see if the generated tree is the expected one. If it is ok, you can copy/paste the parsed tree (only the parsed tree with no other message accessories) to the .tree file.

## Checking expected errors

Sometimes you want to check if specific input files generate parsing errors. This can be done with the plugin by adding a new file sibling to the parsed one with .errors extension. Each line is interpreted as an expected parsing error, and the message in the file is compared to the parsed results. If they match, everything is ok, otherwise a test error is raised.

For example, if you have an 'examples/fileToBeParsed.txt' file, you can create a 'examples/fileToBeParsed.txt.errors' file containing the expected parse error messages.

A tip to use this feature is to configure the plugin to shows the errors using **<verbose>true</verbose>** option. Then you can check the log output manually to see exact error message generated and copy/paste the error messages (only the parsed tree with no other message accessories) to the .errors file.

7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<properties>
<maven.version>3.6.0</maven.version>
<antlr.version>4.8-1</antlr.version>
<org.easymock.version>3.6</org.easymock.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -111,6 +112,12 @@
<artifactId>diff-match-patch</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>${org.easymock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.apache.maven.plugin.logging.Log;

public class AssertErrorsErrorListener extends BaseErrorListener {
protected static final String CHAR_CR_PLACEHOLDER_REGEXP = "\\\\r"; // to match \r
Expand All @@ -48,10 +49,21 @@ public class AssertErrorsErrorListener extends BaseErrorListener {
protected static final String LITERAL_BACKSLASH_N = "\\\\n";
protected List<String> errorMessages = new ArrayList<>();

private Scenario scenario = null;
private Log log = null;

public AssertErrorsErrorListener(Scenario scenario, Log log) {
this.scenario = scenario;
this.log = log;
}

public void assertErrors(File errorMessagesFile, String encoding) throws AssertErrorsException {
if (!errorMessages.isEmpty()) {
List<String> expectedErrorMessages = null;
String errorMessageFileName = errorMessagesFile.getName();
String errorMessageFileName = null;
if (errorMessagesFile != null ) {
errorMessageFileName = errorMessagesFile.getName();
}
try {
expectedErrorMessages = FileUtil.getNonEmptyLines(errorMessagesFile, encoding);
} catch (final FileNotFoundException ex) {
Expand All @@ -61,7 +73,7 @@ public void assertErrors(File errorMessagesFile, String encoding) throws AssertE
}
asserts(expectedErrorMessages, errorMessageFileName);
} else {
if (errorMessagesFile.exists()) {
if (errorMessagesFile != null && errorMessagesFile.exists()) {
throw new AssertErrorsException(String.format("no errors found, but errors file exists %s", errorMessagesFile.getAbsolutePath()));
}
}
Expand Down Expand Up @@ -106,6 +118,9 @@ protected String replacePlaceholders(String pString) {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
final String errorMessage = String.format("line %d:%d %s", line, charPositionInLine, msg);
if (this.scenario.isVerbose()) {
log.warn(errorMessage);
}
errorMessages.add(errorMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ private void testGrammar(Scenario scenario, File grammarFile) throws Exception {
/*
* build lexer
*/
final AssertErrorsErrorListener assertErrorsErrorListener = new AssertErrorsErrorListener();
final AssertErrorsErrorListener assertErrorsErrorListener = new AssertErrorsErrorListener(this.scenario, this.log);
Lexer lexer = (Lexer) lexerConstructor.newInstance(antlrFileStream);
lexer.addErrorListener(assertErrorsErrorListener);
/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,50 @@ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
*/
package com.khubla.antlr.antlr4test;

import java.io.File;
import java.util.Collections;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;
import static org.easymock.EasyMock.*;

import org.apache.maven.plugin.logging.Log;

/**
*
* @author mario.schroeder
*/
public class AssertErrorsErrorListenerTest {

private AssertErrorsErrorListener classUnderTest;
private Scenario scenario = null;
private Log log = null;
private AssertErrorsErrorListener classUnderTest = null;

@After
public void tearDown() {
this.scenario = null;
this.log = null;
this.classUnderTest = null;
}

@Before
public void setUp() {
classUnderTest = new AssertErrorsErrorListener();
this.scenario = mock(Scenario.class);
this.log = mock(Log.class);
classUnderTest = new AssertErrorsErrorListener(scenario, log);
}

/**
* Test of replacePlaceholders method, of class AssertErrorsErrorListener.
*/
@Test
public void testReplacePlaceholders_Unchanged() {
replay(this.scenario);
replay(this.log);
String line = "mismatched input '\\n' expecting 'foo'";
List<String> lines = Collections.singletonList(line);
classUnderTest.replacePlaceholders(lines);
Expand All @@ -63,6 +82,9 @@ public void testReplacePlaceholders_Unchanged() {
*/
@Test
public void testAssertErrors_Matching() throws AssertErrorsException {
expect(this.scenario.isVerbose()).andReturn(false).times(1);
replay(this.scenario);
replay(this.log);
String line = "mismatched input '\\n' expecting 'foo'";
classUnderTest.syntaxError(null, null, 1, 7, line, null);

Expand All @@ -72,4 +94,196 @@ public void testAssertErrors_Matching() throws AssertErrorsException {
assertEquals(lines.get(0), expected);
}

/**
* Test of assertErrors method with empty file and an error.
*/
@Test
public void testAssertErrors_EmptyFile_Errors() {
expect(this.scenario.isVerbose()).andReturn(false).times(1);
replay(this.scenario);
replay(this.log);
File errorMessagesFile = new File ( ClassLoader.getSystemResource( "./SampleErrorsEmptyFile.errors").getFile() );
String line = "mismatched input '\\n' expecting 'foo'";
classUnderTest.syntaxError(null, null, 1, 7, line, null);
try
{
classUnderTest.assertErrors( errorMessagesFile, "UTF-8" );
fail("Expected exception AssertErrorsException does not thrown");
}
catch ( AssertErrorsException e )
{
assertEquals("SampleErrorsEmptyFile.errors : expected 0 errors, but was 1 errors", e.getMessage());
}
}

/**
* Test of assertErrors method with empty file and no errors.
*/
@Test
public void testAssertErrors_EmptyFile_NoErrors() {
replay(this.scenario);
replay(this.log);
File errorMessagesFile = new File ( ClassLoader.getSystemResource( "./SampleErrorsEmptyFile.errors").getFile() );
try
{
classUnderTest.assertErrors( errorMessagesFile, "UTF-8" );
fail("Expected exception AssertErrorsException does not thrown");
}
catch ( AssertErrorsException e )
{
assertTrue(e.getMessage().startsWith( "no errors found, but errors file exists" ));
assertTrue(e.getMessage().endsWith( "SampleErrorsEmptyFile.errors" ));
}
}

/**
* Test of assertErrors method with non existing file and no errors.
*/
@Test
public void testAssertErrors_NonExistingFile_NoErrors() {
replay(this.scenario);
replay(this.log);
File errorMessagesFile = null;
try
{
classUnderTest.assertErrors( errorMessagesFile, "UTF-8" );
}
catch ( Exception e )
{
e.printStackTrace();
fail("Unexpected exception thrown: " + e.getMessage());
}
}

/**
* Test of assertErrors method with non existing file and no errors.
*/
@Test
public void testAssertErrors_NullFile_Errors() {
expect(this.scenario.isVerbose()).andReturn(false).times(1);
replay(this.scenario);
replay(this.log);
File errorMessagesFile = null;
String line = "mismatched input '\\n' expecting 'foo'";
classUnderTest.syntaxError(null, null, 1, 7, line, null);
try
{
classUnderTest.assertErrors( errorMessagesFile, "UTF-8" );
fail("Expected exception AssertErrorsException does not thrown");
}
catch ( NullPointerException e )
{
assertNull(e.getMessage());
}
catch ( Exception e )
{
fail("Unexpected exception thrown: " + e.getMessage());
}
}

/**
* Test of assertErrors method with non existing file and no errors.
*/
@Test
public void testAssertErrors_NonExistingFile_Errors() {
expect(this.scenario.isVerbose()).andReturn(false).times(1);
replay(this.scenario);
replay(this.log);
File errorMessagesFile = new File("Foo.bar");
String line = "mismatched input '\\n' expecting 'foo'";
classUnderTest.syntaxError(null, null, 1, 7, line, null);
try
{
classUnderTest.assertErrors( errorMessagesFile, "UTF-8" );
fail("Expected exception AssertErrorsException does not thrown");
}
catch ( AssertErrorsException e )
{
assertEquals("found 1 errors, but missing file Foo.bar", e.getMessage());
}
catch ( Exception e )
{
fail("Unexpected exception thrown: " + e.getMessage());
}
}

/**
* Test of assertErrors method with non existing file and no errors.
*/
@Test
public void testAssertErrors_Mismatching_Errors() {
expect(this.scenario.isVerbose()).andReturn(false).times(2);
replay(this.scenario);
replay(this.log);
File errorMessagesFile = new File ( ClassLoader.getSystemResource( "./SampleErrorsFileNonEmpty.errors").getFile() );
String line = "mismatched input '\\n' expecting 'foo'";
classUnderTest.syntaxError(null, null, 1, 7, line, null);
classUnderTest.syntaxError(null, null, 13, 5, line, null);
try
{
classUnderTest.assertErrors( errorMessagesFile, "UTF-8" );
fail("Expected exception AssertErrorsException does not thrown");
}
catch ( NullPointerException e )
{
assertNull(e.getMessage());
}
catch ( AssertErrorsException e )
{
assertEquals("SampleErrorsFileNonEmpty.errors : expected (line 2:8 mismatched input '\n" +
"' expecting 'foo'), but was (line 13:5 mismatched input '\n" +
"' expecting 'foo')", e.getMessage());
}
catch ( Exception e )
{
fail("Unexpected exception thrown: " + e.getMessage());
}
}

/**
* Test of assertErrors method with non existing file and no errors.
*/
@Test
public void testAssertErrors_Matching_Errors() {
expect(this.scenario.isVerbose()).andReturn(false).times(2);
replay(this.scenario);
replay(this.log);
File errorMessagesFile = new File ( ClassLoader.getSystemResource( "./SampleErrorsFileNonEmpty.errors").getFile() );
String line = "mismatched input '\\n' expecting 'foo'";
classUnderTest.syntaxError(null, null, 1, 7, line, null);
classUnderTest.syntaxError(null, null, 2, 8, line, null);
try
{
classUnderTest.assertErrors( errorMessagesFile, "UTF-8" );
}
catch ( Exception e )
{
fail("Unexpected exception thrown: " + e.getMessage());
}
}
/**
* Test of assertErrors method with non existing file and no errors.
*/
@Test
public void testAssertLog() {
File errorMessagesFile = new File ( ClassLoader.getSystemResource( "./SampleErrorsFileNonEmpty.errors").getFile() );
String line = "mismatched input '\\n' expecting 'foo'";
expect(this.scenario.isVerbose()).andReturn(true).times(2);
this.log.warn("line 1:7 mismatched input '\\n' expecting 'foo'");
expectLastCall().times(1);
this.log.warn("line 2:8 mismatched input '\\n' expecting 'foo'");
expectLastCall().times(1);
replay(this.scenario);
replay(this.log);
classUnderTest.syntaxError(null, null, 1, 7, line, null);
classUnderTest.syntaxError(null, null, 2, 8, line, null);
try
{
classUnderTest.assertErrors( errorMessagesFile, "UTF-8" );
}
catch ( Exception e )
{
fail("Unexpected exception thrown: " + e.getMessage());
}
}
}
Empty file.
2 changes: 2 additions & 0 deletions src/test/resources/SampleErrorsFileNonEmpty.errors
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
line 1:7 mismatched input '\n' expecting 'foo'
line 2:8 mismatched input '\n' expecting 'foo'

0 comments on commit 96b52fc

Please sign in to comment.