diff --git a/spec/ysh-stdlib-testing.test.sh b/spec/ysh-stdlib-testing.test.sh index 4e5b391adc..c8a0c2f093 100644 --- a/spec/ysh-stdlib-testing.test.sh +++ b/spec/ysh-stdlib-testing.test.sh @@ -1,91 +1,88 @@ ## our_shell: ysh -## oils_failures_allowed: 5 -#### value.Expr test - positional test +#### Test framework source --builtin testing.ysh +setglobal _test_use_color = false -echo 'parens' -test-expr (42 + 1) -echo - -echo 'brackets' -test-expr [42 + 1] -echo - -echo 'expr in parens' -test-expr (^[42 + 1]) -echo - -## STDOUT: -## END - -#### value.Expr test - named test - -source --builtin testing.ysh - -echo 'parens' -test-named (n=42 + 1) -echo - -echo 'brackets' -test-named [n=42 + 1] -echo - -echo 'expr in parens' -test-named (n=^[42 + 1]) -echo - -echo 'no value' -test-named -echo - -## STDOUT: -## END - -#### assert builtin - -source --builtin testing.ysh # get rid of this line later? - -var x = 42 - -# how do you get the code string here? - -assert [42 === x] +test-suite "three bad tests" { + test-case "the assertion is false" { + assert [1 > 0] + assert [1 > 1] + assert [1 > 2] + } -assert [42 < x] + test-case "there is an exception while evaluating the assertion" { + assert ["Superman" > "Batman"] + } -#assert [42 < x; fail_message='message'] + test-case "there is an exception outside of any assertion" { + error "oops!" + } +} -#assert (^[(42 < x)], fail_message='passed message') +test-case "one good test case" { + assert [1 === 1] + assert [2 === 2] +} -# BUG -assert [42 < x, fail_message='passed message'] +run-tests ## STDOUT: +begin three bad tests + test the assertion is false ... + assertion FAILED + test there is an exception while evaluating the assertion ... + assertion ERRORED: 10 + test there is an exception outside of any assertion ... + ERROR: 10 +end +test one good test case ... ok +3 / 4 tests failed ## END -#### ysh --tool test file +#### Test framework: nested test suites -cat >mytest.ysh < out 2>& err - } - assert (_status === 42) - - cat out - cat err - - # Oh man the here docs are still useful here because of 'diff' interface - # Multiline strings don't quite do it - - diff out - <<< ''' - STDOUT - ''' - - diff err - <<< ''' - STDERR - ''' + try { + p > out 2> err } + + assert [_status === 42] + assert [$(mytest.ysh <append(name) +} - # TODO: - # - need append - # - need :: - # _ _describe->append(cmd) - # - # Need to clean this up - # append (_describe, cmd) # does NOT work! +proc _end-test-suite { + call _test_suite_stack->pop() +} - call _describe_list->append(block) +proc _test-print-indented (msg) { + for _ in (0 .. _test_suite_depth) { + printf " " + } + echo "$msg" } -proc Args { - echo TODO +proc _run-test-suite (; suite) { + for name, elem in (suite) { + if (type (elem) === "Dict") { + # It's another suite. + var begin = _cyan("begin") + _test-print-indented "$begin $name" + setglobal _test_suite_depth += 1; + _run-test-suite (elem) + setglobal _test_suite_depth -= 1; + var end = _cyan("end") + _test-print-indented "$end" + } else { + # It's a test case. + _run-test-case (name, elem) + } + } } -# Problem: this creates a global variable? -Args (&spec) { - flag --filter 'Regex of test descriptions' +proc _run-test-case (; name, block) { + var test = _yellow("test") + for _ in (0 .. _test_suite_depth) { + printf " " + } + printf "$test $name ..." + setglobal _assertion_result = 0 + try { + eval (block) + } + if (_status === 0) { + setglobal _num_test_succ += 1 + var ok = _green(" ok") + printf "$ok" + printf '\n' + } else { + setglobal _num_test_fail += 1 + printf '\n' + if (_assertion_result === 1) { + # An assertion failed. + var fail = _red("assertion FAILED") + _test-print-indented " $fail" + } elif (_assertion_result === 2) { + # There was an exception while evaluating an assertion. + var exn_in_assert = _red("assertion ERRORED:") + _test-print-indented " $exn_in_assert $_status" + } else { + # There was an exception in the test case outside of any `assert`. + var exn = _red("ERROR:") + _test-print-indented " $exn $_status" + } + } } -proc run-tests { - var opt, i = parseArgs(spec, ARGV) +########## +# Public # +########## - # TODO: - # - parse --filter foo, which you can use eggex for! +proc test-suite (name ; ; ; block) { + _start-test-suite (name) + eval (block) + _end-test-suite +} - for cmd in (_describe) { - # TODO: print filename and 'describe' name? +proc test-case (name ; ; ; block) { + var test_suite = _tests + for suite_name in (_test_suite_stack) { + if (not (suite_name in test_suite)) { + setvar test_suite[suite_name] = {} + } + setvar test_suite = test_suite[suite_name] + } + setvar test_suite[name] = block +} + +proc assert ( ; cond LAZY ) { + var success try { - eval (cmd) + setvar success = evalExpr(cond) } if (_status !== 0) { - echo 'failed' + setglobal _assertion_result = 2 + error "exception in assertion" + } elif (not success) { + setglobal _assertion_result = 1 + error "assertion failed" + } +} + +proc run-tests { + if (_testing_in_progress) { + error "Cannot run tests while testing is already in progress" + } + setglobal _testing_in_progress = true + setglobal _num_test_fail = 0 + setglobal _num_test_succ = 0 + + _run-test-suite (_tests) + setglobal _tests = {} + setglobal _testing_in_progress = false + + var total = _num_test_fail + _num_test_succ + if (total === 0) { + var na = _yellow("0 tests ran") + printf "$na" + printf '\n' + } elif (_num_test_fail === 0) { + var success = _green("$total tests succeeded") + printf "$success" + printf '\n' + } else { + var failure = _red("$_num_test_fail / $total tests failed") + printf "$failure" + printf '\n' } - } }