Testing a PetitParser class

TL;DR

Once you have a parser defined as a class, it is a good idea to establish test cases for every single rule, both as regression tests, and as test cases for further development. We show, using the PetitParser SPL case study, how to do this.

Note that we have first fully developed a parser as a script and are now turning our scripted tests into examples. An alternative approach would have been to turn an early version of the scripted parser into a class, to enable TDD for further development. See also Example-driven development by example.

Testing grammar rules

In GT, test cases are implemented as Examples, that is, methods that perform assertions on an example object returned by the example method.

Defining the Example test class

To create test examples for a PetitParser class called MyParser, you should define MyParserExamples. This may be a subclass of Object ProtoObject subclass: #Object instanceVariableNames: '' classVariableNames: 'DependentsFields' package: 'Kernel-Objects' , but even better is to define it as a subclass of PP2CompositeNodeExamples Object subclass: #PP2CompositeNodeExamples instanceVariableNames: 'parserInstance' classVariableNames: '' package: 'PetitParser2-Tests-Core' . This class provides several utility methods that allow one to test that a given input can be parsed by a particular rule, and will fail with other rules.

For our SPLGrammar PP2CompositeNode subclass: #SPLGrammar instanceVariableNames: 'keyword identifier boolean integer float number string varDecl primary unary factor term comparison equality logicAnd logicOr assignment expression exprStmt printStmt ifStmt whileStmt block statement declaration program parenthesizedExpression negatedUnary assignmentExpression comment ignorable' classVariableNames: '' package: 'GToolkit-Demo-SPL-PetitParser' parser class, we define SPLGrammarExamples PP2CompositeNodeExamples subclass: #SPLGrammarExamples instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Demo-SPL-Examples' .

You should also define a parserClass method returning the class to be used to parse the examples. In our case it is SPLGrammarExamples>>#parserClass parserClass ^ SPLGrammar

Defining test examples

Each test is an example method, so must be defined with a <gtExample> pragma.

The most basic tests just check that an input is parsed by a given rule, and then return the result.

For example, we test that the integer rule works using the PP2CompositeNodeExamples>>#parse:rule: parse: aString rule: aSymbol ^ self parse: aString rule: aSymbol end: aString size method.

integer42
	<gtExample>
	^ self parse: '42' rule: #integer
    

We might like to assert that a given input will be parsed by some rules and not others. For this we use PP2CompositeNodeExamples>>#fail:rule: fail: aString rule: aSymbol | production context result | production := self parserInstanceFor: aSymbol. context := self context. result := production parse: aString withContext: context. self assert: (result isPetit2Failure or: [context atEnd not]) description: 'Able to parse ' , aString printString. ^ result :

threeTimesFour
	<gtExample>
	| input |
	input := '3*4'.
	self parse: input rule: #expression.
	self fail: input rule: #unary.
	^ self parse: input rule: #factor
    

Examples can also be composed. Most of the grammar tests have unique inputs, but here is an example of a composed example:

whileProgram
	<gtExample>
	| input |
	input := self whileProgramSource.
	self fail: input rule: #declaration.
	self fail: input rule: #statement.
	^ self parse: input rule: #program