# Here we store the map of annotations internal names and the annotation actually used during test creation
# The reason we do that is to allow annotations to be easily renamed without modifying rest of the code
# Functions with no annotations or with annotations not on the list are rejected during module evaluation
# test and test-skip annotations may be used multiple times throughout the module as the function names are stored in a list
# Other annotations should only be used once within a module file
# If you find yourself in need of multiple before- or after- functions it's a sign your test suite probably needs redesign
def valid-annotations [] {
{
"#[test]": "test",
"#[ignore]": "test-skip",
"#[before-each]": "before-each"
"#[before-all]": "before-all"
"#[after-each]": "after-each"
"#[after-all]": "after-all"
}
}
# Returns a table containing the list of function names together with their annotations (comments above the declaration)
#
# The way this works is we first generate an ast for a file and then extract all tokens containing keyword def with internalcall shape
# Then for each token we extract the next token (the actual function name) and the previous token (usually closing bracket of previous function)
# We then open the file as raw text and extract the last line of substring starting from the span end of previous token up to span start of the def token which contains the annotation and surrounding whitespaces
# We take the last line only in order to not pick up unrelated comments from the source code
def get-annotated [
file: path
] {
let raw_file = (open $file)
let ast = (
^$nu.current-exe --ide-ast $file
| from json
| enumerate
| flatten
)
$ast
| where content == def and shape == shape_internalcall
| insert function_name {|x|
$ast
| get ($x.index + 1)
| get content
}
| insert annotation {|x|
# index == 0 means the function is defined on the first lines of the file in which case there will be no preceding tokens
# Test list can be filtered out by specifying either path to search for, name of the module to run tests for or specific test name
# In order for a function to be recognized as a test by the test runner it needs to be annotated with # test
# Following annotations are supported by the test runner:
# * test - test case to be executed during test run
# * test-skip - test case to be skipped during test run
# * before-all - function to run at the beginning of test run. Returns a global context record that is piped into every test function
# * before-each - function to run before every test case. Returns a per-test context record that is merged with global context and piped into test functions
# * after-each - function to run after every test case. Receives the context record just like the test cases
# * after-all - function to run after all test cases have been executed. Receives the global context record
| filter {|x| ($x.commands|length) > 0 and ($x.commands.annotation|any {|y| $y in (valid-annotations|columns)})} # if file has no private functions at all or no functions annotated with a valid annotation we drop it