├── .github └── workflows │ └── main.yml ├── CHANGELOG.md ├── EXPLAIN.md ├── LICENSE ├── Makefile ├── README.md ├── module.l ├── reporters ├── default.l └── plain.l ├── test.l ├── test ├── test_internal.l ├── test_reporter_default.l ├── test_reporter_plain.l └── test_unit.l └── unit.l /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | timeout-minutes: 5 11 | 12 | strategy: 13 | matrix: 14 | arch: ['src', 'src64'] 15 | version: ['17.12', '18.6', '18.12', '19.6', '19.12', '20.6', 'latest', 'pil21'] 16 | 17 | steps: 18 | - uses: actions/checkout@v1 19 | - uses: aw/picolisp-action@v2 20 | with: 21 | version: ${{matrix.version}} 22 | architecture: ${{matrix.arch}} 23 | 24 | - name: Run the tests on PicoLisp ${{matrix.arch}} v${{matrix.version}} 25 | run: make check 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.1.0 (2020-09-10) 4 | 5 | * Remove colour of output for 'passed' tests in default reporter, 10x speed increase when tests pass 6 | * Update supported PicoLisp versions to 19.6, 19.12, 20.6, pil21 7 | 8 | ## 3.0.0 (2019-04-19) 9 | 10 | * Add Makefile for testing the library 11 | * Update supported PicoLisp version to 18.12 12 | * **Breaking change:** remove all support for PicoLisp namespaces 13 | 14 | ## 2.1.0 (2017-03-23) 15 | 16 | * Restore PicoLisp namespaces for backwards compatibility. Disable with PIL_NAMESPACES=false 17 | 18 | ## 2.0.0 (2017-03-09) 19 | 20 | * Remove the use of PicoLisp namespaces (functionally equivalent to 1.0.0) 21 | 22 | ## 1.0.0 (2015-06-09) 23 | 24 | * Production release 25 | 26 | ## 0.6.2 (2015-05-15) 27 | 28 | * Use 'unless' instead of 'when not =' 29 | * Don't rely on scoped variables 30 | * Remove unused arguments in 'print-failed' 31 | 32 | ## 0.6.1 (2015-04-08) 33 | 34 | * Suppress errors if the 'tput' command doesn't exist 35 | 36 | ## 0.6.0 (2015-04-05) 37 | 38 | * Move test reporters to their own directory 39 | * Add 'plain' reporter with tests 40 | * Get rid of *Passed, *Failed, *Counter global variables 41 | * Lispify some functions 42 | * Update .travis.yml 43 | * Update README.md and EXPLAIN.md 44 | 45 | ## 0.5.2 (2015-03-29) 46 | 47 | * Fix small potential bug in (passed) 48 | 49 | ## 0.5.1 (2015-03-29) 50 | 51 | * Small refactor: replace anonymous function with simpler eval 52 | * Update EXPLAIN.md 53 | 54 | ## 0.5.0 (2015-03-29) 55 | 56 | * Assertions now return T or NIL, for passed or failed tests 57 | * Nothing is printed to the display if (report) is not called 58 | * All test results are accumulated in *Results global variable 59 | 60 | ## 0.4.1 (2015-03-28) 61 | 62 | * Documentation update 63 | 64 | ## 0.4.0 (2015-03-24) 65 | 66 | * Avoid leaking global variables such as MODULE_INFO, *Colours, *Counter.. 67 | 68 | ## 0.3.1 (2015-03-19) 69 | 70 | * Move MODULE_INFO into module.l 71 | 72 | ## 0.3.0 (2015-03-19) 73 | 74 | * Add new assertion: assert-throws 75 | * Minor refactor with (plural?) 76 | * Stylistic changes to MODULE_INFO (credit Alexander Burger) 77 | * Update README.md 78 | 79 | ## 0.2.1 (2015-03-18) 80 | 81 | * Version bump, includes README.md and working .travis.yml 82 | 83 | ## 0.2.0 (2015-03-18) 84 | 85 | * Add new assertions: nil, t, includes, kind-of 86 | * Add .travis.yml for automated tests 87 | * Add a few tests for internal functions 88 | * Add tests for assertions (yes, we are testing the tests) 89 | 90 | ## 0.1.2 (2015-03-18) 91 | 92 | * Ensure expected/actual results are displayed correctly 93 | 94 | ## 0.1.1 (2015-03-18) 95 | 96 | * Adjust alignment of unit test numbers 97 | 98 | ## 0.1.0 (2015-03-18) 99 | 100 | * First release - Unit Testing framework for PicoLisp 101 | -------------------------------------------------------------------------------- /EXPLAIN.md: -------------------------------------------------------------------------------- 1 | # Explanation: Unit Testing framework for PicoLisp 2 | 3 | This document provides a short walkthrough of the source code for the [PicoLisp-Unit](https://github.com/aw/picolisp-unit) testing framework. 4 | 5 | I won't cover concepts which were discussed in previous source code explanations. You can read them here: 6 | 7 | * [Nanomsg Explanation](https://github.com/aw/picolisp-nanomsg/blob/master/EXPLAIN.md) 8 | * [JSON Explanation](https://github.com/aw/picolisp-json/blob/master/EXPLAIN.md) 9 | * [HTTPS Explanation](https://github.com/aw/picolisp-https/blob/master/EXPLAIN.md) 10 | 11 | This document is split into a few sections: 12 | 13 | 1. [Namespace leaky globals](#namespace-leaky-globals): Avoiding side-effects with Namespaces. 14 | 2. [Internal functions](#internal-functions): System calls and printing data to the terminal. 15 | * [System calls](#system-calls) 16 | * [Printing data](#printing-data) 17 | 3. [Public functions](#public-functions): Executing and asserting tests 18 | * [Executing a series of tests](#executing-tests) 19 | * [Asserting test results](#assertions) 20 | 21 | Make sure you read the [README](README.md) to get an idea of what this library does. 22 | 23 | # Namespace leaky globals 24 | 25 | Previously, I thought namespaces would protect my variables from modifying the `'pico` namespace. 26 | 27 | **I was wrong**. 28 | 29 | Here's an example: 30 | 31 | ```lisp 32 | : (setq *Myvar "I am a var") 33 | : (symbols 'mytest 'pico) 34 | -> pico 35 | mytest: (prinl *Myvar) 36 | -> "I am a var" 37 | 38 | # so far so good 39 | 40 | mytest: (setq *Myvar "You are a var") 41 | mytest: (symbols 'pico) 42 | : (prinl *Myvar) 43 | -> "You are a var" 44 | ``` 45 | 46 | What? Yes that's right, the `*Myvar` which was originally in the `'pico` (default) namespace was modified from within the `'mytest` namespace. This is normal, but not expected, and quite dangerous actually. 47 | 48 | ### The fix 49 | 50 | PicoLisp provides a nifty [local](http://software-lab.de/doc/refL.html#local) function for fixing that. I admit it's a bit of a pain and quite ugly, but it does the job. You can consider it similar to JavaScript's `var` expression. 51 | 52 | Let's try again: 53 | 54 | ```lisp 55 | : (setq *Myvar "I am a var") 56 | : (symbols 'mytest 'pico) 57 | -> pico 58 | mytest: (local *Myvar) 59 | mytest: (prinl *Myvar) 60 | -> NIL 61 | 62 | # that looks promising 63 | 64 | mytest: (setq *Myvar "You are a var") 65 | mytest: (symbols 'pico) 66 | : (prinl *Myvar) 67 | -> "I am a var" 68 | ``` 69 | 70 | This is much better, as we can now guarantee "global" functions and variables will not accidentally create side-effects by overwriting existing functions or variables. 71 | 72 | This change has been applied everywhere now, and only public/exported functions can affect the global namespace. 73 | 74 | # Internal functions 75 | 76 | Here we discuss system calls and printing data to the screen with specific alignment. 77 | 78 | ### System calls 79 | 80 | A cool unit-testing framework always displays colours. Just ask anyone from Node.js land. 81 | 82 | To achieve this, we make use of an external [system call](http://software-lab.de/doc/refC.html#call), the *NIX `tput` command. 83 | 84 | ```lisp 85 | [de colour (Colour) 86 | (cond ((assoc (lowc Colour) *Colours) (call 'tput "setaf" (cdr @))) 87 | ((= (lowc Colour) "bold") (call 'tput "bold")) 88 | (T (call 'tput "sgr0")) ) 89 | NIL ] 90 | ``` 91 | 92 | It's quite simple. The first condition checks if the `Colour` is part of the `*Colours` list. If yes, use `tput setaf` to set the terminal colour. 93 | 94 | The second condition checks if the `Colour` is `bold`. If yes, use `tput bold` to set the text to bold. 95 | 96 | The default catch-all (`T`) resets the terminal back to normal. 97 | 98 | I tend to stay away from external system calls as we're not always sure about the environment. In our case though, colour terminal is not such a big deal, and the `(colour)` function will return `NIL` whether `tput` succedes or fails. 99 | 100 | ### Printing data 101 | 102 | Printing data to the screen is simple in PicoLisp, until you realize there are at least 5 known functions to do that: [prin](http://software-lab.de/doc/refP.html#prin), [prinl](http://software-lab.de/doc/refP.html#prinl), [print](http://software-lab.de/doc/refP.html#print), [println](http://software-lab.de/doc/refP.html#println), and [printsp](http://software-lab.de/doc/refP.html#printsp). There's probably more. 103 | 104 | In some cases, using a combination of multiple _printing_ functions can be helpful to achieve your designed results: 105 | 106 | ```lisp 107 | [de print-expected (Result) 108 | (prin (align 8 " ") 109 | "Expected: " 110 | (colour "green") ) 111 | 112 | (println Result) 113 | (colour) ] 114 | ``` 115 | 116 | This has 2 print statements, but it only prints one line. The first uses [align](http://software-lab.de/doc/refA.html#align) to align the column to 8 spaces. This is really useful to help keep displayed text aligned in columns. The second prints the result and appends a newline at the end. 117 | 118 | An alternative would have been: 119 | 120 | ```lisp 121 | [de print-expected (Result) 122 | (prin (align 8 " ") 123 | "Expected: " 124 | (colour "green") 125 | Result 126 | "^J" ) 127 | (colour) ] 128 | ``` 129 | 130 | The `^J` character gets translated to a newline. 131 | 132 | You'll notice we often call `(colour)` without any arguments, to end-up in the _catch-all_ mentioned earlier, which resets the terminal. 133 | 134 | # Public functions 135 | 136 | Public functions do all the work in this library. They execute a series of tests, and they assert results to see if your test should pass or fail. 137 | 138 | I'll admit I was inspired mostly by Ruby's Minitest framework, which is quite huge compared to this one, but it pretty much does the same thing. 139 | 140 | ### Executing tests 141 | 142 | All good unit tests should be designed to run as **units**. O'Rly? Yeah. This means the order of the tests shouldn't matter at all. The units should not carry state, and this framework tests for that as well. 143 | 144 | The magic happens in a simple `(randomize)` function which takes the list of tests to execute, randomizes it, and then returns the list. 145 | 146 | ```lisp 147 | [de randomize (List) 148 | (if *My_tests_are_order_dependent 149 | List 150 | (by '((N) (rand 1 (size List))) sort List) ] 151 | ``` 152 | 153 | It first checks if the `*My_tests_are_order_dependent` variable is `NIL` (if it isn't, don't randomize). 154 | 155 | To randomize, it uses [by](http://software-lab.de/doc/refB.html#by), not to be confused with `(bye)` (that would be a major fail), and does stuff with it. 156 | 157 | There's our _anonymous function_ again, used as the 1st argument to `(by)`, which is cons'd to the `List` (3rd argument), and then applied to the 2nd argument, which is the [sort](http://software-lab.de/doc/refS.html#sort) function. 158 | 159 | The 1st argument (anonymous function) generates a random number between 1 and the size of the `List`. 160 | 161 | It's crazy how that works. I'm not even sure how I came up with that. 162 | 163 | ```lisp 164 | [de execute @ 165 | (mapcar eval (randomize (rest) ] 166 | ``` 167 | 168 | Once our list of tests is randomized, we run it through our favourite [mapcar](http://software-lab.de/doc/refM.html#mapcar) function which evaluates (runs) the test using the infamous [eval](http://software-lab.de/doc/refE.html#eval). 169 | 170 | > **Note:* Technically, assertions don't catch errors, so if your assertion were to throw an unhandled error, then the entire test suite would fail and ugly things will happen. In fact, your terminals colours might not even get reset. That's a good thing. You should handle your errors. 171 | 172 | ### Assertions 173 | 174 | PicoLisp natively supports assertions, and has a ton of predicates for testing and comparing values. 175 | 176 | This library introduces simple wrappers around those predicates, which then call a `(passed)` or `(failed)` function with additional arguments. 177 | 178 | ```lisp 179 | [de assert-equal (Expected Result Message) 180 | (if (= Expected Result) 181 | (passed Message) 182 | (failed Expected Result Message) ] 183 | ``` 184 | 185 | This one is quite simple, all it does is check if `Expected` is equal to `Result`. 186 | 187 | The `(passed)` and `(failed)` functions will return `T` or `NIL`, respectively, so these can be used within your own code as well, not only in the context of unit tests. 188 | 189 | The [other assertions](https://github.com/aw/picolisp-unit/blob/master/README.md#assertions-table) are quite similar and seem to cover most test cases. I've considered adding opposite tests such as `refute`, but I've rarely found a need for them as there are alternate approaches. 190 | 191 | # The end 192 | 193 | That's pretty much all I have to explain about the Unit Testing framework for PicoLisp. I'm very open to providing more details about functionality I've skipped, so just file an [issue](https://github.com/aw/picolisp-unit/issues/new) and I'll do my best. 194 | 195 | # License 196 | 197 | This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/). 198 | 199 | Copyright (c) 2015 Alexander Williams, Unscramble 200 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Alexander Williams, Unscramble 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # picolisp-unit Makefile 2 | 3 | .PHONY: all 4 | 5 | all: check 6 | 7 | check: 8 | @for i in plain default; do TEST_REPORTER="$$i" ./test.l; done 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unit Testing framework for PicoLisp 2 | 3 | [![GitHub release](https://img.shields.io/github/release/aw/picolisp-unit.svg)](https://github.com/aw/picolisp-unit) ![Build status](https://github.com/aw/picolisp-unit/workflows/CI/badge.svg?branch=master) 4 | 5 | This library can be used for Unit Testing your [PicoLisp](http://picolisp.com/) code. 6 | 7 | ![picolisp-unit](https://cloud.githubusercontent.com/assets/153401/6712097/b1b77e14-cd82-11e4-9603-cae666b8655d.png) 8 | 9 | Please read [EXPLAIN.md](EXPLAIN.md) to learn more about PicoLisp and this Unit Testing framework. 10 | 11 | 1. [Requirements](#requirements) 12 | 2. [Getting Started](#getting-started) 13 | 3. [Usage](#usage) 14 | 4. [Examples](#examples) 15 | 5. [Testing](#testing) 16 | 6. [Reporters](#reporters) 17 | 7. [Alternatives](#alternatives) 18 | 8. [Contributing](#contributing) 19 | 9. [Changelog](#changelog) 20 | 10. [License](#license) 21 | 22 | # Requirements 23 | 24 | * PicoLisp 32-bit or 64-bit `v3.1.9+`, or `pil21` 25 | * Tested up to PicoLisp `v20.6.29`, [see test runs](https://github.com/aw/picolisp-unit/commit/1a455ba10058dffacfb3d4ef930c89ee82a57a83/checks) 26 | 27 | # Getting Started 28 | 29 | This is a pure PicoLisp library with no external dependencies. You can either copy `unit.l` into your project(s), or add it as a git submodule to stay up-to-date (recommended). 30 | 31 | ### Add submodule to your project 32 | 33 | 1. git submodule add https://github.com/aw/picolisp-unit vendor/picolisp-unit 34 | 2. Include `unit.l` in your test file 35 | 3. See the [examples](#examples) below 36 | 37 | # Usage 38 | 39 | Here are some guidelines on how to use `unit.l`, but you're free to poke around the code and use it as you wish. 40 | 41 | There exists a few public functions: `(execute)`, `(report)`, and a bunch of `(assert-X)` where X is a type of assertion. 42 | 43 | * **(execute arg1 ..)** Executes arg1 to argN tests 44 | - `arg1` _Quoted List_: a list of assertions, example `'(assert-nil NIL "I AM NIL")` 45 | * **(report)** Prints a report of failed tests, and exits with 1 if there is a failure 46 | * **(assert-X)** Various assertions for testing 47 | 48 | ### Assertions table 49 | 50 | Only a few assertions exist for the moment; more [can](#contributing)/will be added. 51 | 52 | | Assertion | Arguments | Example | 53 | | :----------| :---------: | :-------: | 54 | | (assert-equal) | Expected Result Message | `(assert-equal 0 0 "It must be zero")` | 55 | | (assert-nil) | Result Message | `(assert-nil NIL "It must be NIL")` | 56 | | (assert-t) | Result Message | `(assert-t T "I pity the fool!")` | 57 | | (assert-includes) | String List Message | `(assert-includes "abc" '("def") "It includes abc")` | 58 | | (assert-kind-of) | Type Value Message | `(assert-kind-of 'Number 42 "The answer..")` | 59 | | (assert-throws) | Type Error 'Result Message | `(assert-throws 'Err "fail" '(throw 'Err "fail") "Throws a fail")` | 60 | 61 | ### (assert-kind-of) types 62 | 63 | There are 5 types currently defined: 64 | 65 | * **'Flag** Uses [flg?](http://software-lab.de/doc/refF.html#flg?) to test if the value is `T` or `NIL`. 66 | * **'String** Uses [str?](http://software-lab.de/doc/refS.html#str?) to test if the value is a string. 67 | * **'Number** Uses [num?](http://software-lab.de/doc/refN.html#num?) to test if a value is a number. 68 | * **'List** Uses [lst?](http://software-lab.de/doc/refL.html#lst?) to test if a value is a list. 69 | * **'Atom** Uses [atom](http://software-lab.de/doc/refA.html#atom) to test if a value is an atom. 70 | 71 | >> **Warning:** `NIL` is also list, but will be asserted as a `'Flag` when using `(assert-kind-of)`. Use `(assert-nil)` if you specifically want to know if the value is `NIL`. 72 | 73 | ### Notes 74 | 75 | * Use `(execute)` at the start of your tests (see [examples](#examples)) 76 | * Unit Tests are run in **RANDOM** order. 77 | * If your tests are **order dependent**, then you can: `(setq *My_tests_are_order_dependent T)` 78 | * Colours and bold text are only displayed if your terminal supports it, and if your system has the `tput` command. 79 | * The `(assert-includes)` function uses [sub?](http://software-lab.de/doc/refS.html#sub?) to find a substring in a string or list. 80 | * The `(assert-throws)` function requires the `(throw)` to be [quoted](http://software-lab.de/doc/refQ.html#quote). 81 | * Assertions return `T` if the test passed, and `NIL` if the test failed 82 | * Results of all tests are accumulated in the `*Results` global variable 83 | 84 | ### Test results 85 | 86 | The `*Results` global variable is a list of lists, which will have one of two formats: 87 | 88 | ((NIL 1 "should be NIL" NIL T) (T 2 "should be NIL")) 89 | 90 | 1. `NIL` or `T` in the `(car)` if the test failed or passed, respectively 91 | 2. Number indicating the counter for the test 92 | 3. Message of the test 93 | 4. Expected result (only when the test failed) 94 | 5. Actual result (only when the test failed) 95 | 96 | # Examples 97 | 98 | It is recommended to create a "test runner" similar to what's found in [test.l](https://github.com/aw/picolisp-unit/blob/master/test.l). 99 | 100 | Tests should be placed in a `test/` directory, and the test files should be prefixed with `test_`. 101 | 102 | ### A simple unit test 103 | 104 | ```lisp 105 | pil + 106 | 107 | (load "unit.l") 108 | 109 | (setq *My_tests_are_order_dependent NIL) 110 | 111 | [execute 112 | '(assert-equal 0 0 "(assert-equal) should assert equal values") 113 | '(assert-nil NIL "(assert-nil) should assert NIL") 114 | '(assert-t T "(assert-t) should assert T") 115 | '(assert-includes "abc" '("xyzabcdef") "(assert-includes) should assert includes") 116 | '(assert-kind-of 'Flag NIL "(assert-kind-of) should assert a Flag (NIL)") 117 | '(assert-kind-of 'Flag T "(assert-kind-of) should assert a Flag (T)") 118 | '(assert-kind-of 'String "picolisp" "(assert-kind-of) should assert a String") 119 | '(assert-kind-of 'Number 42 "(assert-kind-of) should assert a Number") 120 | '(assert-kind-of 'List (1 2 3 4) "(assert-kind-of) should assert a List") 121 | '(assert-kind-of 'Atom 'atom "(assert-kind-of) should assert a Atom") ] 122 | 123 | # output 124 | 125 | 1) ✓ (assert-kind-of) should assert a List 126 | 2) ✓ (assert-kind-of) should assert a Flag (T) 127 | 3) ✓ (assert-includes) should assert includes 128 | 4) ✓ (assert-t) should assert T 129 | 5) ✓ (assert-kind-of) should assert a Number 130 | 6) ✓ (assert-nil) should assert NIL 131 | 7) ✓ (assert-equal) should assert equal values 132 | 8) ✓ (assert-kind-of) should assert a Flag (NIL) 133 | 9) ✓ (assert-kind-of) should assert a Atom 134 | 10) ✓ (assert-kind-of) should assert a String 135 | 136 | -> (T T T T T T T T T T) 137 | ``` 138 | 139 | ### When tests fail 140 | 141 | ```lisp 142 | pil + 143 | 144 | (load "unit.l") 145 | 146 | [execute 147 | '(assert-equal 0 1 "(assert-equal) should assert equal values") 148 | '(assert-nil NIL "(assert-nil) should assert NIL") 149 | '(assert-t NIL "(assert-t) should assert T") 150 | '(assert-includes "abc" '("hello") "(assert-includes) should assert includes") 151 | '(assert-kind-of 'Flag "abc" "(assert-kind-of) should assert a Flag (NIL)") ] 152 | 153 | (report) 154 | 155 | # output 156 | 157 | -> (T NIL NIL NIL NIL) 158 | 159 | 1) ✓ (assert-nil) should assert NIL 160 | 2) ✕ (assert-kind-of) should assert a Flag (NIL) 161 | 3) ✕ (assert-includes) should assert includes 162 | 4) ✕ (assert-t) should assert T 163 | 5) ✕ (assert-equal) should assert equal values 164 | 165 | -> ---- 166 | 1 test passed, 4 tests failed 167 | 168 | Failed tests: 169 | 170 | - 2) (assert-kind-of) should assert a Flag (NIL) 171 | Expected: "abc should be of type Flag" 172 | Actual: String 173 | 174 | - 3) (assert-includes) should assert includes 175 | Expected: "abc in hello" 176 | Actual: "abc" 177 | 178 | - 4) (assert-t) should assert T 179 | Expected: T 180 | Actual: NIL 181 | 182 | - 5) (assert-equal) should assert equal values 183 | Expected: 0 184 | Actual: 1 185 | ``` 186 | 187 | # Testing 188 | 189 | This testing library has its own set of tests (hehe). You can use those as examples as well. To run them type: 190 | 191 | ./test.l 192 | 193 | or 194 | 195 | make check 196 | 197 | # Reporters 198 | 199 | If you don't call `(report)`, the test results will not be printed to the screen. 200 | 201 | This allows you to create your own custom reporter and use it to output the test results however you like. 202 | 203 | There are two existing reporters, to use one set the `TEST_REPORTER` environment variable: 204 | 205 | TEST_REPORTER=plain ./test.l 206 | 207 | or 208 | 209 | TEST_REPORTER=default ./test.l 210 | 211 | ### Creating a custom reporter 212 | 213 | Your custom reporter only needs to implement the `(print-report)` function. This gets called by the public `(report)` function. 214 | 215 | Add a file to the `reporters/` directory (ex: `custom.l`). The filename (without `.l` extension) will be the name of your reporter. 216 | 217 | You can use any of the following helper functions/variables: 218 | 219 | * `(colour)` 220 | * `(plural?)` 221 | * `(get-results)` 222 | * `*Results` global variable 223 | 224 | > **Note:** Avoid using the internal functions as they might change without notice. 225 | 226 | * All test results can be obtained with the `*Results` global variable. 227 | * All _failed_ results can be obtained with `(get-results NIL)`. 228 | * All _passed_ results can be obtained with `(get-results T)`. 229 | 230 | Please make a pull-request if you would like your custom reporter to be added to this library. 231 | 232 | # Alternatives 233 | 234 | The alternative approaches to testing in PicoLisp involve the use of [test](http://software-lab.de/doc/refT.html#test) and [assert](http://software-lab.de/doc/refA.html#assert). 235 | 236 | # Contributing 237 | 238 | If you find any bugs or issues, please [create an issue](https://github.com/aw/picolisp-unit/issues/new). 239 | 240 | If you want to improve this library, please make a pull-request. 241 | 242 | ### Contributors 243 | 244 | * @cryptorick [Rick Hanson](https://github.com/cryptorick) 245 | 246 | # Changelog 247 | 248 | * [Changelog](CHANGELOG.md) 249 | 250 | # License 251 | 252 | [MIT License](LICENSE) 253 | 254 | Copyright (c) 2015-2020 Alexander Williams, Unscramble 255 | -------------------------------------------------------------------------------- /module.l: -------------------------------------------------------------------------------- 1 | [de MODULE_INFO 2 | ("name" "unit") 3 | ("version" "3.1.0") 4 | ("summary" "Unit Testing framework for PicoLisp") 5 | ("source" "https://github.com/aw/picolisp-unit.git") 6 | ("author" "Alexander Williams") 7 | ("license" "MIT") 8 | ("copyright" "(c) 2020 Alexander Williams, Unscramble ") ] 9 | -------------------------------------------------------------------------------- /reporters/default.l: -------------------------------------------------------------------------------- 1 | # reporters/default.l 2 | # 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2020 Alexander Williams, Unscramble 6 | 7 | (de *Colours ("red" . 1) 8 | ("green" . 2) 9 | ("yellow" . 3) 10 | ("blue" . 4) ) 11 | 12 | 13 | [de print-report () 14 | (mapcar '((N) (prin (align 3 (cadr N)) ") ") 15 | (if (=T (car N)) 16 | (print-passed (caddr N)) 17 | (print-failed (caddr N)) ) ) 18 | *Results ) 19 | 20 | (let (Passed_results (get-results T) 21 | Failed_results (get-results) ) 22 | 23 | (prinl (colour "bold") 24 | "----^J" 25 | (colour "green") 26 | (length Passed_results) 27 | " test" (plural? Passed_results) 28 | " passed, " 29 | (colour "red") 30 | (length Failed_results) 31 | " test" (plural? Failed_results) 32 | " failed" 33 | (colour) ) 34 | 35 | (report-failed Failed_results) 36 | (exit-gracefully Failed_results) ] 37 | 38 | [de print-passed @ 39 | (prinl (char (hex "2713")) 40 | (align 2 " ") 41 | (next) ) ] 42 | 43 | [de print-failed (Message) 44 | (prinl (colour "red") 45 | (char (hex "2715")) 46 | (align 2 " ") 47 | Message 48 | (colour) ] 49 | 50 | (de report-failed (Failed_results) 51 | (when Failed_results 52 | (prinl "^J" (colour "red") "^J Failed test" (plural? Failed_results) ": ") 53 | 54 | (mapcar 55 | '((N) (print-error (cadr N) (caddr N)) 56 | (print-expected (; N 4)) 57 | (print-got (; N 5)) ) 58 | Failed_results ) 59 | 60 | (colour) ] 61 | 62 | [de print-error (Number Result) 63 | (prinl (colour "red") 64 | "^J" 65 | (align 4 " - ") 66 | Number 67 | ") " 68 | Result 69 | (colour) ] 70 | 71 | [de print-expected (Result) 72 | (prin (align 8 " ") 73 | "Expected: " 74 | (colour "green") ) 75 | 76 | (println Result) 77 | (colour) ] 78 | 79 | [de print-got (Result) 80 | (prin (align 8 " ") 81 | (align 10 " Actual: ") 82 | (colour "red") ) 83 | 84 | (println Result) 85 | (colour) ] 86 | 87 | [de exit-gracefully (Failed_results) 88 | (if Failed_results 89 | (bye 1) 90 | (bye) ] 91 | -------------------------------------------------------------------------------- /reporters/plain.l: -------------------------------------------------------------------------------- 1 | # reporters/plain.l 2 | # 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2020 Alexander Williams, Unscramble 6 | 7 | [de print-report () 8 | (mapcar println *Results) 9 | 10 | (if (get-results) 11 | (bye 1) 12 | (bye) ] 13 | -------------------------------------------------------------------------------- /test.l: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pil 2 | 3 | (load "unit.l") 4 | 5 | (chdir "test/" 6 | (mapcar load '("test_internal.l" "test_unit.l" `(pack "test_reporter_" (if (sys "TEST_REPORTER") @ "default") ".l"))) ) 7 | 8 | (report) 9 | -------------------------------------------------------------------------------- /test/test_internal.l: -------------------------------------------------------------------------------- 1 | (prinl "^J Testing Unit Testing^J") 2 | 3 | (de test-dont-randomize () 4 | (setq *My_tests_are_order_dependent T) 5 | (let Result (randomize '(1 2 3 4)) 6 | (setq *My_tests_are_order_dependent NIL) 7 | Result ) ) 8 | 9 | [execute 10 | '(assert-kind-of 'List (randomize '(1 2)) "Randomize the list") 11 | '(assert-equal '(1 2 3 4) (test-dont-randomize) "Don't randomize the list") ] 12 | -------------------------------------------------------------------------------- /test/test_reporter_default.l: -------------------------------------------------------------------------------- 1 | (setq *My_tests_are_order_dependent NIL) 2 | 3 | [execute 4 | '(assert-nil (prog (colour "yellow") (colour)) "Set colour to yellow") 5 | '(assert-nil (colour) "Reset colour") ] 6 | -------------------------------------------------------------------------------- /test/test_reporter_plain.l: -------------------------------------------------------------------------------- 1 | (setq *My_tests_are_order_dependent NIL) 2 | 3 | [execute 4 | '(assert-t (fun? print-report) "Ensure (print-error) is a function") ] 5 | -------------------------------------------------------------------------------- /test/test_unit.l: -------------------------------------------------------------------------------- 1 | (setq *My_tests_are_order_dependent NIL) 2 | 3 | [execute 4 | '(assert-equal 0 0 "(assert-equal) should assert equal values") 5 | '(assert-nil NIL "(assert-nil) should assert NIL") 6 | '(assert-t T "(assert-t) should assert T") 7 | '(assert-includes "abc" '("xyzabcdef") "(assert-includes) should assert includes") 8 | '(assert-kind-of 'Flag NIL "(assert-kind-of) should assert a Flag (NIL)") 9 | '(assert-kind-of 'Flag T "(assert-kind-of) should assert a Flag (T)") 10 | '(assert-kind-of 'String "picolisp" "(assert-kind-of) should assert a String") 11 | '(assert-kind-of 'Number 42 "(assert-kind-of) should assert a Number") 12 | '(assert-kind-of 'List (1 2 3 4) "(assert-kind-of) should assert a List") 13 | '(assert-kind-of 'Atom 'atom "(assert-kind-of) should assert a Atom") 14 | '(assert-throws 'InternalError '(UnitTestError . "Unit test throws an error") '(throw 'InternalError (cons 'UnitTestError "Unit test throws an error")) "(assert-throws) should assert a thrown error") ] 15 | -------------------------------------------------------------------------------- /unit.l: -------------------------------------------------------------------------------- 1 | # unit.l 2 | # 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2020 Alexander Williams, Unscramble 6 | 7 | (load (pack (car (file)) "module.l")) 8 | (load (pack (car (file)) "reporters/" (if (sys "TEST_REPORTER") @ "default") ".l")) 9 | 10 | (setq 11 | *My_tests_are_order_dependent NIL 12 | *Results NIL ) 13 | 14 | 15 | # initialize 16 | (seed (in "/dev/urandom" (rd 20))) 17 | 18 | 19 | # internal 20 | [de randomize (List) 21 | (if *My_tests_are_order_dependent 22 | List 23 | (by '((N) (rand 1 (size List))) sort List) ] 24 | 25 | [de passed (Message) 26 | (queue-results T Message) ] 27 | 28 | [de failed (Expected Result Message) 29 | (queue-results NIL Message Expected Result) ] 30 | 31 | [de queue-results (Flag . @) 32 | (queue '*Results 33 | (pass list 34 | Flag 35 | (inc (length *Results)) ) ) 36 | 37 | Flag ] 38 | 39 | 40 | # reporter 41 | [de colour (Colour) 42 | (err "/dev/null" 43 | (cond ((assoc (lowc Colour) *Colours) (call 'tput "setaf" (cdr @))) 44 | ((= (lowc Colour) "bold") (call 'tput "bold")) 45 | (T (call 'tput "sgr0")) ) 46 | NIL ] 47 | 48 | [de plural? (String) 49 | (unless (= (length String) 1) "s") ] 50 | 51 | (de get-results (Type) 52 | (filter '((N) (= Type (car N))) *Results) ) 53 | 54 | 55 | # public 56 | [de execute @ 57 | (mapcar eval (randomize (rest) ] 58 | 59 | [de assert-equal (Expected Result Message) 60 | (if (= Expected Result) 61 | (passed Message) 62 | (failed Expected Result Message) ] 63 | 64 | [de assert-nil (Result Message) 65 | (if Result 66 | (failed NIL Result Message) 67 | (passed Message) ] 68 | 69 | [de assert-t (Result Message) 70 | (if (=T Result) 71 | (passed Message) 72 | (failed T Result Message) ] 73 | 74 | [de assert-includes (String List Message) 75 | (if (sub? String List) 76 | (passed Message) 77 | (failed (pack String " in " List) String Message) ] 78 | 79 | [de assert-kind-of (Type Value Message) 80 | (let Result 81 | (cond ((flg? Value) 'Flag) 82 | ((str? Value) 'String) 83 | ((num? Value) 'Number) 84 | ((lst? Value) 'List) 85 | ((atom Value) 'Atom) ) 86 | 87 | (if (= Type Result) 88 | (passed Message) 89 | (failed (text "@1 should be of type @2" Value Type) Result Message) ] 90 | 91 | [de assert-throws (Type Error Result Message) 92 | (let Result (catch Type (eval Result) NIL) 93 | (if (= Error Result) 94 | (passed Message) 95 | (failed Error Result Message) ] 96 | 97 | (de report () 98 | (print-report) ) 99 | --------------------------------------------------------------------------------