6.6 KiB
Tutorial: Configure tests
Riju has far too many languages to rely on manual testing to ensure that features are not broken by upstream updates. As such, we have a system to automatically generate test cases which are validated whenever language configuration changes.
Here are the tests that can be configured for each language:
run(mandatory for all languages): Verify that thetemplatecode printsHello, world!when run using theruncommand.repl(mandatory for all languages withrepl): Verify that submitting123 * 234to thereplcommand causes28782to be printed.runrepl(mandatory for all languages withrepl): Same asrepl, but using theruncommand instead of thereplcommand (i.e., test that theruncommand starts a REPL after executingmain).scope(optional, only for languages with variable-scope-preservingrepl): Verify that if a variable assignment is appended tomain, then that variable can be evaluated from the REPL using theruncommand.format(mandatory for all languages withformat): Verify that a misformatted version of thetemplatecode is correctly formatted back to its canonical form when executingformat.run.lsp(mandatory for all languages withlsp): Verify that the language server produces a given autocompletion in a given context.ensure(optional): Verify that a specific shell command executes successfully. This is currently unused.
Test configuration
See jsonschema.yaml for full
documentation.
run- If your language can't be made to print exactly
Hello, world!, specify the actual output using thehellokey.- In extraordinary circumstances, a language may be unable to
produce deterministic output (e.g. Entropy). In such cases,
hellocan also be a JavaScript-compatible regular expression, and you must specifyhelloMaxLengthwhich is the maximum possible length of theHello, worldoutput matched by the regex.
- In extraordinary circumstances, a language may be unable to
produce deterministic output (e.g. Entropy). In such cases,
- Some languages require user input at the REPL to run the code,
despite our best efforts to the contrary. In this case, you can
specify the required input in the
helloInputkey. (See "Specifying user input" below.) - If a language doesn't have
repl, then theruncommand is expected to terminate after executing user code. By default the expected exit status is 0, and theruntest will fail otherwise. If for some reason your language exits with a nonzero status even in the absence of an error, then you can specify the expected status in thehelloStatuskey.
- If your language can't be made to print exactly
repl- We try to compute
123 * 234in most languages' REPLs. Naturally, the syntax may vary depending on the language, so you can specify an alternate input using theinputkey. (See "Specifying user input" below.) - The result of
123 * 234is generally28782. In the case that we get the output in some other format, you can specify the expected output using theoutputkey.
- We try to compute
runrepl- In the case that
inputneeds to be different forrunreplthan forrepl, you can override it specifically forrunreplusing therunReplInputkey. - In the case that
outputneeds to be different forrunreplthan forrepl, you can override it specifically forrunreplusing therunReplOutputkey.
- In the case that
scope- Required: In
scope.code, specify the code that is needed to assign123 * 234to a local variable namedx, or as close to that as the language can manage. For example, in Python, this would bex = 123 * 234. - By default,
scope.codeis appended at the end oftemplate. However, if it needs to go in the middle, you can specifyscope.after, which should match an entire line oftemplate. Thenscope.codewill be placed on the next list afterscope.after. - By default, it's expected that typing
xinto the REPL will produce28782. You can override the input to something else by specifyingscope.input. (See "Specifying user input" below.) - If the expected output is something other than
28782, you can override it usingscope.output.
- Required: In
format- Required: In
format.input, specify the input code. This should be distinct fromtemplate, but should turn intotemplatewhen theformatcommand is run on it. - In the case that you can't come up with input that formats to
template, you can specifyformat.outputas the expected result of formattingformat.input.
- Required: In
lsp- Required: In
lsp.code, specify input (not necessarily an entire line) that we should pretend the user has typed. We expect an autocompletion to be presented with the cursor at the end of this input. - Required: In
lsp.item, specify the text of an autocompletion that we expect the language server to generate. This should not match any text that actually appears intemplateorlsp.code. - In
lsp.after, you can specify a string that will match exactly one place intemplate. This is where the cursor will be positioned beforelsp.codeis inserted. Iflsp.afteris not specified, thenlsp.codewill be inserted at the end oftemplateon a new line.
- Required: In
ensure- If you want to use an
ensuretest, just supply the shell command using theensurekey.
- If you want to use an
Specifying user input
We have a couple different formats for common types of user input.
This will type eval and send a newline:
input: |
eval
This will type eval, send a newline, then type go and send another
newline:
input: |
eval
go
This will type foo, send a newline, wait 1 second, then type bar
and send another newline:
input: |
foo
DELAY: 1
bar
Why is this useful? Unfortunately, many languages have race conditions and will fail to notice input if you send it before they have finished starting up.
Finally, this will type foo and then send a newline followed by an
EOF:
input: |
foo
EOF
Broken tests
We try very hard to get tests working for every newly added language. However, sometimes there's something truly puzzling going on that's not worth blocking a new language being added. For that reason it's possible to mark a test as temporarily skipped, e.g.:
skip:
- repl
- runrepl
- scope
- lsp
This is unfortunately currently the case for many of the LSP tests due to the fragility of most language servers.