aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2022-02-03 11:21:01 +0000
committers-ol <s+removethis@s-ol.nu>2025-03-02 14:24:49 +0000
commit5851bcdf714457057ca9716984b136f37918bf80 (patch)
tree7041826b1a792bd23e4b16c8fc202372bb55d382
parentfix do, if, when (diff)
downloadalive-5851bcdf714457057ca9716984b136f37918bf80.tar.gz
alive-5851bcdf714457057ca9716984b136f37918bf80.zip
lib: add testing module
-rw-r--r--alv-lib/testing.moon97
-rw-r--r--spec/lang/testing_spec.moon80
2 files changed, 177 insertions, 0 deletions
diff --git a/alv-lib/testing.moon b/alv-lib/testing.moon
new file mode 100644
index 0000000..a321e43
--- /dev/null
+++ b/alv-lib/testing.moon
@@ -0,0 +1,97 @@
+import Constant, Op, Builtin, Input, Error, T, any, const from require "alv.base"
+import Tag, Cell from require "alv"
+
+assert_ = Constant.meta
+ meta:
+ name: 'assert'
+ summary: "Throw an error if a condition is false."
+ examples: { "(assert check [msg])" }
+ description: "
+Check is any bool result. When it is `false`, this op throws an error.
+`msg` is a str= value that is used as the error message.
+By default, the message contains the failing check expression."
+
+ value: class extends Builtin
+ assertOp = class extends Op
+ pattern = any.bool + const.str
+ setup: (inputs) =>
+ { check, msg } = pattern\match inputs
+
+ super
+ check: Input.hot check
+ msg: msg and Input.cold msg
+
+ tick: =>
+ { :check, :msg } = @unwrap_all!
+
+ if not check
+ error Error 'assertion', msg or "assertion failed"
+
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail == 1 or #tail == 2, "'assert' takes one or two arguments"
+
+ tag = @tag\clone Tag.parse '-1'
+ inner = Cell tag, {
+ Constant.literal T.opdef, assertOp, 'assert'
+ tail[1]
+ tail[2] or Constant.str "assertion failed: #{tail[1]\stringify 2}"
+ }
+ super inner\eval scope
+
+expect_eq = Constant.meta
+ meta:
+ name: 'expect='
+ summary: "Throw an error if arguments aren't equal."
+ examples: { "(expect= expected actual [actual…])" }
+ description: "
+Check if `expected` and `actual` are equal.
+If not, throws an error."
+
+ value: class extends Builtin
+ expectOp = class extends Op
+ pattern = any! + (any! + const.str)\named('val', 'src')*0
+ setup: (inputs) =>
+ { expected, values } = pattern\match inputs
+
+ super
+ expected: Input.hot expected
+ values: [Input.hot got.val for got in *values]
+ sources: [Input.cold got.src for got in *values]
+
+ type = expected\type!
+ for i, val in ipairs @inputs.values
+ same = type == val\type!
+ assert same, Error 'assertion', "Expected #{@inputs.sources[i]!} to equal #{expected.result} (got #{val.result})"
+
+ tick: =>
+ type = @inputs.expected\type!
+ expected = @inputs.expected!
+
+ for i, val in ipairs @inputs.values
+ assert (type\eq expected, val!), Error 'assertion', "Expected #{@inputs.sources[i]!} to equal #{@inputs.expected.result} (got #{val.result})"
+
+ eval: (scope, tail) =>
+ L\trace "evaling #{@}"
+ assert #tail > 1, "'expect=' takes at least two arguments"
+
+ children = {
+ Constant.literal T.opdef, expectOp, 'assert'
+ tail[1]
+ }
+ for arg in *tail[2,]
+ table.insert children, arg
+ table.insert children, Constant.str arg\stringify 2
+
+ tag = @tag\clone Tag.parse '-1'
+ inner = Cell tag, children
+ super inner\eval scope
+
+Constant.meta
+ meta:
+ name: 'testing'
+ summary: "Operators for testing."
+
+ value:
+ 'assert': assert_
+ 'expect=': expect_eq
diff --git a/spec/lang/testing_spec.moon b/spec/lang/testing_spec.moon
new file mode 100644
index 0000000..5be7848
--- /dev/null
+++ b/spec/lang/testing_spec.moon
@@ -0,0 +1,80 @@
+import TestPilot from require 'spec.test_setup'
+import T, Array, Constant from require 'alv'
+
+describe "testing", ->
+ test = TestPilot '', '(import* testing logic)\n'
+
+ describe "assert", ->
+ it "ignores true", ->
+ with COPILOT\eval_once '(assert true)'
+ assert.is.true \is_const!
+ assert.is.nil .result
+
+ with COPILOT\eval_once '(assert true "with message")'
+ assert.is.true \is_const!
+ assert.is.nil .result
+
+ it "throws on false", ->
+ assert.has.error -> COPILOT\eval_once '(assert false)'
+
+ it "shows failing expression", ->
+ err = assert.has.error -> COPILOT\eval_once '(assert false)'
+ assert.matches "assertion failed: false", err
+
+ err = assert.has.error -> COPILOT\eval_once '(assert (== 1 2))'
+ assert.matches "assertion failed: %(== 1 2%)", err
+
+ it "supports custom error messages", ->
+ err = assert.has.error -> COPILOT\eval_once '(assert (== "green" "red") "duck isnt green")'
+ assert.matches "duck isnt green", err
+
+ describe "expect=", ->
+ it "passes equal params", ->
+ with COPILOT\eval_once '(expect= 1 1 1)'
+ assert.is.true \is_const!
+ assert.is.nil .result
+
+ with COPILOT\eval_once '(expect= 2 2 2)'
+ assert.is.true \is_const!
+ assert.is.nil .result
+
+ with COPILOT\eval_once '(expect= true true)'
+ assert.is.true \is_const!
+ assert.is.nil .result
+
+ with COPILOT\eval_once '(expect= "hello" "hello")'
+ assert.is.true \is_const!
+ assert.is.nil .result
+
+ with COPILOT\eval_once '(expect= (array 1 2) (array 1 2))'
+ assert.is.true \is_const!
+ assert.is.nil .result
+
+ it "fails different values", ->
+ assert.has.error -> COPILOT\eval_once '(expect= 1 2)'
+ assert.has.error -> COPILOT\eval_once '(expect= 1 1 2)'
+ assert.has.error -> COPILOT\eval_once '(expect= 2 1 1)'
+
+ assert.has.error -> COPILOT\eval_once '(expect= true false)'
+
+ assert.has.error -> COPILOT\eval_once '(expect= "asdf" "bsdf")'
+ assert.has.error -> COPILOT\eval_once '(expect= (array 1 2) (array 1 3))'
+
+ it "fails different types", ->
+ assert.has.error -> COPILOT\eval_once '(expect= true 2)'
+ assert.has.error -> COPILOT\eval_once '(expect= true true 1)'
+ assert.has.error -> COPILOT\eval_once '(expect= true false "str")'
+
+ it "reports first failing arguments", ->
+ err = assert.has.error -> COPILOT\eval_once '(expect= 1 2)'
+ assert.matches "assertion error: Expected 2 to equal <num= 1> %(got <num= 2>%)", err
+
+ err = assert.has.error -> COPILOT\eval_once '(expect= 1 1 2 4)'
+ assert.matches "assertion error: Expected 2 to equal <num= 1> %(got <num= 2>%)", err
+
+ err = assert.has.error -> COPILOT\eval_once '(expect= true 2 3)'
+ assert.matches "assertion error: Expected 2 to equal <bool= true> %(got <num= 2>%)", err
+
+ it "reports full expressions", ->
+ err = assert.has.error -> COPILOT\eval_once '(import* math) (expect= 1 (+ 1 1))'
+ assert.matches "assertion error: Expected %(%+ 1 1%) to equal <num= 1> %(got <num= 2>%)", err