This short primer shows how to use tests as a basis for language development with Spoofax. As an example project we create a small 'calculator' language that shows many of the basics of language definitions. The source for our example project is collected in [[%ATTACHURL%/before.zip][before.zip]]. The complete project, with all functionality implemented to make the test cases succeed is collected in [[%ATTACHURL%/after.zip][after.zip]]. A full description of the testing language can be found in the paper [[http://researchr.org/publication/KatsVermaasVisser2011][Integrated Language Definition Testing. Enabling Test-Driven Language Development]]. Presentation slides also show Spoofax testing in action: [[http://slidesha.re/tYaHQT]] %TOC{depth="3"}% ---++ A Calculator Language Our example language supports arithmetic expressions, variables, and assignments. An example: a = 3 a * 4 Based on this simple language we can write test cases. Doing test-driven development, these can even drive the development of the language, but in this document we focus on the tests. Tests can be written using a =.spt= file. Create one in a Spoofax project, and press control-space to get a basic test definition. It will have tests of this form: test description [[ a = 3 a * 4 ]] 0 errors As the example shows, each test can quote a program using =[[=, =[[[= or =[[[[= brackets. It can also specify a _description_ for the test, and a condition. This test specifies that =0= semantic =errors= are expected. ---++ Syntax Example of tests of the syntax: test Add [[ 1 + 2 ]] parse succeeds test Abstract syntax (1) [[ 1 ]] parse to Int("1") test Abstract syntax (2) [[ 1 * 2 ]] parse to Mul(Int("1"), _) test Parentheses [[ (1 + 2) ]] These tests specify parse success, compare the abstract syntax of the test input against a pattern, or simply specify that the test should succeed. Tests can also use concrete syntax to test operator precedence and associativity: test Multiply and add (1) [[ 1 + 2 * 3 ]] parse to Add(_, Mul(_, _)) test Multiply and add (2) [[ 1 + 2 * 3 ]] parse to [[ 1 + (2 * 3) ]] test Add and multiply [[ 1 * 2 + 3 ]] parse to [[ (1 * 2) + 3 ]] test Add and add [[ 1 + 2 + 3 ]] parse to [[ (1 + 2) + 3 ]] ---++ Evaluation of expressions The following tests will =run= a transformation named =calc= to test evaluation of expressions: test Constant [[ 1 ]] run calc to "1" test Add [[ 1 + 1 ]] run calc to "2" test Subexpression [[ 1 + [[2 + 3]] ]] run calc to "5" Note that the last test in this series uses the =[[= ... ]] brackets to make a *selection* in the test. The =calc= transformation is evaluated for this selection. ---++ Variables The following tests exercise the definition of variables: test Variable [[ x ]] parse test Variable [[ longname ]] parse test Assignment [[ x = 4 ]] parse test Multiple statements [[ x = 1 y = 2 ]] parse to Statements([Assign(_, _), Assign(_, _)]) Stm* -> Start {cons("Statements")} ID "=" Exp -> Stm {cons("Assign")} Exp -> Stm ID -> Exp {cons("Var")} test Evaluate multiple statements [[ 1 2 ]] run calc to "2" calc: Statements(s*) -> last where s'* := s*; last := s'* test Eval constant [[ PI ]] run calc to "3.14" calc: Var("PI") -> "3.14" test Eval multiple variables [[ x = 2 y = x * 2 + x y ]] run calc to "6" ---++ Editor features The following test cases test the editor facilities of the language: test Variable unassigned [[ y ]] 1 error /unassigned/ This test succeeds if the input has =1 error= and it matches the regular expression =/unassigned/= (as variable =y= is unassigned). test Multiple assignments to same variable [[ y = 1 y = 2 y ]] /multiple/ This test succeeds if there are one or more (error) messages that match =/multiple/= (as there are _multiple_ definitions of =y=). test Reference resolving (1) [[ x = 4 [[x]] ]] resolve test Reference resolving (2) [[ [[x]] = 4 [[x]] ]] resolve #2 to #1 test Content completion [[ avariable = 1 [[a]] ]] complete to "avariable" These test cases test reference resolving and content completion. They use the =[[= ... ]] selection mechanic to select parts of the program. The first test case specifies that reference resolving should work for the selection. The second specifies that the second selection should resolve to the first selection. The last test case specifies that the selection should provide a content completion option =avariable=. ---++ Execution The =Calculang= project generates Java code. The following test case tests the output that is returned when the program is compiled and executed: test 42 [[ 42 ]] build generate-result to 42 ---++ Refactoring Refactorings are a form of transformations that can be triggered in the editor based on a selection. These test cases test the rename refactoring of Calculang: test Basic rename [[ a = 1 [[a]] ]] refactor rename-var("b") to [[ b = 1 b ]] test Another rename [[ a = 1 b = 2 [[a]] ]] refactor rename-var("c") to [[ c = 1 b = 2 c ]] test Rename collision [[ a = 1 b = 1 [[a]] ]] refactor rename-var("b") to _ 1 error Note how these tests specify the new name as an argument of the `rename-var` refactoring. Also note the last test which is a negative test case: the refactoring should report an error if a user attempts to rename =a= to =b=. rename-var: (newname, selected-name, position, ast, path, project-path) -> ([(ast, new-ast)], fatal-errors, errors, warnings) with new-ast := ast; (errors, warnings) := (ast, new-ast); fatal-errors := [] rename-type(|old-name, new-name): Assign(old-name, y) -> Assign(new-name, y) rename-type(|old-name, new-name): Var(old-name) -> Var(new-name) semantic-constraint-issues: (ast, new-ast) -> ((new-errors, errors), (new-warnings, warnings)) where (_, errors, warnings, _) := (ast, "", ""); (_, new-errors, new-warnings, _) := (new-ast, "", "")