Testing antlr4 visitor rules

1.8k views Asked by At

I've got a fairly complicated antlr4 grammar that utilizes a visitor pattern. I'd like to test parts of the visitor. What's a good way to test individual visit rules?

My visitor has tons of rules like this that I want to test:

@Override
public Object visitQux(ExclParser.QuxContext ctx) {
  return visitChildren(ctx);
}

And my test code basically is the following:

PrintStream ps = new PrintStream(stdError, false /* autoFlush */, "UTF-8")
ANTLRInputStream input = new ANTLRInputStream(is);

MyLexer lexer = new MyLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
MyParser parser = new MyParser(tokens);

parser.removeErrorListeners();
MyErrorListener errorListener = new MyErrorListener(ps, filename);
parser.addErrorListener(errorListener);

MyVisitor visitor = new MyVisitor();
visitor.setParser(filename, parser, errorListener);
ParseTree tree = parser.qux();   // <--- This is the line I want to vary.
Object result = visitor.visit(tree);
assertThat(describeExpectation(), result, equalTo(this.expectedOutput));

Ideally I would be able to test any visitor using a parameterized test. But to get the parse tree I want to visit (parser.qux) I can't specify any variant of qux() in a table because parser.qux() is not static.

Any thoughts?

2

There are 2 answers

0
Epsilon Prime On

Reflection may be the right answer here:

Method method = MyParser.class.getDeclaredMethod("qux");
ParseTree tree = (ParseTree) method.invoke(parser);

is a suitable replacement for:

ParseTree tree = parser.qux();
0
yankee On

I created an example project on how ANTLR-visitors could be tested (using Mockito&TestNG). The full source code can be found on GitHub. Here are the most important parts:

public class MyVisitor extends DemoBaseVisitor<String> {
    @Override
    public String visitPlus(final DemoParser.PlusContext ctx) {
        return visit(ctx.left) + " PLUS " + visit(ctx.right);
    }

    @Override
    public String visitLiteralNumber(final DemoParser.LiteralNumberContext ctx) {
        return ctx.getText();
    }
}

Any my test for that visitor:

public class MyVisitorTest {
    private final MyVisitor myVisitor = new MyVisitor();

    @Test
    public void visitPlus_joinsOperatorsWithWordPLUSAsSeparator() throws Exception {
        // setup
        final DemoParser.PlusContext plusNode = mock(DemoParser.PlusContext.class);
        plusNode.left = mockForVisitorResult(DemoParser.ExpressionContext.class, "2");
        plusNode.right = mockForVisitorResult(DemoParser.ExpressionContext.class, "4");

        // execution
        final String actual = myVisitor.visitPlus(plusNode);

        // evaluation
        assertEquals(actual, "2 PLUS 4");
    }

    private<T extends RuleContext> T mockForVisitorResult(final Class<T> nodeType, final String visitResult) {
        final T mock = mock(nodeType);
        when(mock.accept(myVisitor)).thenReturn(visitResult);
        return mock;
    }

    @Test
    public void visitLiteralNumber_returnsTextValueOfNumber() throws Exception {
        // setup
        final DemoParser.LiteralNumberContext literalNumberNode = mock(DemoParser.LiteralNumberContext.class);
        when(literalNumberNode.getText()).thenReturn("42");

        // execution
        final String actual = myVisitor.visitLiteralNumber(literalNumberNode);

        // evaluation
        assertEquals(actual, "42");
    }
}

In your special example where you have a method which calls visitChildren() your would test that calling that method returns the aggregated result of visiting all child nodes (what the aggregation is depends on your implementation of the aggregateResult method).