Adding my first attempt at writing a lisp
[sandbox] / mini / mini.py-unit-tests.mini
diff --git a/mini/mini.py-unit-tests.mini b/mini/mini.py-unit-tests.mini
new file mode 100644 (file)
index 0000000..2b80731
--- /dev/null
@@ -0,0 +1,262 @@
+# feature tests: variables
+(assert "Evaluating undefined variable throws exception"
+        (throws? undefined-identifier "UndefinedIdentifierError"))
+
+# pair tests
+(assert "`car` retrieves first argument to `cons`"
+        (car (cons true false)))
+(assert "`cdr` retrieves first argument to `cons`"
+        (cdr (cons false true)))
+
+# `=` tests
+(assert "`=` returns true for equal numbers" (= 0 0))
+(assert "`=` returns false for non-equal numbers" (not (= 0 1)))
+(assert "`=` returns true for equal strings" (= "Hello, world" "Hello, world"))
+(assert "`=` returns false for non-equal strings" (not (= "Hello, world" "Goodnight, moon")))
+(assert "`=` returns true for equal true values" (= true true))
+(assert "`=` returns true for equal false values" (= false false))
+(assert "`=` returns true for nils" (= nil nil))
+(assert "`=` returns true for equal symbols" (= :symbol :symbol))
+(assert "`=` returns false for non-equal symbols" (not (= :symbol :not-the-same)))
+(assert "`=` returns false for symbol-to-string comparison (string first)"
+        (not (= :symbol ":symbol")))
+(assert "`=` returns false for symbol-to-string comparison (symbol first)"
+        (not (= ":symbol" :symbol)))
+
+# `+` tests
+(assert "`+` adds" (= (+ 1 2) 3))
+
+# `-` tests
+(assert "`-` subtracts" (= (- 3 2) 1))
+
+# `*` tests
+(assert "`*` divides" (= (* 2 3) 6))
+
+# `/` tests
+(assert "`/` divides evenly" (= (/ 6 3) 2))
+(assert "`/` divides fractionally" (= (/ 7 2) 3.5))
+
+# `//` tests
+(assert "`//` divides evenly" (= (// 6 3) 2))
+(assert "`//` divides integrally" (= (// 7 2) 3))
+
+# `and` tests
+(assert "`and` returns false for both false" (not (and false false)))
+(assert "`and` returns false for left false and right true" (not (and false true)))
+(assert "`and` returns false for left true and right false" (not (and true false)))
+(assert "`and` returns true for both true" (and true true))
+(assert "`and` doesn't evalueate second argument if first is false" (not (and false (/ 1 0))))
+
+# `assert` tests
+(assert "`assert` executes without exception for true assertion" true)
+(assert "`assert` returns nil for true assertion" (= (assert true) nil))
+(assert "`assert` throws AssertionError for false assertion"
+        (throws? (assert false) "AssertionError"))
+(assert "`assert` throws TypeError for non-boolean assertion"
+        (throws? (assert 1) "TypeError"))
+(assert "`assert` can take multiple arguments" false true)
+(assert "`assert` executes assertion in a nested scope"
+        (assert "define identifier in nested scope"
+                (define identifier-in-nested-scope true)
+                true)
+        (not (defined? identifier-in-nested-scope)))
+
+# `merge-association-list-with-cons-dict` tests
+(assert "merge-association-list-with-cons-dict returns cons-dict"
+        (= :value
+           (cons-dict-get (merge-association-list-with-cons-dict (cons-list-zip (cons-list :key) (cons-list :value))
+                                                                 nil)
+                          :key)))
+
+# `concatenate` tests
+(assert "`concatenate` concatenates strings"
+        (= (concatenate "Hello, " "world")
+           "Hello, world"))
+
+# `cond` tests
+(assert "`cond` returns nil for no conditions"
+        (= (cond) nil))
+(assert "`cond` returns nil for no true conditions"
+        (= (cond (false :not-returned)
+                 (false :also-not-returned))
+           nil))
+(assert "`cond` returns true when pair evaluates to true"
+        (= (cond (false :not-returned)
+                 (true :returned)
+                 (false :also-not-returned))
+           :returned))
+(assert "`cond` does not execute bodies for false conditions"
+        (= (cond (false (/ 1 0))
+                 (true :returned))
+           :returned))
+
+# `cons-dict` tests
+(assert "`cons-dict-get` returns association created by `cons-dict-set`"
+        (cons-dict-get (cons-dict-set nil :key true) :key))
+
+# `cons-list` tests
+(assert "`cons-list` first argument is first item"
+        (car (cons-list true)))
+(assert "`cons-list` terminated by null cdr"
+        (= (cdr (cons-list false)) nil))
+
+# `cons-list?` tests
+(assert "`cons-list?` returns true for nil"
+        (cons-list? nil))
+(assert "`cons-list?` returns true for cons-list"
+        (cons-list? (cons-list 1 2 3)))
+(assert "`cons-list?` returns false for non cons-list"
+        (not (cons-list? 1)))
+
+# `cons-list-map` tests
+(assert "`cons-list-map` returns empty list for empty list"
+        (= (cons-list-map identifier->symbol nil) nil))
+(assert "`cons-list-map` calls mapping function on items"
+        (define inc (wrap (operative (i) _ (+ i 1))))
+        (define mapped (cons-list-map inc (cons-list 1 2)))
+        (and (= 2 (car mapped))
+             (= 3 (car (cdr mapped)))))
+
+# `cons-list-zip` tests
+(assert "`cons-list-zip` returns nil for empty lists"
+        (= (cons-list-zip nil nil) nil))
+(assert "`cons-list-zip` returns association list"
+        (define a-list (cons-list-zip (cons-list :a) (cons-list :b)))
+        (and (= :a (car (car a-list)))
+             (= :b (cdr (car a-list)))))
+
+# `define` tests
+(assert "`define` adds identifier to environment"
+        (define previously-undefined-identifier true)
+        previously-undefined-identifier)
+(assert "`define` throws exception for already-defined variable"
+        (define already-defined-identifier :value)
+        (throws? (define already-defined-identifier :another-value) "AlreadyDefinedError"))
+
+# `defined?` tests
+(assert "`defined?` returns true for defined identifier"
+        (define identifier :value)
+        (defined? identifier))
+(assert "`defined?` returns false for undefined identifier"
+        (not (defined? undefined-identifier)))
+
+# `function` tests
+(assert "`function` creates function that returns body"
+        ((function _ true)))
+(assert "`function` closes around defining environment"
+        (define defining-environment-identifier true)
+        ((function _ defining-environment-identifier)))
+(assert "`function` with an identifier arg binding receives list"
+        (and (= ((function args (car args)) :arg) :arg)
+             (= ((function args (cdr args)) :arg) nil)))
+(assert "`function` with an s-expression arg binding receives arguments bound to names"
+        (and (= ((function (foo bar) foo) :baz :qux) :baz)
+             (= ((function (foo bar) bar) :baz :qux) :qux)))
+(assert "`function` can recurse"
+        (define factorial (function (n) (if (= n 1) 1 (* n (factorial (- n 1))))))
+        (and (= 6 (factorial 3))
+             (= 120 (factorial 5))))
+
+# `get-current-environment` tests
+(assert "`get-current-environment` contains local variables as symbols"
+        (define local-identifier true)
+        (cons-dict-get (get-current-environment) :local-identifier))
+(assert "`get-current-environment` contains parent scope under :__parent__"
+        (define parent-identifier true)
+        (assert (cons-dict-get (cons-dict-get (get-current-environment) :__parent__) :parent-identifier))
+        true)
+
+# `identifier->symbol` tests
+(assert "`identifier->symbol` returns a symbol when given an identifier"
+        (= (identifier->symbol (quote identifier)) :identifier))
+
+# `identifier?` tests
+(assert "`identifier?` returns true for identifier"
+        (identifier? (quote identifier)))
+(assert "`identifier?` returns false for non-identifier"
+        (not (identifier? 1)))
+
+# `if` tests
+(assert "`if` returns second argument for true condition"
+        (if true true false))
+(assert "`if` returns third argument for false condition"
+        (if false false true))
+(assert "`if` doesn't execute third argument for true condition"
+        (if true true undefined-identifier))
+(assert "`if` doesn't execute second argument for false condition"
+        (if false undefined-identifier true))
+
+# `length` tests
+(assert "`length` returns length of string"
+        (= 12 (length "Hello, world")))
+
+# `not` tests
+(assert "`not` returns false for true" (= (not true) false))
+(assert "`not` returns true for false" (= (not false) true))
+(assert "`not` throws TypeError for non-boolean argument"
+        (throws? (not 1) "TypeError"))
+
+# `operative` tests
+(assert "`operative` creates callable operative"
+        ((operative () env true)))
+(assert "`operative` receives the environment"
+        (define receives-environment (operative () env (evaluate (quote true-identifier) env)))
+        (define true-identifier true)
+        (receives-environment))
+(assert "`operative` receives arguments"
+        ((operative (arg) env (evaluate arg env)) true))
+(assert "`operative` executes in its own environment"
+        ((operative () env
+                    (define should-not-be-defined false)))
+        (not (defined? should-not-be-defined)))
+(assert "`operative` doesn't evaluate its arguments"
+        ((operative (arg) env true) (/ 1 0)))
+(assert "`operative` with a symbol argument receives a cons-linked-list"
+        (and (= ((operative argument-list env (car argument-list)) 1) 1)
+             (= ((operative argument-list env (cdr argument-list)) 1) nil)))
+(assert "`operative` argument lists nest"
+        (and (= ((operative argument-list env (car (car argument-list))) (1)) 1)
+             (= ((operative argument-list env (cdr (car argument-list))) (1)) nil)))
+(assert "`operative` with an s-expression argument still receives lists"
+        (and (= ((operative (arg) env (car arg)) (1)) 1)
+             (= ((operative (arg) env (cdr arg)) (1)) nil)))
+(assert "`operative` executes body in a nested scope"
+        ((operative () env
+                    (define defined-in-nested-scope :value)
+                    (assert (defined? defined-in-nested-scope))))
+        (not (defined? defined-in-nested-scope)))
+(assert "`operative` with no args receives nil"
+        (= ((operative args-list _ args-list)) nil))
+
+# `or` tests
+(assert "`or` returns false for both false" (not (or false false)))
+(assert "`or` returns true for left false and right true" (or false true))
+(assert "`or` returns true for left true and right false" (or true false))
+(assert "`or` returns true for both true" (or true true))
+(assert "`or` doesn't evalueate second argument if first is true" (or true (/ 1 0)))
+
+# `read` tests
+(assert "`read` reads identifiers"
+    (= (identifier->symbol (read "identifier")) :identifier))
+
+# `slice` tests
+(assert "`slice` returns a slice of a string"
+        (= (slice "Hello, world" 1 11) "ello, worl"))
+(assert "`slice` uses start of string if start index is nil"
+        (= (slice "Hello, world" nil 11) "Hello, worl"))
+(assert "`slice` uses end of string if end index is nil"
+        (= (slice "Hello, world" 1 nil) "ello, world"))
+(assert "`slice` counts backward if start or end is negative"
+        (= (slice "Hello, world" -11 -1) "ello, worl"))
+
+# `throws?` tests
+(assert "`throws?` returns false when no exception is thrown"
+        (not (throws? (assert true) "AssertionError")))
+(assert "`throws?` returns true when the correct exception is thrown"
+        (throws? (assert false) "AssertionError"))
+(assert "`throws?` doesn't catch when the wrong exception is thrown"
+        (throws? (throws? (assert false) "TypeError") "AssertionError"))
+
+# `wrap` tests
+(assert "`wrap` evaluates arguments to wrapped operative"
+        ((wrap (operative (input) _ input)) (= 1 1)))